@smartive/graphql-magic 16.3.6 → 16.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/testing.yml +1 -1
- package/CHANGELOG.md +2 -2
- package/dist/bin/gqm.cjs +37 -35
- package/dist/cjs/index.cjs +51 -48
- package/dist/esm/api/execute.d.ts +1 -5
- package/dist/esm/api/execute.js.map +1 -1
- package/dist/esm/client/queries.d.ts +4 -4
- package/dist/esm/client/queries.js.map +1 -1
- package/dist/esm/db/generate.js +2 -0
- package/dist/esm/db/generate.js.map +1 -1
- package/dist/esm/migrations/generate.js +1 -1
- package/dist/esm/migrations/generate.js.map +1 -1
- package/dist/esm/models/models.d.ts +1 -1
- package/dist/esm/models/models.js.map +1 -1
- package/dist/esm/models/utils.d.ts +8 -12
- package/dist/esm/models/utils.js +3 -5
- package/dist/esm/models/utils.js.map +1 -1
- package/dist/esm/permissions/check.js +0 -15
- package/dist/esm/permissions/check.js.map +1 -1
- package/dist/esm/permissions/generate.d.ts +5 -19
- package/dist/esm/permissions/generate.js.map +1 -1
- package/dist/esm/resolvers/arguments.js.map +1 -1
- package/dist/esm/resolvers/filters.js +0 -2
- package/dist/esm/resolvers/filters.js.map +1 -1
- package/dist/esm/resolvers/mutations.js +5 -4
- package/dist/esm/resolvers/mutations.js.map +1 -1
- package/dist/esm/resolvers/node.js.map +1 -1
- package/dist/esm/resolvers/resolver.d.ts +2 -2
- package/dist/esm/resolvers/resolver.js +1 -1
- package/dist/esm/resolvers/resolver.js.map +1 -1
- package/dist/esm/resolvers/selects.js.map +1 -1
- package/dist/esm/resolvers/utils.d.ts +2 -6
- package/dist/esm/resolvers/utils.js +4 -2
- package/dist/esm/resolvers/utils.js.map +1 -1
- package/dist/esm/schema/utils.d.ts +4 -4
- package/dist/esm/schema/utils.js +31 -30
- package/dist/esm/schema/utils.js.map +1 -1
- package/dist/esm/utils/dates.d.ts +2 -4
- package/dist/esm/utils/dates.js +1 -3
- package/dist/esm/utils/dates.js.map +1 -1
- package/docs/docs/1-tutorial.md +1 -1
- package/docs/package-lock.json +1675 -893
- package/docs/package.json +4 -4
- package/eslint.config.mjs +42 -0
- package/migrations/20230912185644_setup.ts +3 -3
- package/package.json +8 -13
- package/src/api/execute.ts +1 -0
- package/src/bin/gqm/codegen.ts +1 -1
- package/src/client/gql.ts +1 -1
- package/src/client/mutations.ts +8 -8
- package/src/client/queries.ts +15 -14
- package/src/db/generate.ts +8 -5
- package/src/migrations/generate.ts +26 -22
- package/src/models/models.ts +24 -9
- package/src/models/mutation-hook.ts +1 -1
- package/src/models/utils.ts +8 -7
- package/src/permissions/check.ts +22 -30
- package/src/permissions/generate.ts +8 -25
- package/src/resolvers/arguments.ts +7 -2
- package/src/resolvers/filters.ts +8 -10
- package/src/resolvers/mutations.ts +19 -16
- package/src/resolvers/node.ts +3 -2
- package/src/resolvers/resolver.ts +11 -10
- package/src/resolvers/selects.ts +4 -3
- package/src/resolvers/utils.ts +15 -10
- package/src/schema/generate.ts +11 -11
- package/src/schema/utils.ts +84 -82
- package/src/utils/dates.ts +3 -3
- package/tests/generated/api/index.ts +2 -2
- package/tests/generated/client/index.ts +1 -193
- package/tests/generated/db/index.ts +4 -4
- package/tests/generated/models.json +2 -1
- package/tests/generated/schema.graphql +1 -1
- package/tests/utils/graphql-client.ts +49 -0
- package/tests/utils/models.ts +1 -0
- package/tests/utils/server.ts +4 -5
- package/tsconfig.eslint.json +18 -3
- package/tsconfig.json +11 -2
- package/.eslintrc +0 -13
package/src/models/models.ts
CHANGED
|
@@ -71,7 +71,7 @@ export class Models {
|
|
|
71
71
|
kind: 'raw-enum',
|
|
72
72
|
name: 'Order',
|
|
73
73
|
values: ['ASC', 'DESC'],
|
|
74
|
-
}
|
|
74
|
+
},
|
|
75
75
|
);
|
|
76
76
|
const entities = this.definitions.filter(isEntityModelDefinition);
|
|
77
77
|
|
|
@@ -204,7 +204,7 @@ export class Models {
|
|
|
204
204
|
}
|
|
205
205
|
|
|
206
206
|
this.models = this.definitions.map(
|
|
207
|
-
(definition) => new (MODEL_KIND_TO_CLASS_MAPPING[definition.kind] as any)(this, definition)
|
|
207
|
+
(definition) => new (MODEL_KIND_TO_CLASS_MAPPING[definition.kind] as any)(this, definition),
|
|
208
208
|
);
|
|
209
209
|
for (const model of this.models) {
|
|
210
210
|
this.modelsByName[model.name] = model;
|
|
@@ -228,6 +228,7 @@ export class Models {
|
|
|
228
228
|
if (!(model instanceof expectedType)) {
|
|
229
229
|
throw new Error(`Model ${name} is not of kind ${kind}.`);
|
|
230
230
|
}
|
|
231
|
+
|
|
231
232
|
return model as ModelKindToClassMapping[K];
|
|
232
233
|
}
|
|
233
234
|
}
|
|
@@ -237,7 +238,10 @@ export abstract class Model {
|
|
|
237
238
|
plural: string;
|
|
238
239
|
description: string;
|
|
239
240
|
|
|
240
|
-
constructor(
|
|
241
|
+
constructor(
|
|
242
|
+
public models: Models,
|
|
243
|
+
definition: ModelDefinition,
|
|
244
|
+
) {
|
|
241
245
|
Object.assign(this, definition);
|
|
242
246
|
this.plural = definition.plural || pluralize(definition.name);
|
|
243
247
|
}
|
|
@@ -358,6 +362,7 @@ export class EntityModel extends Model {
|
|
|
358
362
|
.filter(isRelation)
|
|
359
363
|
.map((relationField) => new NormalRelation(this, relationField, this.models.getModel(relationField.type, 'entity')));
|
|
360
364
|
}
|
|
365
|
+
|
|
361
366
|
return this._relations;
|
|
362
367
|
}
|
|
363
368
|
|
|
@@ -368,6 +373,7 @@ export class EntityModel extends Model {
|
|
|
368
373
|
this._relationsByName[relation.name] = relation;
|
|
369
374
|
}
|
|
370
375
|
}
|
|
376
|
+
|
|
371
377
|
return this._relationsByName;
|
|
372
378
|
}
|
|
373
379
|
|
|
@@ -380,9 +386,10 @@ export class EntityModel extends Model {
|
|
|
380
386
|
this._reverseRelations = this.models.entities.flatMap((model) =>
|
|
381
387
|
model.relations
|
|
382
388
|
.filter((relation) => relation.targetModel.name === this.name || relation.targetModel.name === this.rootModel.name)
|
|
383
|
-
.map((relation) => relation.reverse)
|
|
389
|
+
.map((relation) => relation.reverse),
|
|
384
390
|
);
|
|
385
391
|
}
|
|
392
|
+
|
|
386
393
|
return this._reverseRelations;
|
|
387
394
|
}
|
|
388
395
|
|
|
@@ -393,6 +400,7 @@ export class EntityModel extends Model {
|
|
|
393
400
|
this._reverseRelationsByName[reverseRelation.name] = reverseRelation;
|
|
394
401
|
}
|
|
395
402
|
}
|
|
403
|
+
|
|
396
404
|
return this._reverseRelationsByName;
|
|
397
405
|
}
|
|
398
406
|
|
|
@@ -405,7 +413,7 @@ export class EntityModel extends Model {
|
|
|
405
413
|
this._manyToManyRelations = [];
|
|
406
414
|
for (const relationFromSource of this.reverseRelations) {
|
|
407
415
|
const relationToTarget = relationFromSource.targetModel.relations.find(
|
|
408
|
-
(relation) => !relation.field.generated && relation.field.name !== relationFromSource.field.name
|
|
416
|
+
(relation) => !relation.field.generated && relation.field.name !== relationFromSource.field.name,
|
|
409
417
|
);
|
|
410
418
|
if (!relationToTarget) {
|
|
411
419
|
continue;
|
|
@@ -413,7 +421,7 @@ export class EntityModel extends Model {
|
|
|
413
421
|
|
|
414
422
|
const inapplicableFields = relationFromSource.targetModel.fields.filter(
|
|
415
423
|
(otherField) =>
|
|
416
|
-
!otherField.generated && ![relationFromSource.field.name, relationToTarget.field.name].includes(otherField.name)
|
|
424
|
+
!otherField.generated && ![relationFromSource.field.name, relationToTarget.field.name].includes(otherField.name),
|
|
417
425
|
);
|
|
418
426
|
if (inapplicableFields.length) {
|
|
419
427
|
continue;
|
|
@@ -422,6 +430,7 @@ export class EntityModel extends Model {
|
|
|
422
430
|
this._manyToManyRelations.push(new ManyToManyRelation(relationFromSource, relationToTarget));
|
|
423
431
|
}
|
|
424
432
|
}
|
|
433
|
+
|
|
425
434
|
return this._manyToManyRelations;
|
|
426
435
|
}
|
|
427
436
|
|
|
@@ -432,6 +441,7 @@ export class EntityModel extends Model {
|
|
|
432
441
|
this._manyToManyRelationsByName[manyToManyRelation.name] = manyToManyRelation;
|
|
433
442
|
}
|
|
434
443
|
}
|
|
444
|
+
|
|
435
445
|
return this._manyToManyRelationsByName;
|
|
436
446
|
}
|
|
437
447
|
|
|
@@ -444,6 +454,7 @@ export class EntityModel extends Model {
|
|
|
444
454
|
if (!this._parentModel) {
|
|
445
455
|
this._parentModel = this.models.getModel(this.parent, 'entity');
|
|
446
456
|
}
|
|
457
|
+
|
|
447
458
|
return this._parentModel;
|
|
448
459
|
}
|
|
449
460
|
}
|
|
@@ -472,14 +483,18 @@ export abstract class Relation {
|
|
|
472
483
|
public name: string,
|
|
473
484
|
public sourceModel: EntityModel,
|
|
474
485
|
public field: RelationField,
|
|
475
|
-
public targetModel: EntityModel
|
|
486
|
+
public targetModel: EntityModel,
|
|
476
487
|
) {}
|
|
477
488
|
}
|
|
478
489
|
|
|
479
490
|
export class NormalRelation extends Relation {
|
|
480
491
|
public reverse: ReverseRelation;
|
|
481
492
|
|
|
482
|
-
constructor(
|
|
493
|
+
constructor(
|
|
494
|
+
sourceModel: EntityModel,
|
|
495
|
+
public field: RelationField,
|
|
496
|
+
targetModel: EntityModel,
|
|
497
|
+
) {
|
|
483
498
|
super(field.name, sourceModel, field, targetModel);
|
|
484
499
|
this.reverse = new ReverseRelation(this);
|
|
485
500
|
}
|
|
@@ -492,7 +507,7 @@ export class ReverseRelation extends Relation {
|
|
|
492
507
|
(reverse.field.toOne ? typeToField(reverse.sourceModel.name) : reverse.sourceModel.pluralField),
|
|
493
508
|
reverse.targetModel,
|
|
494
509
|
reverse.field,
|
|
495
|
-
reverse.sourceModel
|
|
510
|
+
reverse.sourceModel,
|
|
496
511
|
);
|
|
497
512
|
}
|
|
498
513
|
}
|
|
@@ -10,5 +10,5 @@ export type MutationHook<DateType extends AnyDateType = AnyDateType> = (
|
|
|
10
10
|
action: Action,
|
|
11
11
|
when: 'before' | 'after',
|
|
12
12
|
data: { prev: Entity; input: Entity; normalizedInput: Entity; next: Entity },
|
|
13
|
-
ctx: Context<DateType
|
|
13
|
+
ctx: Context<DateType>,
|
|
14
14
|
) => Promise<void>;
|
package/src/models/utils.ts
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
|
|
20
20
|
const isNotFalsy = <T>(v: T | null | undefined | false): v is T => typeof v !== 'undefined' && v !== null && v !== false;
|
|
21
21
|
|
|
22
|
-
export const merge = <T>(objects: (
|
|
22
|
+
export const merge = <T>(objects: (Record<string, T> | undefined | false)[] | undefined): Record<string, T> =>
|
|
23
23
|
(objects || []).filter(isNotFalsy).reduce((i, acc) => ({ ...acc, ...i }), {});
|
|
24
24
|
|
|
25
25
|
// Target -> target
|
|
@@ -42,7 +42,7 @@ export const not =
|
|
|
42
42
|
(field: T) =>
|
|
43
43
|
!predicate(field);
|
|
44
44
|
|
|
45
|
-
export const isRootModel = (model: EntityModel) => model.root;
|
|
45
|
+
export const isRootModel = (model: EntityModel) => !!model.root;
|
|
46
46
|
|
|
47
47
|
export const isEntityModel = (model: Model): model is EntityModel => model instanceof EntityModel;
|
|
48
48
|
|
|
@@ -77,7 +77,7 @@ export const isEnum = (field: EntityField): field is EnumField => field.kind ===
|
|
|
77
77
|
|
|
78
78
|
export const isRelation = (field: EntityField): field is RelationField => field.kind === 'relation';
|
|
79
79
|
|
|
80
|
-
export const isInherited = (field: EntityField) => field.inherited;
|
|
80
|
+
export const isInherited = (field: EntityField) => !!field.inherited;
|
|
81
81
|
|
|
82
82
|
export const isInTable = (field: EntityField) => field.name === 'id' || !field.inherited;
|
|
83
83
|
|
|
@@ -114,7 +114,7 @@ export const getActionableRelations = (model: EntityModel, action: 'create' | 'u
|
|
|
114
114
|
(relation) =>
|
|
115
115
|
relation.field[
|
|
116
116
|
`${action === 'filter' ? action : action.slice(0, -1)}able` as 'filterable' | 'creatable' | 'updatable'
|
|
117
|
-
]
|
|
117
|
+
],
|
|
118
118
|
)
|
|
119
119
|
.map(({ name }) => name);
|
|
120
120
|
|
|
@@ -133,6 +133,7 @@ export const summon = <T>(array: readonly T[] | undefined, cb: Parameters<T[]['f
|
|
|
133
133
|
console.trace();
|
|
134
134
|
throw new Error(errorMessage || 'Element not found.');
|
|
135
135
|
}
|
|
136
|
+
|
|
136
137
|
return result;
|
|
137
138
|
};
|
|
138
139
|
|
|
@@ -154,11 +155,13 @@ export const get = <T, U extends keyof ForSure<T>>(object: T | null | undefined,
|
|
|
154
155
|
console.warn(error);
|
|
155
156
|
throw error;
|
|
156
157
|
}
|
|
158
|
+
|
|
157
159
|
return value as ForSure<ForSure<T>[U]>;
|
|
158
160
|
};
|
|
159
161
|
|
|
160
162
|
export const getString = (v: unknown) => {
|
|
161
163
|
assert(typeof v === 'string');
|
|
164
|
+
|
|
162
165
|
return v;
|
|
163
166
|
};
|
|
164
167
|
|
|
@@ -168,9 +171,8 @@ export const retry = async <T>(cb: () => Promise<T>, condition: (e: any) => bool
|
|
|
168
171
|
} catch (e) {
|
|
169
172
|
if (condition(e)) {
|
|
170
173
|
return await cb();
|
|
171
|
-
} else {
|
|
172
|
-
throw e;
|
|
173
174
|
}
|
|
175
|
+
throw e;
|
|
174
176
|
}
|
|
175
177
|
};
|
|
176
178
|
|
|
@@ -182,7 +184,6 @@ type Typeof = {
|
|
|
182
184
|
symbol: symbol;
|
|
183
185
|
undefined: undefined;
|
|
184
186
|
object: object;
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/ban-types
|
|
186
187
|
function: Function;
|
|
187
188
|
};
|
|
188
189
|
|
package/src/permissions/check.ts
CHANGED
|
@@ -11,7 +11,7 @@ export const getRole = (ctx: Pick<FullContext, 'user'>) => ctx.user?.role ?? 'UN
|
|
|
11
11
|
export const getPermissionStack = (
|
|
12
12
|
ctx: Pick<FullContext, 'permissions' | 'user'>,
|
|
13
13
|
type: string,
|
|
14
|
-
action: PermissionAction
|
|
14
|
+
action: PermissionAction,
|
|
15
15
|
): boolean | PermissionStack => {
|
|
16
16
|
const rolePermissions = ctx.permissions[getRole(ctx)];
|
|
17
17
|
if (typeof rolePermissions === 'boolean' || rolePermissions === undefined) {
|
|
@@ -37,7 +37,7 @@ export const applyPermissions = (
|
|
|
37
37
|
tableAlias: string,
|
|
38
38
|
query: Knex.QueryBuilder,
|
|
39
39
|
action: PermissionAction,
|
|
40
|
-
verifiedPermissionStack?: PermissionStack
|
|
40
|
+
verifiedPermissionStack?: PermissionStack,
|
|
41
41
|
): boolean | PermissionStack => {
|
|
42
42
|
const permissionStack = getPermissionStack(ctx, type, action);
|
|
43
43
|
|
|
@@ -47,8 +47,9 @@ export const applyPermissions = (
|
|
|
47
47
|
|
|
48
48
|
if (permissionStack === false) {
|
|
49
49
|
console.error(`No applicable permissions exist for ${getRole(ctx)} ${type} ${action}.`);
|
|
50
|
-
|
|
50
|
+
|
|
51
51
|
query.where(false);
|
|
52
|
+
|
|
52
53
|
return permissionStack;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -59,8 +60,8 @@ export const applyPermissions = (
|
|
|
59
60
|
hash(prefixChain) === hash(chain.slice(0, -1)) &&
|
|
60
61
|
// TODO: this is stricter than it could be if we add these checks to the query
|
|
61
62
|
!('where' in get(chain, chain.length - 1)) &&
|
|
62
|
-
!('me' in get(chain, chain.length - 1))
|
|
63
|
-
)
|
|
63
|
+
!('me' in get(chain, chain.length - 1)),
|
|
64
|
+
),
|
|
64
65
|
)
|
|
65
66
|
) {
|
|
66
67
|
// The user has access to a parent entity with one or more from a set of rules, all of which are inherited by this entity
|
|
@@ -68,15 +69,14 @@ export const applyPermissions = (
|
|
|
68
69
|
return permissionStack;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
72
72
|
ors(
|
|
73
73
|
query,
|
|
74
74
|
permissionStack.map(
|
|
75
75
|
(links) => (query) =>
|
|
76
76
|
query
|
|
77
77
|
.whereNull(`${tableAlias}.id`)
|
|
78
|
-
.orWhereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, ctx.knex.raw(`"${tableAlias}".id`)))
|
|
79
|
-
)
|
|
78
|
+
.orWhereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, ctx.knex.raw(`"${tableAlias}".id`))),
|
|
79
|
+
),
|
|
80
80
|
);
|
|
81
81
|
|
|
82
82
|
return permissionStack;
|
|
@@ -89,7 +89,7 @@ export const getEntityToMutate = async (
|
|
|
89
89
|
ctx: Pick<FullContext, 'models' | 'permissions' | 'user' | 'knex'>,
|
|
90
90
|
model: EntityModel,
|
|
91
91
|
where: Record<string, unknown>,
|
|
92
|
-
action: 'UPDATE' | 'DELETE' | 'RESTORE'
|
|
92
|
+
action: 'UPDATE' | 'DELETE' | 'RESTORE',
|
|
93
93
|
) => {
|
|
94
94
|
const query = ctx
|
|
95
95
|
.knex(model.parent || model.name)
|
|
@@ -101,7 +101,7 @@ export const getEntityToMutate = async (
|
|
|
101
101
|
console.error(
|
|
102
102
|
`Not found: ${Object.entries(where)
|
|
103
103
|
.map(([key, value]) => `${key}: ${value}`)
|
|
104
|
-
.join(', ')}
|
|
104
|
+
.join(', ')}`,
|
|
105
105
|
);
|
|
106
106
|
throw new NotFoundError(`Entity to ${action.toLowerCase()}`);
|
|
107
107
|
}
|
|
@@ -112,7 +112,7 @@ export const getEntityToMutate = async (
|
|
|
112
112
|
console.error(
|
|
113
113
|
`Permission error: ${Object.entries(where)
|
|
114
114
|
.map(([key, value]) => `${key}: ${value}`)
|
|
115
|
-
.join(', ')}
|
|
115
|
+
.join(', ')}`,
|
|
116
116
|
);
|
|
117
117
|
throw new PermissionError(getRole(ctx), action, `this ${model.name}`, 'no available permissions applied');
|
|
118
118
|
}
|
|
@@ -132,7 +132,7 @@ export const checkCanWrite = async (
|
|
|
132
132
|
ctx: Pick<FullContext, 'models' | 'permissions' | 'user' | 'knex'>,
|
|
133
133
|
model: EntityModel,
|
|
134
134
|
data: Record<string, unknown>,
|
|
135
|
-
action: 'CREATE' | 'UPDATE'
|
|
135
|
+
action: 'CREATE' | 'UPDATE',
|
|
136
136
|
) => {
|
|
137
137
|
const permissionStack = getPermissionStack(ctx, model.name, action);
|
|
138
138
|
|
|
@@ -143,7 +143,6 @@ export const checkCanWrite = async (
|
|
|
143
143
|
throw new PermissionError(getRole(ctx), action, model.plural, 'no applicable permissions');
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- using `select(1 as any)` to instantiate an "empty" query builder
|
|
147
146
|
const query = ctx.knex.select(1 as any).first();
|
|
148
147
|
let linked = false;
|
|
149
148
|
|
|
@@ -168,7 +167,7 @@ export const checkCanWrite = async (
|
|
|
168
167
|
|
|
169
168
|
if (fieldPermissionStack === true) {
|
|
170
169
|
// User can link any entity from this type, just check whether it exists
|
|
171
|
-
|
|
170
|
+
|
|
172
171
|
query.whereExists((subQuery) => subQuery.from(`${field.type} as a`).whereRaw(`a.id = ?`, foreignId));
|
|
173
172
|
continue;
|
|
174
173
|
}
|
|
@@ -178,16 +177,15 @@ export const checkCanWrite = async (
|
|
|
178
177
|
role,
|
|
179
178
|
action,
|
|
180
179
|
`this ${model.name}'s ${field.name}`,
|
|
181
|
-
'no applicable permissions on data to link'
|
|
180
|
+
'no applicable permissions on data to link',
|
|
182
181
|
);
|
|
183
182
|
}
|
|
184
183
|
|
|
185
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
186
184
|
ors(
|
|
187
185
|
query,
|
|
188
186
|
fieldPermissionStack.map(
|
|
189
|
-
(links) => (query) => query.whereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, foreignId))
|
|
190
|
-
)
|
|
187
|
+
(links) => (query) => query.whereExists((subQuery) => permissionLinkQuery(ctx, subQuery, links, foreignId)),
|
|
188
|
+
),
|
|
191
189
|
);
|
|
192
190
|
}
|
|
193
191
|
|
|
@@ -206,7 +204,7 @@ const permissionLinkQuery = (
|
|
|
206
204
|
ctx: Pick<FullContext, 'models' | 'user'>,
|
|
207
205
|
subQuery: Knex.QueryBuilder,
|
|
208
206
|
links: PermissionLink[],
|
|
209
|
-
id: Knex.RawBinding | Knex.ValueDict
|
|
207
|
+
id: Knex.RawBinding | Knex.ValueDict,
|
|
210
208
|
) => {
|
|
211
209
|
const aliases = new AliasGenerator();
|
|
212
210
|
let alias = aliases.getShort();
|
|
@@ -214,16 +212,14 @@ const permissionLinkQuery = (
|
|
|
214
212
|
|
|
215
213
|
if (me) {
|
|
216
214
|
if (!ctx.user) {
|
|
217
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
218
215
|
subQuery.where(false);
|
|
216
|
+
|
|
219
217
|
return;
|
|
220
218
|
}
|
|
221
219
|
|
|
222
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
223
220
|
subQuery.where({ [`${alias}.id`]: ctx.user.id });
|
|
224
221
|
}
|
|
225
222
|
|
|
226
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
227
223
|
subQuery.from(`${type} as ${alias}`);
|
|
228
224
|
|
|
229
225
|
if (where) {
|
|
@@ -234,20 +230,18 @@ const permissionLinkQuery = (
|
|
|
234
230
|
const model = ctx.models.getModel(type, 'entity');
|
|
235
231
|
const subAlias = aliases.getShort();
|
|
236
232
|
if (reverse) {
|
|
237
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
238
233
|
subQuery.leftJoin(`${type} as ${subAlias}`, `${alias}.${foreignKey || 'id'}`, `${subAlias}.id`);
|
|
239
234
|
} else {
|
|
240
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
241
235
|
subQuery.rightJoin(`${type} as ${subAlias}`, `${alias}.id`, `${subAlias}.${foreignKey || 'id'}`);
|
|
242
236
|
}
|
|
243
|
-
|
|
237
|
+
|
|
244
238
|
subQuery.where({ [`${subAlias}.deleted`]: false });
|
|
245
239
|
if (where) {
|
|
246
240
|
applyWhere(model, subQuery, subAlias, where, aliases);
|
|
247
241
|
}
|
|
248
242
|
alias = subAlias;
|
|
249
243
|
}
|
|
250
|
-
|
|
244
|
+
|
|
251
245
|
subQuery.whereRaw(`"${alias}".id = ?`, id);
|
|
252
246
|
};
|
|
253
247
|
|
|
@@ -257,18 +251,16 @@ const applyWhere = (model: EntityModel, query: Knex.QueryBuilder, alias: string,
|
|
|
257
251
|
|
|
258
252
|
if (relation) {
|
|
259
253
|
const subAlias = aliases.getShort();
|
|
260
|
-
|
|
254
|
+
|
|
261
255
|
query.leftJoin(
|
|
262
256
|
`${relation.targetModel.name} as ${subAlias}`,
|
|
263
257
|
`${alias}.${relation.field.foreignKey || `${relation.field.name}Id`}`,
|
|
264
|
-
`${subAlias}.id
|
|
258
|
+
`${subAlias}.id`,
|
|
265
259
|
);
|
|
266
260
|
applyWhere(relation.targetModel, query, subAlias, value, aliases);
|
|
267
261
|
} else if (Array.isArray(value)) {
|
|
268
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
269
262
|
query.whereIn(`${alias}.${key}`, value);
|
|
270
263
|
} else {
|
|
271
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
272
264
|
query.where({ [`${alias}.${key}`]: value });
|
|
273
265
|
}
|
|
274
266
|
}
|
|
@@ -8,36 +8,19 @@ const ACTIONS: PermissionAction[] = ['READ', 'CREATE', 'UPDATE', 'DELETE', 'REST
|
|
|
8
8
|
/**
|
|
9
9
|
* Initial representation (tree structure, as defined by user).
|
|
10
10
|
*/
|
|
11
|
-
export type PermissionsConfig =
|
|
12
|
-
[role: string]:
|
|
13
|
-
| true
|
|
14
|
-
| {
|
|
15
|
-
[type: string]: PermissionsBlock;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
11
|
+
export type PermissionsConfig = Record<string, true | Record<string, PermissionsBlock>>;
|
|
18
12
|
|
|
19
|
-
export type PermissionsBlock = {
|
|
20
|
-
[action in PermissionAction]?: true;
|
|
21
|
-
} & {
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export type PermissionsBlock = Partial<Record<PermissionAction, true>> & {
|
|
23
14
|
WHERE?: Record<string, any>;
|
|
24
|
-
RELATIONS?:
|
|
25
|
-
[relation: string]: PermissionsBlock;
|
|
26
|
-
};
|
|
15
|
+
RELATIONS?: Record<string, PermissionsBlock>;
|
|
27
16
|
};
|
|
28
17
|
|
|
29
18
|
/**
|
|
30
19
|
* Final representation (lookup table (role, model, action) -> permission stack).
|
|
31
20
|
*/
|
|
32
|
-
export type Permissions =
|
|
33
|
-
[role: string]: true | RolePermissions;
|
|
34
|
-
};
|
|
21
|
+
export type Permissions = Record<string, true | RolePermissions>;
|
|
35
22
|
|
|
36
|
-
type RolePermissions =
|
|
37
|
-
[type: string]: {
|
|
38
|
-
[action in PermissionAction]?: true | PermissionStack;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
23
|
+
type RolePermissions = Record<string, Partial<Record<PermissionAction, true | PermissionStack>>>;
|
|
41
24
|
|
|
42
25
|
/**
|
|
43
26
|
* For a given role, model and action,
|
|
@@ -53,7 +36,7 @@ export type PermissionLink = {
|
|
|
53
36
|
foreignKey?: string;
|
|
54
37
|
reverse?: boolean;
|
|
55
38
|
me?: boolean;
|
|
56
|
-
|
|
39
|
+
|
|
57
40
|
where?: any;
|
|
58
41
|
};
|
|
59
42
|
|
|
@@ -85,7 +68,7 @@ export const generatePermissions = (models: Models, config: PermissionsConfig) =
|
|
|
85
68
|
...('WHERE' in block && { where: block.WHERE }),
|
|
86
69
|
},
|
|
87
70
|
],
|
|
88
|
-
block
|
|
71
|
+
block,
|
|
89
72
|
);
|
|
90
73
|
}
|
|
91
74
|
permissions[role] = rolePermissions;
|
|
@@ -107,7 +90,7 @@ const addPermissions = (models: Models, permissions: RolePermissions, links: Per
|
|
|
107
90
|
permissions[type][action] = [];
|
|
108
91
|
}
|
|
109
92
|
if (permissions[type][action] !== true) {
|
|
110
|
-
|
|
93
|
+
permissions[type][action].push(links);
|
|
111
94
|
}
|
|
112
95
|
}
|
|
113
96
|
}
|
|
@@ -35,6 +35,7 @@ function getRawValue(value: ValueNode, values?: VariableValues): Value {
|
|
|
35
35
|
if (!values) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
|
+
|
|
38
39
|
return value.values.map((value) => getRawValue(value, values));
|
|
39
40
|
case Kind.VARIABLE:
|
|
40
41
|
return values?.[value.name.value];
|
|
@@ -56,6 +57,7 @@ function getRawValue(value: ValueNode, values?: VariableValues): Value {
|
|
|
56
57
|
for (const field of value.fields) {
|
|
57
58
|
res[field.name.value] = getRawValue(field.value, values);
|
|
58
59
|
}
|
|
60
|
+
|
|
59
61
|
return res;
|
|
60
62
|
}
|
|
61
63
|
}
|
|
@@ -69,7 +71,7 @@ export const normalizeArguments = (node: FieldResolverNode) => {
|
|
|
69
71
|
const normalizedValue = normalizeValue(
|
|
70
72
|
rawValue,
|
|
71
73
|
summonByKey(node.fieldDefinition.arguments || [], 'name.value', argument.name.value).type,
|
|
72
|
-
node.ctx.info.schema
|
|
74
|
+
node.ctx.info.schema,
|
|
73
75
|
);
|
|
74
76
|
if (normalizedValue === undefined) {
|
|
75
77
|
continue;
|
|
@@ -77,6 +79,7 @@ export const normalizeArguments = (node: FieldResolverNode) => {
|
|
|
77
79
|
normalizedArguments[argument.name.value] = normalizedValue as any;
|
|
78
80
|
}
|
|
79
81
|
}
|
|
82
|
+
|
|
80
83
|
return normalizedArguments;
|
|
81
84
|
};
|
|
82
85
|
|
|
@@ -88,6 +91,7 @@ export function normalizeValue(value: Value, type: TypeNode, schema: GraphQLSche
|
|
|
88
91
|
for (const v of value) {
|
|
89
92
|
res.push(normalizeValue(v, type.type, schema));
|
|
90
93
|
}
|
|
94
|
+
|
|
91
95
|
return res;
|
|
92
96
|
}
|
|
93
97
|
|
|
@@ -104,7 +108,7 @@ export function normalizeValue(value: Value, type: TypeNode, schema: GraphQLSche
|
|
|
104
108
|
return normalizeValueByTypeDefinition(
|
|
105
109
|
value,
|
|
106
110
|
(schema.getType(type.name.value) as Maybe<GraphQLObjectType>)?.astNode,
|
|
107
|
-
schema
|
|
111
|
+
schema,
|
|
108
112
|
);
|
|
109
113
|
}
|
|
110
114
|
}
|
|
@@ -125,5 +129,6 @@ export const normalizeValueByTypeDefinition = (value: Value, type: Maybe<TypeDef
|
|
|
125
129
|
}
|
|
126
130
|
res[key] = normalizedValue;
|
|
127
131
|
}
|
|
132
|
+
|
|
128
133
|
return res;
|
|
129
134
|
};
|
package/src/resolvers/filters.ts
CHANGED
|
@@ -33,12 +33,10 @@ export const applyFilters = (node: FieldResolverNode, query: Knex.QueryBuilder,
|
|
|
33
33
|
const { limit, offset, orderBy, where, search } = normalizedArguments;
|
|
34
34
|
|
|
35
35
|
if (limit) {
|
|
36
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
37
36
|
query.limit(limit);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
if (offset) {
|
|
41
|
-
// eslint-disable-next-line @typescript-eslint/no-floating-promises -- we do not need to await knex here
|
|
42
40
|
query.offset(offset);
|
|
43
41
|
}
|
|
44
42
|
|
|
@@ -108,8 +106,8 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
108
106
|
ops.push((query) =>
|
|
109
107
|
ors(
|
|
110
108
|
query,
|
|
111
|
-
allSubOps.map((subOps) => (subQuery) => apply(subQuery, subOps))
|
|
112
|
-
)
|
|
109
|
+
allSubOps.map((subOps) => (subQuery) => apply(subQuery, subOps)),
|
|
110
|
+
),
|
|
113
111
|
);
|
|
114
112
|
continue;
|
|
115
113
|
}
|
|
@@ -145,7 +143,7 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
145
143
|
]);
|
|
146
144
|
void apply(subQuery, subOps);
|
|
147
145
|
applyJoins(aliases, subQuery, subJoins);
|
|
148
|
-
})
|
|
146
|
+
}),
|
|
149
147
|
);
|
|
150
148
|
continue;
|
|
151
149
|
}
|
|
@@ -183,8 +181,8 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
183
181
|
ops.push((query) =>
|
|
184
182
|
ors(
|
|
185
183
|
query,
|
|
186
|
-
value.map((v) => (subQuery) => subQuery.whereRaw('? = ANY(??)', [v, column] as string[]))
|
|
187
|
-
)
|
|
184
|
+
value.map((v) => (subQuery) => subQuery.whereRaw('? = ANY(??)', [v, column] as string[])),
|
|
185
|
+
),
|
|
188
186
|
);
|
|
189
187
|
continue;
|
|
190
188
|
}
|
|
@@ -195,7 +193,7 @@ const applyWhere = (node: WhereNode, where: Where | undefined, ops: QueryBuilder
|
|
|
195
193
|
ors(query, [
|
|
196
194
|
(subQuery) => subQuery.whereIn(column, value.filter((v) => v !== null) as string[]),
|
|
197
195
|
(subQuery) => subQuery.whereNull(column),
|
|
198
|
-
])
|
|
196
|
+
]),
|
|
199
197
|
);
|
|
200
198
|
continue;
|
|
201
199
|
}
|
|
@@ -220,8 +218,8 @@ const applySearch = (node: FieldResolverNode, search: string, query: Knex.QueryB
|
|
|
220
218
|
.map(
|
|
221
219
|
({ name }) =>
|
|
222
220
|
(query) =>
|
|
223
|
-
query.whereILike(getColumn(node, name), `%${search}%`)
|
|
224
|
-
)
|
|
221
|
+
query.whereILike(getColumn(node, name), `%${search}%`),
|
|
222
|
+
),
|
|
225
223
|
);
|
|
226
224
|
|
|
227
225
|
const applyOrderBy = (node: FieldResolverNode, orderBy: OrderBy, query: Knex.QueryBuilder) => {
|