@directus/api 14.1.0 → 14.1.1
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/dist/constants.js
CHANGED
|
@@ -55,7 +55,7 @@ export const COOKIE_OPTIONS = {
|
|
|
55
55
|
secure: env['REFRESH_TOKEN_COOKIE_SECURE'] ?? false,
|
|
56
56
|
sameSite: env['REFRESH_TOKEN_COOKIE_SAME_SITE'] || 'strict',
|
|
57
57
|
};
|
|
58
|
-
export const OAS_REQUIRED_SCHEMAS = ['
|
|
58
|
+
export const OAS_REQUIRED_SCHEMAS = ['Query', 'x-metadata'];
|
|
59
59
|
/** Formats from which transformation is supported */
|
|
60
60
|
export const SUPPORTED_IMAGE_TRANSFORM_FORMATS = ['image/jpeg', 'image/png', 'image/webp', 'image/tiff', 'image/avif'];
|
|
61
61
|
/** Formats where metadata extraction is supported */
|
|
@@ -11,7 +11,7 @@ router.get('/specs/oas', asyncHandler(async (req, res, next) => {
|
|
|
11
11
|
accountability: req.accountability,
|
|
12
12
|
schema: req.schema,
|
|
13
13
|
});
|
|
14
|
-
res.locals['payload'] = await service.oas.generate();
|
|
14
|
+
res.locals['payload'] = await service.oas.generate(req.headers.host);
|
|
15
15
|
return next();
|
|
16
16
|
}), respond);
|
|
17
17
|
router.get('/specs/graphql/:scope?', asyncHandler(async (req, res) => {
|
|
@@ -2,20 +2,14 @@ import type { Accountability, SchemaOverview } from '@directus/types';
|
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { OpenAPIObject } from 'openapi3-ts/oas30';
|
|
4
4
|
import type { AbstractServiceOptions } from '../types/index.js';
|
|
5
|
-
import { CollectionsService } from './collections.js';
|
|
6
|
-
import { FieldsService } from './fields.js';
|
|
7
5
|
import { GraphQLService } from './graphql/index.js';
|
|
8
|
-
import { RelationsService } from './relations.js';
|
|
9
6
|
export declare class SpecificationService {
|
|
10
7
|
accountability: Accountability | null;
|
|
11
8
|
knex: Knex;
|
|
12
9
|
schema: SchemaOverview;
|
|
13
|
-
fieldsService: FieldsService;
|
|
14
|
-
collectionsService: CollectionsService;
|
|
15
|
-
relationsService: RelationsService;
|
|
16
10
|
oas: OASSpecsService;
|
|
17
11
|
graphql: GraphQLSpecsService;
|
|
18
|
-
constructor(
|
|
12
|
+
constructor({ accountability, knex, schema }: AbstractServiceOptions);
|
|
19
13
|
}
|
|
20
14
|
interface SpecificationSubService {
|
|
21
15
|
generate: (_?: any) => Promise<any>;
|
|
@@ -24,15 +18,8 @@ declare class OASSpecsService implements SpecificationSubService {
|
|
|
24
18
|
accountability: Accountability | null;
|
|
25
19
|
knex: Knex;
|
|
26
20
|
schema: SchemaOverview;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
relationsService: RelationsService;
|
|
30
|
-
constructor(options: AbstractServiceOptions, { fieldsService, collectionsService, relationsService, }: {
|
|
31
|
-
fieldsService: FieldsService;
|
|
32
|
-
collectionsService: CollectionsService;
|
|
33
|
-
relationsService: RelationsService;
|
|
34
|
-
});
|
|
35
|
-
generate(): Promise<OpenAPIObject>;
|
|
21
|
+
constructor({ knex, schema, accountability }: AbstractServiceOptions);
|
|
22
|
+
generate(host?: string): Promise<OpenAPIObject>;
|
|
36
23
|
private generateTags;
|
|
37
24
|
private generatePaths;
|
|
38
25
|
private generateComponents;
|
|
@@ -6,57 +6,39 @@ import getDatabase from '../database/index.js';
|
|
|
6
6
|
import env from '../env.js';
|
|
7
7
|
import { getRelationType } from '../utils/get-relation-type.js';
|
|
8
8
|
import { version } from '../utils/package.js';
|
|
9
|
-
import { CollectionsService } from './collections.js';
|
|
10
|
-
import { FieldsService } from './fields.js';
|
|
11
9
|
import { GraphQLService } from './graphql/index.js';
|
|
12
|
-
import {
|
|
10
|
+
import { reduceSchema } from '../utils/reduce-schema.js';
|
|
13
11
|
export class SpecificationService {
|
|
14
12
|
accountability;
|
|
15
13
|
knex;
|
|
16
14
|
schema;
|
|
17
|
-
fieldsService;
|
|
18
|
-
collectionsService;
|
|
19
|
-
relationsService;
|
|
20
15
|
oas;
|
|
21
16
|
graphql;
|
|
22
|
-
constructor(
|
|
23
|
-
this.accountability =
|
|
24
|
-
this.knex =
|
|
25
|
-
this.schema =
|
|
26
|
-
this.
|
|
27
|
-
this.
|
|
28
|
-
this.relationsService = new RelationsService(options);
|
|
29
|
-
this.oas = new OASSpecsService(options, {
|
|
30
|
-
fieldsService: this.fieldsService,
|
|
31
|
-
collectionsService: this.collectionsService,
|
|
32
|
-
relationsService: this.relationsService,
|
|
33
|
-
});
|
|
34
|
-
this.graphql = new GraphQLSpecsService(options);
|
|
17
|
+
constructor({ accountability, knex, schema }) {
|
|
18
|
+
this.accountability = accountability || null;
|
|
19
|
+
this.knex = knex || getDatabase();
|
|
20
|
+
this.schema = schema;
|
|
21
|
+
this.oas = new OASSpecsService({ knex, schema, accountability });
|
|
22
|
+
this.graphql = new GraphQLSpecsService({ knex, schema });
|
|
35
23
|
}
|
|
36
24
|
}
|
|
37
25
|
class OASSpecsService {
|
|
38
26
|
accountability;
|
|
39
27
|
knex;
|
|
40
28
|
schema;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.knex = options.knex || getDatabase();
|
|
47
|
-
this.schema = options.schema;
|
|
48
|
-
this.fieldsService = fieldsService;
|
|
49
|
-
this.collectionsService = collectionsService;
|
|
50
|
-
this.relationsService = relationsService;
|
|
29
|
+
constructor({ knex, schema, accountability }) {
|
|
30
|
+
this.accountability = accountability || null;
|
|
31
|
+
this.knex = knex || getDatabase();
|
|
32
|
+
this.schema =
|
|
33
|
+
this.accountability?.admin === true ? schema : reduceSchema(schema, accountability?.permissions || null);
|
|
51
34
|
}
|
|
52
|
-
async generate() {
|
|
53
|
-
const collections = await this.collectionsService.readByQuery();
|
|
54
|
-
const fields = await this.fieldsService.readAll();
|
|
55
|
-
const relations = (await this.relationsService.readAll());
|
|
35
|
+
async generate(host) {
|
|
56
36
|
const permissions = this.accountability?.permissions ?? [];
|
|
57
|
-
const tags = await this.generateTags(
|
|
37
|
+
const tags = await this.generateTags();
|
|
58
38
|
const paths = await this.generatePaths(permissions, tags);
|
|
59
|
-
const components = await this.generateComponents(
|
|
39
|
+
const components = await this.generateComponents(tags);
|
|
40
|
+
const isDefaultPublicUrl = env['PUBLIC_URL'] === '/';
|
|
41
|
+
const url = isDefaultPublicUrl && host ? host : env['PUBLIC_URL'];
|
|
60
42
|
const spec = {
|
|
61
43
|
openapi: '3.0.1',
|
|
62
44
|
info: {
|
|
@@ -66,7 +48,7 @@ class OASSpecsService {
|
|
|
66
48
|
},
|
|
67
49
|
servers: [
|
|
68
50
|
{
|
|
69
|
-
url
|
|
51
|
+
url,
|
|
70
52
|
description: 'Your current Directus instance.',
|
|
71
53
|
},
|
|
72
54
|
],
|
|
@@ -78,11 +60,17 @@ class OASSpecsService {
|
|
|
78
60
|
spec.components = components;
|
|
79
61
|
return spec;
|
|
80
62
|
}
|
|
81
|
-
async generateTags(
|
|
63
|
+
async generateTags() {
|
|
82
64
|
const systemTags = cloneDeep(spec.tags);
|
|
65
|
+
const collections = Object.values(this.schema.collections);
|
|
83
66
|
const tags = [];
|
|
84
|
-
// System tags that don't have an associated collection are always readable to the user
|
|
85
67
|
for (const systemTag of systemTags) {
|
|
68
|
+
// Check if necessary authentication level is given
|
|
69
|
+
if (systemTag['x-authentication'] === 'admin' && !this.accountability?.admin)
|
|
70
|
+
continue;
|
|
71
|
+
if (systemTag['x-authentication'] === 'user' && !this.accountability?.user)
|
|
72
|
+
continue;
|
|
73
|
+
// Remaining system tags that don't have an associated collection are publicly available
|
|
86
74
|
if (!systemTag['x-collection']) {
|
|
87
75
|
tags.push(systemTag);
|
|
88
76
|
}
|
|
@@ -103,8 +91,8 @@ class OASSpecsService {
|
|
|
103
91
|
name: 'Items' + formatTitle(collection.collection).replace(/ /g, ''),
|
|
104
92
|
'x-collection': collection.collection,
|
|
105
93
|
};
|
|
106
|
-
if (collection.
|
|
107
|
-
tag.description = collection.
|
|
94
|
+
if (collection.note) {
|
|
95
|
+
tag.description = collection.note;
|
|
108
96
|
}
|
|
109
97
|
tags.push(tag);
|
|
110
98
|
}
|
|
@@ -256,33 +244,38 @@ class OASSpecsService {
|
|
|
256
244
|
}
|
|
257
245
|
return paths;
|
|
258
246
|
}
|
|
259
|
-
async generateComponents(
|
|
247
|
+
async generateComponents(tags) {
|
|
248
|
+
if (!tags)
|
|
249
|
+
return;
|
|
260
250
|
let components = cloneDeep(spec.components);
|
|
261
251
|
if (!components)
|
|
262
252
|
components = {};
|
|
263
253
|
components.schemas = {};
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
254
|
+
const tagSchemas = tags.reduce((schemas, tag) => [...schemas, ...(tag['x-schemas'] ? tag['x-schemas'] : [])], []);
|
|
255
|
+
const requiredSchemas = [...OAS_REQUIRED_SCHEMAS, ...tagSchemas];
|
|
256
|
+
for (const [name, schema] of Object.entries(spec.components?.schemas ?? {})) {
|
|
257
|
+
if (requiredSchemas.includes(name)) {
|
|
258
|
+
const collection = spec.tags?.find((tag) => tag.name === name)?.['x-collection'];
|
|
259
|
+
components.schemas[name] = {
|
|
260
|
+
...cloneDeep(schema),
|
|
261
|
+
...(collection && { 'x-collection': collection }),
|
|
262
|
+
};
|
|
270
263
|
}
|
|
271
264
|
}
|
|
272
|
-
|
|
273
|
-
return;
|
|
265
|
+
const collections = Object.values(this.schema.collections);
|
|
274
266
|
for (const collection of collections) {
|
|
275
267
|
const tag = tags.find((tag) => tag['x-collection'] === collection.collection);
|
|
276
268
|
if (!tag)
|
|
277
269
|
continue;
|
|
278
270
|
const isSystem = collection.collection.startsWith('directus_');
|
|
279
|
-
const fieldsInCollection =
|
|
271
|
+
const fieldsInCollection = Object.values(collection.fields);
|
|
280
272
|
if (isSystem) {
|
|
281
273
|
const schemaComponent = cloneDeep(spec.components.schemas[tag.name]);
|
|
282
274
|
schemaComponent.properties = {};
|
|
275
|
+
schemaComponent['x-collection'] = collection.collection;
|
|
283
276
|
for (const field of fieldsInCollection) {
|
|
284
277
|
schemaComponent.properties[field.field] =
|
|
285
|
-
cloneDeep(spec.components.schemas[tag.name].properties[field.field]) || this.generateField(
|
|
278
|
+
cloneDeep(spec.components.schemas[tag.name].properties[field.field]) || this.generateField(collection.collection, field, tags);
|
|
286
279
|
}
|
|
287
280
|
components.schemas[tag.name] = schemaComponent;
|
|
288
281
|
}
|
|
@@ -293,7 +286,7 @@ class OASSpecsService {
|
|
|
293
286
|
'x-collection': collection.collection,
|
|
294
287
|
};
|
|
295
288
|
for (const field of fieldsInCollection) {
|
|
296
|
-
schemaComponent.properties[field.field] = this.generateField(
|
|
289
|
+
schemaComponent.properties[field.field] = this.generateField(collection.collection, field, tags);
|
|
297
290
|
}
|
|
298
291
|
components.schemas[tag.name] = schemaComponent;
|
|
299
292
|
}
|
|
@@ -316,16 +309,14 @@ class OASSpecsService {
|
|
|
316
309
|
return 'read';
|
|
317
310
|
}
|
|
318
311
|
}
|
|
319
|
-
generateField(
|
|
312
|
+
generateField(collection, field, tags) {
|
|
320
313
|
let propertyObject = {};
|
|
321
|
-
|
|
322
|
-
|
|
314
|
+
propertyObject.nullable = field.nullable;
|
|
315
|
+
if (field.note) {
|
|
316
|
+
propertyObject.description = field.note;
|
|
323
317
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
const relation = relations.find((relation) => (relation.collection === field.collection && relation.field === field.field) ||
|
|
328
|
-
(relation.related_collection === field.collection && relation.meta?.one_field === field.field));
|
|
318
|
+
const relation = this.schema.relations.find((relation) => (relation.collection === collection && relation.field === field.field) ||
|
|
319
|
+
(relation.related_collection === collection && relation.meta?.one_field === field.field));
|
|
329
320
|
if (!relation) {
|
|
330
321
|
propertyObject = {
|
|
331
322
|
...propertyObject,
|
|
@@ -336,13 +327,17 @@ class OASSpecsService {
|
|
|
336
327
|
const relationType = getRelationType({
|
|
337
328
|
relation,
|
|
338
329
|
field: field.field,
|
|
339
|
-
collection:
|
|
330
|
+
collection: collection,
|
|
340
331
|
});
|
|
341
332
|
if (relationType === 'm2o') {
|
|
342
333
|
const relatedTag = tags.find((tag) => tag['x-collection'] === relation.related_collection);
|
|
343
|
-
|
|
344
|
-
|
|
334
|
+
if (!relatedTag ||
|
|
335
|
+
!relation.related_collection ||
|
|
336
|
+
relation.related_collection in this.schema.collections === false) {
|
|
345
337
|
return propertyObject;
|
|
338
|
+
}
|
|
339
|
+
const relatedCollection = this.schema.collections[relation.related_collection];
|
|
340
|
+
const relatedPrimaryKeyField = relatedCollection.fields[relatedCollection.primary];
|
|
346
341
|
propertyObject.oneOf = [
|
|
347
342
|
{
|
|
348
343
|
...this.fieldTypes[relatedPrimaryKeyField.type],
|
|
@@ -354,7 +349,11 @@ class OASSpecsService {
|
|
|
354
349
|
}
|
|
355
350
|
else if (relationType === 'o2m') {
|
|
356
351
|
const relatedTag = tags.find((tag) => tag['x-collection'] === relation.collection);
|
|
357
|
-
|
|
352
|
+
if (!relatedTag || !relation.related_collection || relation.collection in this.schema.collections === false) {
|
|
353
|
+
return propertyObject;
|
|
354
|
+
}
|
|
355
|
+
const relatedCollection = this.schema.collections[relation.collection];
|
|
356
|
+
const relatedPrimaryKeyField = relatedCollection.fields[relatedCollection.primary];
|
|
358
357
|
if (!relatedTag || !relatedPrimaryKeyField)
|
|
359
358
|
return propertyObject;
|
|
360
359
|
propertyObject.type = 'array';
|
package/dist/types/services.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { Accountability, Query, SchemaOverview } from '@directus/types';
|
|
|
2
2
|
import type { Knex } from 'knex';
|
|
3
3
|
import type { Item, PrimaryKey } from './items.js';
|
|
4
4
|
export type AbstractServiceOptions = {
|
|
5
|
-
knex?: Knex;
|
|
5
|
+
knex?: Knex | undefined;
|
|
6
6
|
accountability?: Accountability | null | undefined;
|
|
7
7
|
schema: SchemaOverview;
|
|
8
8
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "14.1.
|
|
3
|
+
"version": "14.1.1",
|
|
4
4
|
"description": "Directus is a real-time API and App dashboard for managing SQL database content",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"directus",
|
|
@@ -145,23 +145,23 @@
|
|
|
145
145
|
"ws": "8.14.2",
|
|
146
146
|
"zod": "3.22.4",
|
|
147
147
|
"zod-validation-error": "1.0.1",
|
|
148
|
-
"@directus/app": "10.13.
|
|
148
|
+
"@directus/app": "10.13.1",
|
|
149
149
|
"@directus/constants": "11.0.1",
|
|
150
|
-
"@directus/
|
|
150
|
+
"@directus/extensions": "0.2.0",
|
|
151
151
|
"@directus/extensions-sdk": "10.2.0",
|
|
152
|
+
"@directus/errors": "0.2.0",
|
|
152
153
|
"@directus/pressure": "1.0.13",
|
|
153
|
-
"@directus/extensions": "0.2.0",
|
|
154
154
|
"@directus/schema": "11.0.0",
|
|
155
|
-
"@directus/specs": "10.2.2",
|
|
156
155
|
"@directus/storage": "10.0.7",
|
|
156
|
+
"@directus/specs": "10.2.3",
|
|
157
157
|
"@directus/storage-driver-azure": "10.0.14",
|
|
158
|
-
"@directus/storage-driver-gcs": "10.0.14",
|
|
159
|
-
"@directus/storage-driver-cloudinary": "10.0.14",
|
|
160
158
|
"@directus/storage-driver-local": "10.0.14",
|
|
159
|
+
"@directus/storage-driver-cloudinary": "10.0.14",
|
|
161
160
|
"@directus/storage-driver-s3": "10.0.14",
|
|
161
|
+
"@directus/storage-driver-gcs": "10.0.14",
|
|
162
162
|
"@directus/storage-driver-supabase": "1.0.6",
|
|
163
|
-
"@directus/
|
|
164
|
-
"@directus/
|
|
163
|
+
"@directus/utils": "11.0.2",
|
|
164
|
+
"@directus/validation": "0.0.9"
|
|
165
165
|
},
|
|
166
166
|
"devDependencies": {
|
|
167
167
|
"@ngneat/falso": "6.4.0",
|