mirror of
https://github.com/directus/directus.git
synced 2026-02-12 07:34:59 -05:00
@@ -37,6 +37,7 @@ export async function applyDiff(
|
||||
const schema = options?.schema ?? (await getSchema({ database, bypassCache: true }));
|
||||
|
||||
const nestedActionEvents: ActionEventParams[] = [];
|
||||
|
||||
const mutationOptions: MutationOptions = {
|
||||
autoPurgeSystemCache: false,
|
||||
bypassEmitAction: (params) => nestedActionEvents.push(params),
|
||||
@@ -119,6 +120,7 @@ export async function applyDiff(
|
||||
logger.error(
|
||||
`Failed to delete collection "${collection}" due to relation "${relation.collection}.${relation.field}"`
|
||||
);
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -153,6 +155,7 @@ export async function applyDiff(
|
||||
|
||||
// Check if parent collection already exists in schema
|
||||
const parentExists = currentSnapshot.collections.find((c) => c.collection === groupName) !== undefined;
|
||||
|
||||
// If this is a new collection and the parent collection doesn't exist in current schema ->
|
||||
// Check if the parent collection will be created as part of applying this snapshot ->
|
||||
// If yes -> this collection will be created recursively
|
||||
@@ -165,6 +168,7 @@ export async function applyDiff(
|
||||
snapshotDiff.collections.filter(
|
||||
({ collection, diff }) => diff[0]?.kind === DiffKind.NEW && collection === groupName
|
||||
).length > 0;
|
||||
|
||||
// Has group, but parent is not new, parent is also not being created in this snapshot apply
|
||||
if (parentExists && !parentWillBeCreatedInThisApply) return true;
|
||||
|
||||
@@ -230,6 +234,7 @@ export async function applyDiff(
|
||||
deepDiff.applyChange(acc, undefined, currentDiff);
|
||||
return acc;
|
||||
}, cloneDeep(currentField));
|
||||
|
||||
await fieldsService.updateField(collection, newValues, mutationOptions);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to update field "${collection}.${field}"`);
|
||||
@@ -286,6 +291,7 @@ export async function applyDiff(
|
||||
deepDiff.applyChange(acc, undefined, currentDiff);
|
||||
return acc;
|
||||
}, cloneDeep(currentRelation));
|
||||
|
||||
await relationsService.updateOne(collection, field, newValues, mutationOptions);
|
||||
} catch (err) {
|
||||
logger.error(`Failed to update relation "${collection}.${field}"`);
|
||||
|
||||
@@ -165,6 +165,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
||||
`${aliasedParentCollection}.${relation.field}`,
|
||||
`${alias}.${schema.collections[relation.related_collection!]!.primary}`
|
||||
);
|
||||
|
||||
aliasMap[aliasKey]!.collection = relation.related_collection!;
|
||||
} else if (relationType === 'a2o') {
|
||||
const pathScope = pathParts[0]!.split(':')[1];
|
||||
@@ -187,6 +188,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
aliasMap[aliasKey]!.collection = pathScope;
|
||||
} else if (relationType === 'o2a') {
|
||||
rootQuery.leftJoin({ [alias]: relation.collection }, (joinClause) => {
|
||||
@@ -201,6 +203,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
aliasMap[aliasKey]!.collection = relation.collection;
|
||||
|
||||
hasMultiRelational = true;
|
||||
@@ -210,6 +213,7 @@ function addJoin({ path, collection, aliasMap, rootQuery, schema, relations, kne
|
||||
`${aliasedParentCollection}.${schema.collections[relation.related_collection!]!.primary}`,
|
||||
`${alias}.${relation.field}`
|
||||
);
|
||||
|
||||
aliasMap[aliasKey]!.collection = relation.collection;
|
||||
|
||||
hasMultiRelational = true;
|
||||
@@ -295,6 +299,7 @@ export function applySort(
|
||||
relations,
|
||||
schema,
|
||||
});
|
||||
|
||||
const [alias, field] = columnPath.split('.');
|
||||
|
||||
if (!hasMultiRelationalSort) {
|
||||
|
||||
@@ -50,6 +50,7 @@ describe('filter items', () => {
|
||||
{},
|
||||
],
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual(items.filter((item) => item.action === 'read'));
|
||||
});
|
||||
|
||||
@@ -59,6 +60,7 @@ describe('filter items', () => {
|
||||
_eq: 'read',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result).toStrictEqual(items.filter((item) => item.action === 'read'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,10 @@ import { getConfigFromEnv } from './get-config-from-env.js';
|
||||
|
||||
export function generateHash(stringToHash: string): Promise<string> {
|
||||
const argon2HashConfigOptions = getConfigFromEnv('HASH_', 'HASH_RAW'); // Disallow the HASH_RAW option, see https://github.com/directus/directus/discussions/7670#discussioncomment-1255805
|
||||
|
||||
// associatedData, if specified, must be passed as a Buffer to argon2.hash, see https://github.com/ranisalt/node-argon2/wiki/Options#associateddata
|
||||
'associatedData' in argon2HashConfigOptions &&
|
||||
(argon2HashConfigOptions['associatedData'] = Buffer.from(argon2HashConfigOptions['associatedData']));
|
||||
|
||||
return argon2.hash(stringToHash, argon2HashConfigOptions);
|
||||
}
|
||||
|
||||
@@ -183,6 +183,7 @@ export default async function getASTFromQuery(
|
||||
query: {},
|
||||
relatedCollection: foundRelation.collection,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,8 +204,10 @@ describe('get cache headers', () => {
|
||||
return matchingKey ? (scenario.input.headers as any)?.[matchingKey] : undefined;
|
||||
}),
|
||||
} as Partial<Request>;
|
||||
|
||||
factoryEnv = scenario.input.env;
|
||||
const { ttl, globalCacheSettings, personalized } = scenario.input;
|
||||
|
||||
expect(getCacheControlHeader(mockRequest as Request, ttl, globalCacheSettings, personalized)).toEqual(
|
||||
scenario.output
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ vi.mock('../../src/env', () => {
|
||||
CAMELCASE_OBJECT__FIRST_KEY: 'firstValue',
|
||||
CAMELCASE_OBJECT__SECOND_KEY: 'secondValue',
|
||||
};
|
||||
|
||||
return {
|
||||
default: MOCK_ENV,
|
||||
getEnv: () => MOCK_ENV,
|
||||
|
||||
@@ -30,6 +30,7 @@ export function getConfigFromEnv(
|
||||
const path = key
|
||||
.split('__')
|
||||
.map((key, index) => (index === 0 ? transform(transform(key.slice(prefix.length))) : transform(key)));
|
||||
|
||||
set(config, path.join('.'), value);
|
||||
} else {
|
||||
config[transform(key.slice(prefix.length))] = value;
|
||||
|
||||
@@ -10,6 +10,7 @@ const query = `
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const variables = JSON.stringify({ id: 1 });
|
||||
const additionalProperty = 'test';
|
||||
|
||||
|
||||
@@ -125,6 +125,7 @@ export default function getLocalType(
|
||||
if (special.includes('uuid') || special.includes('file')) return 'uuid';
|
||||
if (special.includes('cast-timestamp')) return 'timestamp';
|
||||
if (special.includes('cast-datetime')) return 'dateTime';
|
||||
|
||||
if (type?.startsWith('geometry')) {
|
||||
return (special[0] as Type) || 'geometry';
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ test('Returns the payload of an access token', () => {
|
||||
const payload = { id: 1, role: 1, app_access: true, admin_access: true };
|
||||
const token = jwt.sign(payload, secret, options);
|
||||
const result = verifyAccessJWT(token, secret);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 1,
|
||||
role: 1,
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, permissions: conditionalFilter },
|
||||
{ ...permissionTemplate, permissions: conditionalFilter2 }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({
|
||||
...permissionTemplate,
|
||||
permissions: {
|
||||
@@ -36,6 +37,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, validation: conditionalFilter },
|
||||
{ ...permissionTemplate, validation: conditionalFilter2 }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({
|
||||
...permissionTemplate,
|
||||
validation: {
|
||||
@@ -50,6 +52,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, permissions: conditionalFilter },
|
||||
{ ...permissionTemplate, permissions: conditionalFilter2 }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({
|
||||
...permissionTemplate,
|
||||
permissions: {
|
||||
@@ -64,6 +67,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, validation: conditionalFilter },
|
||||
{ ...permissionTemplate, validation: conditionalFilter2 }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({
|
||||
...permissionTemplate,
|
||||
validation: {
|
||||
@@ -78,6 +82,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, permissions: fullFilter },
|
||||
{ ...permissionTemplate, permissions: conditionalFilter }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({ ...permissionTemplate, permissions: fullFilter });
|
||||
});
|
||||
|
||||
@@ -87,6 +92,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, validation: fullFilter },
|
||||
{ ...permissionTemplate, validation: conditionalFilter }
|
||||
);
|
||||
|
||||
expect(mergedPermission).toStrictEqual({ ...permissionTemplate, validation: fullFilter });
|
||||
});
|
||||
|
||||
@@ -96,6 +102,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, permissions: fullFilter },
|
||||
{ ...permissionTemplate, permissions: conditionalFilter }
|
||||
);
|
||||
|
||||
const expectedPermission = {
|
||||
...permissionTemplate,
|
||||
permissions: {
|
||||
@@ -112,6 +119,7 @@ describe('merging permissions', () => {
|
||||
{ ...permissionTemplate, validation: fullFilter },
|
||||
{ ...permissionTemplate, validation: conditionalFilter }
|
||||
);
|
||||
|
||||
const expectedPermission = {
|
||||
...permissionTemplate,
|
||||
validation: {
|
||||
|
||||
@@ -27,6 +27,7 @@ export function sanitizeField(field: Field | undefined, sanitizeAllSchema = fals
|
||||
if (!field) return field;
|
||||
|
||||
const defaultPaths = ['collection', 'field', 'type', 'meta', 'name', 'children'];
|
||||
|
||||
const pickedPaths = sanitizeAllSchema
|
||||
? defaultPaths
|
||||
: [
|
||||
|
||||
@@ -19,6 +19,7 @@ export class Url {
|
||||
!isProtocolRelative && !isRootRelative && !isPathRelative
|
||||
? parsedUrl.protocol.substring(0, parsedUrl.protocol.length - 1)
|
||||
: null;
|
||||
|
||||
this.host = !isRootRelative && !isPathRelative ? parsedUrl.hostname : null;
|
||||
this.port = parsedUrl.port !== '' ? parsedUrl.port : null;
|
||||
this.path = parsedUrl.pathname.split('/').filter((p) => p !== '');
|
||||
|
||||
@@ -22,6 +22,7 @@ test('should fail on invalid hash', () => {
|
||||
hash: 'abc',
|
||||
diff: { collections: [{ collection: 'test', diff: [] }], fields: [], relations: [] },
|
||||
} as SnapshotDiffWithHash;
|
||||
|
||||
const snapshot = { hash: 'xyz' } as SnapshotWithHash;
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).toThrowError(
|
||||
@@ -56,6 +57,7 @@ describe('should throw accurate error', () => {
|
||||
const diff = baseDiff({
|
||||
collections: [{ collection: 'test', diff: [{ kind: 'N', rhs: {} as Collection }] }],
|
||||
});
|
||||
|
||||
const snapshot = baseSnapshot({ collections: [{ collection: 'test' } as Collection] });
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).toThrowError(
|
||||
@@ -77,6 +79,7 @@ describe('should throw accurate error', () => {
|
||||
const diff = baseDiff({
|
||||
fields: [{ collection: 'test', field: 'test', diff: [{ kind: 'N', rhs: {} as SnapshotField }] }],
|
||||
});
|
||||
|
||||
const snapshot = baseSnapshot({ fields: [{ collection: 'test', field: 'test' } as SnapshotField] });
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).toThrowError(
|
||||
@@ -105,6 +108,7 @@ describe('should throw accurate error', () => {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const snapshot = baseSnapshot({
|
||||
relations: [{ collection: 'test', field: 'test', related_collection: 'relation' } as SnapshotRelation],
|
||||
});
|
||||
@@ -248,6 +252,7 @@ test('should not throw error for diffs with varying types of lhs/rhs', () => {
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const snapshot = { hash: 'abc' } as SnapshotWithHash;
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).not.toThrow();
|
||||
@@ -289,6 +294,7 @@ test('should not throw error for relation diff with null related_collection (app
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const snapshot = { hash: 'abc' } as SnapshotWithHash;
|
||||
|
||||
expect(() => validateApplyDiff(diff, snapshot)).not.toThrow();
|
||||
@@ -299,6 +305,7 @@ test('should detect empty diff', () => {
|
||||
hash: 'abc',
|
||||
diff: { collections: [], fields: [], relations: [] },
|
||||
};
|
||||
|
||||
const snapshot = {} as SnapshotWithHash;
|
||||
|
||||
expect(validateApplyDiff(diff, snapshot)).toBe(false);
|
||||
@@ -309,6 +316,7 @@ test('should pass on valid diff', () => {
|
||||
hash: 'abc',
|
||||
diff: { collections: [{ collection: 'test', diff: [] }], fields: [], relations: [] },
|
||||
};
|
||||
|
||||
const snapshot = { hash: 'abc' } as SnapshotWithHash;
|
||||
|
||||
expect(validateApplyDiff(diff, snapshot)).toBe(true);
|
||||
|
||||
@@ -8,6 +8,7 @@ vi.mock('../env', () => ({
|
||||
PRESENT_TEST_VARIABLE: true,
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('../logger', () => ({
|
||||
default: {
|
||||
error: vi.fn(),
|
||||
|
||||
@@ -92,6 +92,7 @@ describe('validate keys', () => {
|
||||
expect(() =>
|
||||
validateKeys(schema, 'pk_uuid', 'id', [uuid(), 'fakeuuid-62d9-434d-a7c7-878c8376782e', uuid()])
|
||||
).toThrowError();
|
||||
|
||||
expect(() => validateKeys(schema, 'pk_uuid', 'id', [uuid(), 'invalid', uuid()])).toThrowError();
|
||||
expect(() => validateKeys(schema, 'pk_uuid', 'id', [uuid(), NaN, uuid()])).toThrowError();
|
||||
expect(() => validateKeys(schema, 'pk_uuid', 'id', [uuid(), 111, uuid()])).toThrowError();
|
||||
|
||||
@@ -3,10 +3,12 @@ import { validateQuery } from './validate-query.js';
|
||||
|
||||
vi.mock('../env', async () => {
|
||||
const actual = (await vi.importActual('../env')) as { default: Record<string, any> };
|
||||
|
||||
const MOCK_ENV = {
|
||||
...actual.default,
|
||||
MAX_QUERY_LIMIT: 100,
|
||||
};
|
||||
|
||||
return {
|
||||
default: MOCK_ENV,
|
||||
getEnv: () => MOCK_ENV,
|
||||
|
||||
@@ -134,6 +134,7 @@ function validateList(value: any, key: string) {
|
||||
|
||||
function validateBoolean(value: any, key: string) {
|
||||
if (value === null) return true;
|
||||
|
||||
if (typeof value !== 'boolean') {
|
||||
throw new InvalidQueryException(`"${key}" has to be a boolean`);
|
||||
}
|
||||
@@ -143,6 +144,7 @@ function validateBoolean(value: any, key: string) {
|
||||
|
||||
function validateGeometry(value: any, key: string) {
|
||||
if (value === null) return true;
|
||||
|
||||
try {
|
||||
stringify(value);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user