@directus/api 12.0.3 → 12.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/database/migrations/20230721A-require-shares-fields.d.ts +3 -0
- package/dist/database/migrations/20230721A-require-shares-fields.js +12 -0
- package/dist/database/system-data/fields/shares.yaml +1 -1
- package/dist/middleware/error-handler.js +9 -16
- package/dist/services/graphql/subscription.d.ts +12 -6
- package/dist/services/graphql/subscription.js +43 -16
- package/dist/services/import-export.js +6 -0
- package/dist/services/mail/templates/base.liquid +2 -2
- package/dist/services/meta.js +11 -3
- package/dist/storage/get-storage-driver.js +1 -0
- package/dist/utils/url.d.ts +1 -0
- package/dist/utils/url.js +8 -1
- package/dist/websocket/controllers/hooks.js +14 -0
- package/dist/websocket/handlers/subscribe.d.ts +0 -5
- package/dist/websocket/handlers/subscribe.js +5 -94
- package/dist/websocket/utils/items.d.ts +53 -0
- package/dist/websocket/utils/items.js +133 -0
- package/package.json +19 -18
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function up(knex) {
|
|
2
|
+
await knex.schema.alterTable('directus_shares', (table) => {
|
|
3
|
+
table.dropNullable('collection');
|
|
4
|
+
table.dropNullable('item');
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
export async function down(knex) {
|
|
8
|
+
await knex.schema.alterTable('directus_shares', (table) => {
|
|
9
|
+
table.setNullable('collection');
|
|
10
|
+
table.setNullable('item');
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -12,20 +12,7 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
12
12
|
errors: [],
|
|
13
13
|
};
|
|
14
14
|
const errors = toArray(err);
|
|
15
|
-
|
|
16
|
-
res.status(500);
|
|
17
|
-
}
|
|
18
|
-
else {
|
|
19
|
-
let status = errors[0].status;
|
|
20
|
-
for (const err of errors) {
|
|
21
|
-
if (status !== err.status) {
|
|
22
|
-
// If there's multiple different status codes in the errors, use 500
|
|
23
|
-
status = 500;
|
|
24
|
-
break;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
res.status(status);
|
|
28
|
-
}
|
|
15
|
+
let status = null;
|
|
29
16
|
for (const err of errors) {
|
|
30
17
|
if (env['NODE_ENV'] === 'development') {
|
|
31
18
|
err.extensions = {
|
|
@@ -35,7 +22,12 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
35
22
|
}
|
|
36
23
|
if (isDirectusError(err)) {
|
|
37
24
|
logger.debug(err);
|
|
38
|
-
|
|
25
|
+
if (!status) {
|
|
26
|
+
status = err.status;
|
|
27
|
+
}
|
|
28
|
+
else if (status !== err.status) {
|
|
29
|
+
status = 500;
|
|
30
|
+
}
|
|
39
31
|
payload.errors.push({
|
|
40
32
|
message: err.message,
|
|
41
33
|
extensions: {
|
|
@@ -49,7 +41,7 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
49
41
|
}
|
|
50
42
|
else {
|
|
51
43
|
logger.error(err);
|
|
52
|
-
|
|
44
|
+
status = 500;
|
|
53
45
|
if (req.accountability?.admin === true) {
|
|
54
46
|
payload = {
|
|
55
47
|
errors: [
|
|
@@ -77,6 +69,7 @@ const errorHandler = (err, req, res, _next) => {
|
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
}
|
|
72
|
+
res.status(status ?? 500);
|
|
80
73
|
emitter
|
|
81
74
|
.emitFilter('request.error', payload.errors, {}, {
|
|
82
75
|
database: getDatabase(),
|
|
@@ -3,14 +3,20 @@ import type { GraphQLResolveInfo } from 'graphql';
|
|
|
3
3
|
export declare function bindPubSub(): void;
|
|
4
4
|
export declare function createSubscriptionGenerator(self: GraphQLService, event: string): (_x: unknown, _y: unknown, _z: unknown, request: GraphQLResolveInfo) => AsyncGenerator<{
|
|
5
5
|
[x: string]: {
|
|
6
|
-
key:
|
|
7
|
-
data:
|
|
8
|
-
event:
|
|
6
|
+
key: string | number;
|
|
7
|
+
data: null;
|
|
8
|
+
event: "delete";
|
|
9
9
|
};
|
|
10
10
|
} | {
|
|
11
11
|
[x: string]: {
|
|
12
|
-
key:
|
|
13
|
-
data:
|
|
14
|
-
event:
|
|
12
|
+
key: string | number;
|
|
13
|
+
data: any;
|
|
14
|
+
event: "create";
|
|
15
|
+
};
|
|
16
|
+
} | {
|
|
17
|
+
[x: string]: {
|
|
18
|
+
key: string | number;
|
|
19
|
+
data: any;
|
|
20
|
+
event: "update";
|
|
15
21
|
};
|
|
16
22
|
}, void, unknown>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { EventEmitter, on } from 'events';
|
|
2
2
|
import { getMessenger } from '../../messenger.js';
|
|
3
3
|
import { getSchema } from '../../utils/get-schema.js';
|
|
4
|
-
import {
|
|
4
|
+
import { refreshAccountability } from '../../websocket/authenticate.js';
|
|
5
|
+
import { getSinglePayload } from '../../websocket/utils/items.js';
|
|
5
6
|
const messages = createPubSub(new EventEmitter());
|
|
6
7
|
export function bindPubSub() {
|
|
7
8
|
const messenger = getMessenger();
|
|
@@ -18,25 +19,51 @@ export function createSubscriptionGenerator(self, event) {
|
|
|
18
19
|
if ('event' in args && eventData['action'] !== args['event']) {
|
|
19
20
|
continue; // skip filtered events
|
|
20
21
|
}
|
|
22
|
+
const accountability = await refreshAccountability(self.accountability);
|
|
21
23
|
const schema = await getSchema();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
const subscription = {
|
|
25
|
+
collection: eventData['collection'],
|
|
26
|
+
event: eventData['action'],
|
|
27
|
+
query: { fields },
|
|
28
|
+
};
|
|
29
|
+
if (eventData['action'] === 'delete') {
|
|
30
|
+
// we have no data to send besides the key
|
|
31
|
+
for (const key of eventData.keys) {
|
|
32
|
+
yield { [event]: { key, data: null, event: eventData['action'] } };
|
|
33
|
+
}
|
|
27
34
|
}
|
|
28
|
-
if (eventData['action'] === '
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
35
|
+
if (eventData['action'] === 'create') {
|
|
36
|
+
try {
|
|
37
|
+
subscription.item = eventData['key'];
|
|
38
|
+
const result = await getSinglePayload(subscription, accountability, schema, eventData);
|
|
39
|
+
yield {
|
|
40
|
+
[event]: {
|
|
41
|
+
key: eventData['key'],
|
|
42
|
+
data: result['data'],
|
|
43
|
+
event: eventData['action'],
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// dont notify the subscription of permission errors
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
|
-
if (eventData['action'] === '
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
51
|
+
if (eventData['action'] === 'update') {
|
|
52
|
+
for (const key of eventData['keys']) {
|
|
53
|
+
try {
|
|
54
|
+
subscription.item = key;
|
|
55
|
+
const result = await getSinglePayload(subscription, accountability, schema, eventData);
|
|
56
|
+
yield {
|
|
57
|
+
[event]: {
|
|
58
|
+
key,
|
|
59
|
+
data: result['data'],
|
|
60
|
+
event: eventData['action'],
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// dont notify the subscription of permission errors
|
|
66
|
+
}
|
|
40
67
|
}
|
|
41
68
|
}
|
|
42
69
|
}
|
|
@@ -169,6 +169,11 @@ export class ExportService {
|
|
|
169
169
|
schema: this.schema,
|
|
170
170
|
knex: trx,
|
|
171
171
|
});
|
|
172
|
+
const { primary } = this.schema.collections[collection];
|
|
173
|
+
const sort = query.sort ?? [];
|
|
174
|
+
if (sort.includes(primary) === false) {
|
|
175
|
+
sort.push(primary);
|
|
176
|
+
}
|
|
172
177
|
const totalCount = await service
|
|
173
178
|
.readByQuery({
|
|
174
179
|
...query,
|
|
@@ -188,6 +193,7 @@ export class ExportService {
|
|
|
188
193
|
}
|
|
189
194
|
const result = await service.readByQuery({
|
|
190
195
|
...query,
|
|
196
|
+
sort,
|
|
191
197
|
limit,
|
|
192
198
|
offset: batch * env['EXPORT_BATCH_SIZE'],
|
|
193
199
|
});
|
|
@@ -35,7 +35,7 @@ a[x-apple-data-detectors] {
|
|
|
35
35
|
line-height: inherit !important;
|
|
36
36
|
}
|
|
37
37
|
body a {
|
|
38
|
-
color:
|
|
38
|
+
color: {{projectColor}};
|
|
39
39
|
text-decoration: none;
|
|
40
40
|
}
|
|
41
41
|
hr {
|
|
@@ -74,7 +74,7 @@ hr {
|
|
|
74
74
|
color: #FFFFFF !important;
|
|
75
75
|
}
|
|
76
76
|
.link {
|
|
77
|
-
color:
|
|
77
|
+
color: {{projectColor}} !important;
|
|
78
78
|
}
|
|
79
79
|
.button {
|
|
80
80
|
background-color:#0BA582 !important;
|
package/dist/services/meta.js
CHANGED
|
@@ -42,8 +42,9 @@ export class MetaService {
|
|
|
42
42
|
return Number(result?.count ?? 0);
|
|
43
43
|
}
|
|
44
44
|
async filterCount(collection, query) {
|
|
45
|
-
const dbQuery = this.knex(collection)
|
|
45
|
+
const dbQuery = this.knex(collection);
|
|
46
46
|
let filter = query.filter || {};
|
|
47
|
+
let hasJoins = false;
|
|
47
48
|
if (this.accountability?.admin !== true) {
|
|
48
49
|
const permissionsRecord = this.accountability?.permissions?.find((permission) => {
|
|
49
50
|
return permission.action === 'read' && permission.collection === collection;
|
|
@@ -59,12 +60,19 @@ export class MetaService {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
if (Object.keys(filter).length > 0) {
|
|
62
|
-
applyFilter(this.knex, this.schema, dbQuery, filter, collection, {});
|
|
63
|
+
({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}));
|
|
63
64
|
}
|
|
64
65
|
if (query.search) {
|
|
65
66
|
applySearch(this.schema, dbQuery, query.search, collection);
|
|
66
67
|
}
|
|
68
|
+
if (hasJoins) {
|
|
69
|
+
const primaryKeyName = this.schema.collections[collection].primary;
|
|
70
|
+
dbQuery.countDistinct({ count: [`${collection}.${primaryKeyName}`] });
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
dbQuery.count('*', { as: 'count' });
|
|
74
|
+
}
|
|
67
75
|
const records = await dbQuery;
|
|
68
|
-
return Number(records[0]
|
|
76
|
+
return Number(records[0]['count']);
|
|
69
77
|
}
|
|
70
78
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const _aliasMap = {
|
|
2
2
|
local: '@directus/storage-driver-local',
|
|
3
3
|
s3: '@directus/storage-driver-s3',
|
|
4
|
+
supabase: '@directus/storage-driver-supabase',
|
|
4
5
|
gcs: '@directus/storage-driver-gcs',
|
|
5
6
|
azure: '@directus/storage-driver-azure',
|
|
6
7
|
cloudinary: '@directus/storage-driver-cloudinary',
|
package/dist/utils/url.d.ts
CHANGED
package/dist/utils/url.js
CHANGED
|
@@ -6,6 +6,7 @@ export class Url {
|
|
|
6
6
|
path;
|
|
7
7
|
query;
|
|
8
8
|
hash;
|
|
9
|
+
hasTrailingSlash;
|
|
9
10
|
constructor(url) {
|
|
10
11
|
const parsedUrl = new URL(url, 'http://localhost');
|
|
11
12
|
const isProtocolRelative = /^\/\//.test(url);
|
|
@@ -20,6 +21,7 @@ export class Url {
|
|
|
20
21
|
this.path = parsedUrl.pathname.split('/').filter((p) => p !== '');
|
|
21
22
|
this.query = Object.fromEntries(parsedUrl.searchParams.entries());
|
|
22
23
|
this.hash = parsedUrl.hash !== '' ? parsedUrl.hash.substring(1) : null;
|
|
24
|
+
this.hasTrailingSlash = parsedUrl.pathname.length > 1 ? parsedUrl.pathname.endsWith('/') : url.endsWith('/');
|
|
23
25
|
}
|
|
24
26
|
isAbsolute() {
|
|
25
27
|
return this.protocol !== null && this.host !== null;
|
|
@@ -40,6 +42,10 @@ export class Url {
|
|
|
40
42
|
this.path.push(pathSegment);
|
|
41
43
|
}
|
|
42
44
|
}
|
|
45
|
+
const lastPath = paths.at(-1);
|
|
46
|
+
if (pathToAdd.length > 0 && lastPath !== '.' && lastPath !== '..') {
|
|
47
|
+
this.hasTrailingSlash = typeof lastPath === 'string' && lastPath.endsWith('/');
|
|
48
|
+
}
|
|
43
49
|
return this;
|
|
44
50
|
}
|
|
45
51
|
setQuery(key, value) {
|
|
@@ -52,8 +58,9 @@ export class Url {
|
|
|
52
58
|
const port = this.port !== null ? `:${this.port}` : '';
|
|
53
59
|
const origin = `${this.host !== null ? `${protocol}//` : ''}${host}${port}`;
|
|
54
60
|
const path = this.path.length ? `/${this.path.join('/')}` : '';
|
|
61
|
+
const trailingSlash = this.hasTrailingSlash ? '/' : '';
|
|
55
62
|
const query = Object.keys(this.query).length !== 0 ? `?${new URLSearchParams(this.query).toString()}` : '';
|
|
56
63
|
const hash = this.hash !== null ? `#${this.hash}` : '';
|
|
57
|
-
return `${!rootRelative ? origin : ''}${path}${query}${hash}`;
|
|
64
|
+
return `${!rootRelative ? origin : ''}${path}${trailingSlash}${query}${hash}`;
|
|
58
65
|
}
|
|
59
66
|
}
|
|
@@ -9,18 +9,24 @@ export function registerWebSocketEvents() {
|
|
|
9
9
|
'items',
|
|
10
10
|
'activity',
|
|
11
11
|
'collections',
|
|
12
|
+
'dashboards',
|
|
12
13
|
'folders',
|
|
14
|
+
'notifications',
|
|
15
|
+
'operations',
|
|
16
|
+
'panels',
|
|
13
17
|
'permissions',
|
|
14
18
|
'presets',
|
|
15
19
|
'revisions',
|
|
16
20
|
'roles',
|
|
17
21
|
'settings',
|
|
22
|
+
'shares',
|
|
18
23
|
'users',
|
|
19
24
|
'webhooks',
|
|
20
25
|
]);
|
|
21
26
|
registerFieldsHooks();
|
|
22
27
|
registerFilesHooks();
|
|
23
28
|
registerRelationsHooks();
|
|
29
|
+
registerSortHooks();
|
|
24
30
|
}
|
|
25
31
|
function registerActionHooks(modules) {
|
|
26
32
|
// register event hooks that can be handled in an uniform manner
|
|
@@ -108,6 +114,14 @@ function registerRelationsHooks() {
|
|
|
108
114
|
payload: { collection, fields: payload },
|
|
109
115
|
}));
|
|
110
116
|
}
|
|
117
|
+
function registerSortHooks() {
|
|
118
|
+
registerAction('items.sort', ({ collection, item }) => ({
|
|
119
|
+
collection,
|
|
120
|
+
action: 'update',
|
|
121
|
+
keys: [item],
|
|
122
|
+
payload: {},
|
|
123
|
+
}));
|
|
124
|
+
}
|
|
111
125
|
/**
|
|
112
126
|
* Wrapper for emitter.onAction to hook into system events
|
|
113
127
|
* @param event The action event to watch
|
|
@@ -34,10 +34,5 @@ export declare class SubscribeHandler {
|
|
|
34
34
|
* Handle incoming (un)subscribe requests
|
|
35
35
|
*/
|
|
36
36
|
onMessage(client: WebSocketClient, message: WebSocketSubscribeMessage): Promise<void>;
|
|
37
|
-
private getSinglePayload;
|
|
38
|
-
private getMultiPayload;
|
|
39
|
-
private getCollectionPayload;
|
|
40
|
-
private getFieldsPayload;
|
|
41
|
-
private getItemsPayload;
|
|
42
37
|
private getSubscription;
|
|
43
38
|
}
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import emitter from '../../emitter.js';
|
|
2
2
|
import { InvalidPayloadError } from '../../errors/index.js';
|
|
3
3
|
import { getMessenger } from '../../messenger.js';
|
|
4
|
-
import { CollectionsService, FieldsService, MetaService } from '../../services/index.js';
|
|
5
4
|
import { getSchema } from '../../utils/get-schema.js';
|
|
6
|
-
import { getService } from '../../utils/get-service.js';
|
|
7
5
|
import { sanitizeQuery } from '../../utils/sanitize-query.js';
|
|
8
6
|
import { refreshAccountability } from '../authenticate.js';
|
|
9
7
|
import { WebSocketError, handleWebSocketError } from '../errors.js';
|
|
10
8
|
import { WebSocketSubscribeMessage } from '../messages.js';
|
|
11
9
|
import { fmtMessage, getMessageType } from '../utils/message.js';
|
|
10
|
+
import { getMultiPayload, getSinglePayload } from '../utils/items.js';
|
|
12
11
|
/**
|
|
13
12
|
* Handler responsible for subscriptions
|
|
14
13
|
*/
|
|
@@ -108,8 +107,8 @@ export class SubscribeHandler {
|
|
|
108
107
|
try {
|
|
109
108
|
client.accountability = await refreshAccountability(client.accountability);
|
|
110
109
|
const result = 'item' in subscription
|
|
111
|
-
? await
|
|
112
|
-
: await
|
|
110
|
+
? await getSinglePayload(subscription, client.accountability, schema, event)
|
|
111
|
+
: await getMultiPayload(subscription, client.accountability, schema, event);
|
|
113
112
|
if (Array.isArray(result?.['data']) && result?.['data']?.length === 0)
|
|
114
113
|
return;
|
|
115
114
|
client.send(fmtMessage('subscription', result, subscription.uid));
|
|
@@ -152,8 +151,8 @@ export class SubscribeHandler {
|
|
|
152
151
|
if (subscription.event === undefined) {
|
|
153
152
|
data =
|
|
154
153
|
'item' in subscription
|
|
155
|
-
? await
|
|
156
|
-
: await
|
|
154
|
+
? await getSinglePayload(subscription, accountability, schema)
|
|
155
|
+
: await getMultiPayload(subscription, accountability, schema);
|
|
157
156
|
}
|
|
158
157
|
else {
|
|
159
158
|
data = { event: 'init' };
|
|
@@ -177,94 +176,6 @@ export class SubscribeHandler {
|
|
|
177
176
|
}
|
|
178
177
|
}
|
|
179
178
|
}
|
|
180
|
-
async getSinglePayload(subscription, accountability, schema, event) {
|
|
181
|
-
const metaService = new MetaService({ schema, accountability });
|
|
182
|
-
const query = subscription.query ?? {};
|
|
183
|
-
const id = subscription.item;
|
|
184
|
-
const result = {
|
|
185
|
-
event: event?.action ?? 'init',
|
|
186
|
-
};
|
|
187
|
-
if (subscription.collection === 'directus_collections') {
|
|
188
|
-
const service = new CollectionsService({ schema, accountability });
|
|
189
|
-
result['data'] = await service.readOne(String(id));
|
|
190
|
-
}
|
|
191
|
-
else {
|
|
192
|
-
const service = getService(subscription.collection, { schema, accountability });
|
|
193
|
-
result['data'] = await service.readOne(id, query);
|
|
194
|
-
}
|
|
195
|
-
if ('meta' in query) {
|
|
196
|
-
result['meta'] = await metaService.getMetaForQuery(subscription.collection, query);
|
|
197
|
-
}
|
|
198
|
-
return result;
|
|
199
|
-
}
|
|
200
|
-
async getMultiPayload(subscription, accountability, schema, event) {
|
|
201
|
-
const metaService = new MetaService({ schema, accountability });
|
|
202
|
-
const result = {
|
|
203
|
-
event: event?.action ?? 'init',
|
|
204
|
-
};
|
|
205
|
-
switch (subscription.collection) {
|
|
206
|
-
case 'directus_collections':
|
|
207
|
-
result['data'] = await this.getCollectionPayload(accountability, schema, event);
|
|
208
|
-
break;
|
|
209
|
-
case 'directus_fields':
|
|
210
|
-
result['data'] = await this.getFieldsPayload(accountability, schema, event);
|
|
211
|
-
break;
|
|
212
|
-
case 'directus_relations':
|
|
213
|
-
result['data'] = event?.payload;
|
|
214
|
-
break;
|
|
215
|
-
default:
|
|
216
|
-
result['data'] = await this.getItemsPayload(subscription, accountability, schema, event);
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
const query = subscription.query ?? {};
|
|
220
|
-
if ('meta' in query) {
|
|
221
|
-
result['meta'] = await metaService.getMetaForQuery(subscription.collection, query);
|
|
222
|
-
}
|
|
223
|
-
return result;
|
|
224
|
-
}
|
|
225
|
-
async getCollectionPayload(accountability, schema, event) {
|
|
226
|
-
const service = new CollectionsService({ schema, accountability });
|
|
227
|
-
if (!event?.action) {
|
|
228
|
-
return await service.readByQuery();
|
|
229
|
-
}
|
|
230
|
-
else if (event.action === 'create') {
|
|
231
|
-
return await service.readMany([String(event.key)]);
|
|
232
|
-
}
|
|
233
|
-
else if (event.action === 'delete') {
|
|
234
|
-
return event.keys;
|
|
235
|
-
}
|
|
236
|
-
else {
|
|
237
|
-
return await service.readMany(event.keys.map((key) => String(key)));
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
async getFieldsPayload(accountability, schema, event) {
|
|
241
|
-
const service = new FieldsService({ schema, accountability });
|
|
242
|
-
if (!event?.action) {
|
|
243
|
-
return await service.readAll();
|
|
244
|
-
}
|
|
245
|
-
else if (event.action === 'delete') {
|
|
246
|
-
return event.keys;
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
return await service.readOne(event.payload?.['collection'], event.payload?.['field']);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
async getItemsPayload(subscription, accountability, schema, event) {
|
|
253
|
-
const query = subscription.query ?? {};
|
|
254
|
-
const service = getService(subscription.collection, { schema, accountability });
|
|
255
|
-
if (!event?.action) {
|
|
256
|
-
return await service.readByQuery(query);
|
|
257
|
-
}
|
|
258
|
-
else if (event.action === 'create') {
|
|
259
|
-
return await service.readMany([event.key], query);
|
|
260
|
-
}
|
|
261
|
-
else if (event.action === 'delete') {
|
|
262
|
-
return event.keys;
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
return await service.readMany(event.keys, query);
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
179
|
getSubscription(client, uid) {
|
|
269
180
|
for (const userSubscriptions of Object.values(this.subscriptions)) {
|
|
270
181
|
for (const subscription of userSubscriptions) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Accountability, SchemaOverview } from '@directus/types';
|
|
2
|
+
import type { WebSocketEvent } from '../messages.js';
|
|
3
|
+
import type { Subscription } from '../types.js';
|
|
4
|
+
type PSubscription = Omit<Subscription, 'client'>;
|
|
5
|
+
/**
|
|
6
|
+
* Get a single item from a collection using the appropriate service
|
|
7
|
+
*
|
|
8
|
+
* @param subscription Subscription object
|
|
9
|
+
* @param accountability Accountability object
|
|
10
|
+
* @param schema Schema object
|
|
11
|
+
* @param event Event data
|
|
12
|
+
* @returns the fetched item
|
|
13
|
+
*/
|
|
14
|
+
export declare function getSinglePayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any>>;
|
|
15
|
+
/**
|
|
16
|
+
* Get items from a collection using the appropriate service
|
|
17
|
+
*
|
|
18
|
+
* @param subscription Subscription object
|
|
19
|
+
* @param accountability Accountability object
|
|
20
|
+
* @param schema Schema object
|
|
21
|
+
* @param event Event data
|
|
22
|
+
* @returns the fetched items
|
|
23
|
+
*/
|
|
24
|
+
export declare function getMultiPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any>>;
|
|
25
|
+
/**
|
|
26
|
+
* Get collection items
|
|
27
|
+
*
|
|
28
|
+
* @param accountability Accountability object
|
|
29
|
+
* @param schema Schema object
|
|
30
|
+
* @param event Event data
|
|
31
|
+
* @returns the fetched collection data
|
|
32
|
+
*/
|
|
33
|
+
export declare function getCollectionPayload(accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<(string | number)[] | import("../../types/collection.js").Collection[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Get fields items
|
|
36
|
+
*
|
|
37
|
+
* @param accountability Accountability object
|
|
38
|
+
* @param schema Schema object
|
|
39
|
+
* @param event Event data
|
|
40
|
+
* @returns the fetched field data
|
|
41
|
+
*/
|
|
42
|
+
export declare function getFieldsPayload(accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<Record<string, any> | import("@directus/types").Field[] | (string | number)[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Get items from a collection using the appropriate service
|
|
45
|
+
*
|
|
46
|
+
* @param subscription Subscription object
|
|
47
|
+
* @param accountability Accountability object
|
|
48
|
+
* @param schema Schema object
|
|
49
|
+
* @param event Event data
|
|
50
|
+
* @returns the fetched data
|
|
51
|
+
*/
|
|
52
|
+
export declare function getItemsPayload(subscription: PSubscription, accountability: Accountability | null, schema: SchemaOverview, event?: WebSocketEvent): Promise<(string | number)[] | import("../../types/items.js").Item[]>;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { getService } from '../../utils/get-service.js';
|
|
2
|
+
import { CollectionsService, FieldsService, MetaService } from '../../services/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Get a single item from a collection using the appropriate service
|
|
5
|
+
*
|
|
6
|
+
* @param subscription Subscription object
|
|
7
|
+
* @param accountability Accountability object
|
|
8
|
+
* @param schema Schema object
|
|
9
|
+
* @param event Event data
|
|
10
|
+
* @returns the fetched item
|
|
11
|
+
*/
|
|
12
|
+
export async function getSinglePayload(subscription, accountability, schema, event) {
|
|
13
|
+
const metaService = new MetaService({ schema, accountability });
|
|
14
|
+
const query = subscription.query ?? {};
|
|
15
|
+
const id = subscription.item;
|
|
16
|
+
const result = {
|
|
17
|
+
event: event?.action ?? 'init',
|
|
18
|
+
};
|
|
19
|
+
if (subscription.collection === 'directus_collections') {
|
|
20
|
+
const service = new CollectionsService({ schema, accountability });
|
|
21
|
+
result['data'] = await service.readOne(String(id));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const service = getService(subscription.collection, { schema, accountability });
|
|
25
|
+
result['data'] = await service.readOne(id, query);
|
|
26
|
+
}
|
|
27
|
+
if ('meta' in query) {
|
|
28
|
+
result['meta'] = await metaService.getMetaForQuery(subscription.collection, query);
|
|
29
|
+
}
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get items from a collection using the appropriate service
|
|
34
|
+
*
|
|
35
|
+
* @param subscription Subscription object
|
|
36
|
+
* @param accountability Accountability object
|
|
37
|
+
* @param schema Schema object
|
|
38
|
+
* @param event Event data
|
|
39
|
+
* @returns the fetched items
|
|
40
|
+
*/
|
|
41
|
+
export async function getMultiPayload(subscription, accountability, schema, event) {
|
|
42
|
+
const metaService = new MetaService({ schema, accountability });
|
|
43
|
+
const result = {
|
|
44
|
+
event: event?.action ?? 'init',
|
|
45
|
+
};
|
|
46
|
+
switch (subscription.collection) {
|
|
47
|
+
case 'directus_collections':
|
|
48
|
+
result['data'] = await getCollectionPayload(accountability, schema, event);
|
|
49
|
+
break;
|
|
50
|
+
case 'directus_fields':
|
|
51
|
+
result['data'] = await getFieldsPayload(accountability, schema, event);
|
|
52
|
+
break;
|
|
53
|
+
case 'directus_relations':
|
|
54
|
+
result['data'] = event?.payload;
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
result['data'] = await getItemsPayload(subscription, accountability, schema, event);
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
const query = subscription.query ?? {};
|
|
61
|
+
if ('meta' in query) {
|
|
62
|
+
result['meta'] = await metaService.getMetaForQuery(subscription.collection, query);
|
|
63
|
+
}
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get collection items
|
|
68
|
+
*
|
|
69
|
+
* @param accountability Accountability object
|
|
70
|
+
* @param schema Schema object
|
|
71
|
+
* @param event Event data
|
|
72
|
+
* @returns the fetched collection data
|
|
73
|
+
*/
|
|
74
|
+
export async function getCollectionPayload(accountability, schema, event) {
|
|
75
|
+
const service = new CollectionsService({ schema, accountability });
|
|
76
|
+
if (!event?.action) {
|
|
77
|
+
return await service.readByQuery();
|
|
78
|
+
}
|
|
79
|
+
else if (event.action === 'create') {
|
|
80
|
+
return await service.readMany([String(event.key)]);
|
|
81
|
+
}
|
|
82
|
+
else if (event.action === 'delete') {
|
|
83
|
+
return event.keys;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
return await service.readMany(event.keys.map((key) => String(key)));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get fields items
|
|
91
|
+
*
|
|
92
|
+
* @param accountability Accountability object
|
|
93
|
+
* @param schema Schema object
|
|
94
|
+
* @param event Event data
|
|
95
|
+
* @returns the fetched field data
|
|
96
|
+
*/
|
|
97
|
+
export async function getFieldsPayload(accountability, schema, event) {
|
|
98
|
+
const service = new FieldsService({ schema, accountability });
|
|
99
|
+
if (!event?.action) {
|
|
100
|
+
return await service.readAll();
|
|
101
|
+
}
|
|
102
|
+
else if (event.action === 'delete') {
|
|
103
|
+
return event.keys;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return await service.readOne(event.payload?.['collection'], event.payload?.['field']);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Get items from a collection using the appropriate service
|
|
111
|
+
*
|
|
112
|
+
* @param subscription Subscription object
|
|
113
|
+
* @param accountability Accountability object
|
|
114
|
+
* @param schema Schema object
|
|
115
|
+
* @param event Event data
|
|
116
|
+
* @returns the fetched data
|
|
117
|
+
*/
|
|
118
|
+
export async function getItemsPayload(subscription, accountability, schema, event) {
|
|
119
|
+
const query = subscription.query ?? {};
|
|
120
|
+
const service = getService(subscription.collection, { schema, accountability });
|
|
121
|
+
if (!event?.action) {
|
|
122
|
+
return await service.readByQuery(query);
|
|
123
|
+
}
|
|
124
|
+
else if (event.action === 'create') {
|
|
125
|
+
return await service.readMany([event.key], query);
|
|
126
|
+
}
|
|
127
|
+
else if (event.action === 'delete') {
|
|
128
|
+
return event.keys;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return await service.readMany(event.keys, query);
|
|
132
|
+
}
|
|
133
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@directus/api",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.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",
|
|
@@ -143,21 +143,22 @@
|
|
|
143
143
|
"ws": "8.12.1",
|
|
144
144
|
"zod": "3.21.4",
|
|
145
145
|
"zod-validation-error": "1.0.1",
|
|
146
|
-
"@directus/app": "10.
|
|
147
|
-
"@directus/constants": "10.2.
|
|
148
|
-
"@directus/errors": "0.0.
|
|
149
|
-
"@directus/extensions-sdk": "10.1.
|
|
150
|
-
"@directus/pressure": "1.0.
|
|
151
|
-
"@directus/schema": "10.0.
|
|
146
|
+
"@directus/app": "10.6.0",
|
|
147
|
+
"@directus/constants": "10.2.2",
|
|
148
|
+
"@directus/errors": "0.0.2",
|
|
149
|
+
"@directus/extensions-sdk": "10.1.7",
|
|
150
|
+
"@directus/pressure": "1.0.7",
|
|
151
|
+
"@directus/schema": "10.0.2",
|
|
152
152
|
"@directus/specs": "10.1.1",
|
|
153
|
-
"@directus/storage": "10.0.
|
|
154
|
-
"@directus/storage-driver-azure": "10.0.
|
|
155
|
-
"@directus/storage-driver-cloudinary": "10.0.
|
|
156
|
-
"@directus/storage-driver-gcs": "10.0.
|
|
157
|
-
"@directus/storage-driver-local": "10.0.
|
|
158
|
-
"@directus/storage-driver-s3": "10.0.
|
|
159
|
-
"@directus/
|
|
160
|
-
"@directus/
|
|
153
|
+
"@directus/storage": "10.0.5",
|
|
154
|
+
"@directus/storage-driver-azure": "10.0.8",
|
|
155
|
+
"@directus/storage-driver-cloudinary": "10.0.8",
|
|
156
|
+
"@directus/storage-driver-gcs": "10.0.8",
|
|
157
|
+
"@directus/storage-driver-local": "10.0.8",
|
|
158
|
+
"@directus/storage-driver-s3": "10.0.8",
|
|
159
|
+
"@directus/storage-driver-supabase": "1.0.0",
|
|
160
|
+
"@directus/utils": "10.0.8",
|
|
161
|
+
"@directus/validation": "0.0.3"
|
|
161
162
|
},
|
|
162
163
|
"devDependencies": {
|
|
163
164
|
"@ngneat/falso": "6.4.0",
|
|
@@ -204,9 +205,9 @@
|
|
|
204
205
|
"supertest": "6.3.3",
|
|
205
206
|
"typescript": "5.0.4",
|
|
206
207
|
"vitest": "0.31.1",
|
|
207
|
-
"@directus/random": "0.2.
|
|
208
|
-
"@directus/tsconfig": "0.0
|
|
209
|
-
"@directus/types": "10.1.
|
|
208
|
+
"@directus/random": "0.2.2",
|
|
209
|
+
"@directus/tsconfig": "1.0.0",
|
|
210
|
+
"@directus/types": "10.1.3"
|
|
210
211
|
},
|
|
211
212
|
"optionalDependencies": {
|
|
212
213
|
"@keyv/redis": "2.5.8",
|