@directus/api 12.0.2 → 12.1.0

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.
@@ -183,11 +183,11 @@ const handleError = (e) => {
183
183
  if (e instanceof errors.OPError) {
184
184
  if (e.error === 'invalid_grant') {
185
185
  // Invalid token
186
- logger.trace(e, `[OAuth2] Invalid grant`);
186
+ logger.warn(e, `[OAuth2] Invalid grant`);
187
187
  return new InvalidTokenError();
188
188
  }
189
189
  // Server response error
190
- logger.trace(e, `[OAuth2] Unknown OP error`);
190
+ logger.warn(e, `[OAuth2] Unknown OP error`);
191
191
  return new ServiceUnavailableError({
192
192
  service: 'oauth2',
193
193
  reason: `Service returned unexpected response: ${e.error_description}`,
@@ -195,10 +195,10 @@ const handleError = (e) => {
195
195
  }
196
196
  else if (e instanceof errors.RPError) {
197
197
  // Internal client error
198
- logger.trace(e, `[OAuth2] Unknown RP error`);
198
+ logger.warn(e, `[OAuth2] Unknown RP error`);
199
199
  return new InvalidCredentialsError();
200
200
  }
201
- logger.trace(e, `[OAuth2] Unknown error`);
201
+ logger.warn(e, `[OAuth2] Unknown error`);
202
202
  return e;
203
203
  };
204
204
  export function createOAuth2AuthRouter(providerName) {
@@ -205,11 +205,11 @@ const handleError = (e) => {
205
205
  if (e instanceof errors.OPError) {
206
206
  if (e.error === 'invalid_grant') {
207
207
  // Invalid token
208
- logger.trace(e, `[OpenID] Invalid grant`);
208
+ logger.warn(e, `[OpenID] Invalid grant`);
209
209
  return new InvalidTokenError();
210
210
  }
211
211
  // Server response error
212
- logger.trace(e, `[OpenID] Unknown OP error`);
212
+ logger.warn(e, `[OpenID] Unknown OP error`);
213
213
  return new ServiceUnavailableError({
214
214
  service: 'openid',
215
215
  reason: `Service returned unexpected response: ${e.error_description}`,
@@ -217,10 +217,10 @@ const handleError = (e) => {
217
217
  }
218
218
  else if (e instanceof errors.RPError) {
219
219
  // Internal client error
220
- logger.trace(e, `[OpenID] Unknown RP error`);
220
+ logger.warn(e, `[OpenID] Unknown RP error`);
221
221
  return new InvalidCredentialsError();
222
222
  }
223
- logger.trace(e, `[OpenID] Unknown error`);
223
+ logger.warn(e, `[OpenID] Unknown error`);
224
224
  return e;
225
225
  };
226
226
  export function createOpenIDAuthRouter(providerName) {
@@ -45,7 +45,7 @@ export class SAMLAuthDriver extends LocalAuthDriver {
45
45
  if (userID)
46
46
  return userID;
47
47
  if (!allowPublicRegistration) {
48
- logger.trace(`[SAML] User doesn't exist, and public registration not allowed for provider "${provider}"`);
48
+ logger.warn(`[SAML] User doesn't exist, and public registration not allowed for provider "${provider}"`);
49
49
  throw new InvalidCredentialsError();
50
50
  }
51
51
  const firstName = payload[givenNameKey ?? 'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname'];
@@ -0,0 +1,3 @@
1
+ import type { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -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
+ }
@@ -35,7 +35,7 @@ fields:
35
35
  iconRight: lock
36
36
  masked: true
37
37
  width: half
38
- note: $t:shared_leave_blank_for_unlimited
38
+ note: $t:shared_leave_blank_for_passwordless_access
39
39
 
40
40
  - field: date_start
41
41
  width: half
@@ -12,20 +12,7 @@ const errorHandler = (err, req, res, _next) => {
12
12
  errors: [],
13
13
  };
14
14
  const errors = toArray(err);
15
- if (errors.some((err) => isDirectusError(err) === false)) {
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
- res.status(err.status);
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
- res.status(500);
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,6 +3,8 @@ type Options = {
3
3
  subject: string;
4
4
  message?: unknown | null;
5
5
  permissions: string;
6
+ collection?: string;
7
+ item?: string;
6
8
  };
7
9
  declare const _default: import("@directus/types").OperationApiConfig<Options>;
8
10
  export default _default;
@@ -3,7 +3,7 @@ import { NotificationsService } from '../../services/notifications.js';
3
3
  import { getAccountabilityForRole } from '../../utils/get-accountability-for-role.js';
4
4
  export default defineOperationApi({
5
5
  id: 'notification',
6
- handler: async ({ recipient, subject, message, permissions }, { accountability, database, getSchema }) => {
6
+ handler: async ({ recipient, subject, message, permissions, collection, item }, { accountability, database, getSchema }) => {
7
7
  const schema = await getSchema({ database });
8
8
  let customAccountability;
9
9
  if (!permissions || permissions === '$trigger') {
@@ -24,12 +24,16 @@ export default defineOperationApi({
24
24
  knex: database,
25
25
  });
26
26
  const messageString = message ? optionToString(message) : null;
27
+ const collectionString = message ? optionToString(collection) : null;
28
+ const itemString = message ? optionToString(item) : null;
27
29
  const payload = toArray(recipient).map((userId) => {
28
30
  return {
29
31
  recipient: userId,
30
32
  sender: customAccountability?.user ?? null,
31
33
  subject,
32
34
  message: messageString,
35
+ collection: collectionString,
36
+ item: itemString,
33
37
  };
34
38
  });
35
39
  const result = await notificationsService.createMany(payload);
@@ -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: any;
7
- data: import("../../types/items.js").Item;
8
- event: string;
6
+ key: string | number;
7
+ data: null;
8
+ event: "delete";
9
9
  };
10
10
  } | {
11
11
  [x: string]: {
12
- key: any;
13
- data: null;
14
- event: string;
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 { ItemsService } from '../items.js';
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
- if (eventData['action'] === 'create') {
23
- const { collection, key } = eventData;
24
- const service = new ItemsService(collection, { schema });
25
- const data = await service.readOne(key, { fields });
26
- yield { [event]: { key, data, event: 'create' } };
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'] === 'update') {
29
- const { collection, keys } = eventData;
30
- const service = new ItemsService(collection, { schema });
31
- for (const key of keys) {
32
- const data = await service.readOne(key, { fields });
33
- yield { [event]: { key, data, event: 'update' } };
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'] === 'delete') {
37
- const { keys } = eventData;
38
- for (const key of keys) {
39
- yield { [event]: { key, data: null, event: 'delete' } };
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: #6644ff;
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: #6644ff !important;
77
+ color: {{projectColor}} !important;
78
78
  }
79
79
  .button {
80
80
  background-color:#0BA582 !important;
@@ -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).count('*', { as: 'count' });
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].count);
76
+ return Number(records[0]['count']);
69
77
  }
70
78
  }
@@ -2,6 +2,8 @@ import getDatabase from '../database/index.js';
2
2
  import { systemCollectionRows } from '../database/system-data/collections/index.js';
3
3
  import emitter from '../emitter.js';
4
4
  import { ForbiddenError, InvalidPayloadError } from '../errors/index.js';
5
+ import { getCache } from '../cache.js';
6
+ import { shouldClearCache } from '../utils/should-clear-cache.js';
5
7
  export class UtilsService {
6
8
  knex;
7
9
  accountability;
@@ -95,6 +97,11 @@ export class UtilsService {
95
97
  .andWhere(sortField, '<=', sourceSortValue)
96
98
  .andWhereNot({ [primaryKeyField]: item });
97
99
  }
100
+ // check if cache should be cleared
101
+ const { cache } = getCache();
102
+ if (shouldClearCache(cache, undefined, collection)) {
103
+ await cache.clear();
104
+ }
98
105
  emitter.emitAction(['items.sort', `${collection}.items.sort`], {
99
106
  collection,
100
107
  item,
@@ -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',
@@ -8,11 +8,13 @@ import { getEnv } from '../env.js';
8
8
  */
9
9
  export function shouldClearCache(cache, opts, collection) {
10
10
  const env = getEnv();
11
- if (collection && env['CACHE_AUTO_PURGE_IGNORE_LIST'].includes(collection)) {
12
- return false;
13
- }
14
- if (cache && env['CACHE_AUTO_PURGE'] && opts?.autoPurgeCache !== false) {
15
- return true;
11
+ if (env['CACHE_AUTO_PURGE']) {
12
+ if (collection && env['CACHE_AUTO_PURGE_IGNORE_LIST'].includes(collection)) {
13
+ return false;
14
+ }
15
+ if (cache && opts?.autoPurgeCache !== false) {
16
+ return true;
17
+ }
16
18
  }
17
19
  return false;
18
20
  }
@@ -5,6 +5,7 @@ export declare class Url {
5
5
  path: string[];
6
6
  query: Record<string, string>;
7
7
  hash: string | null;
8
+ hasTrailingSlash: boolean;
8
9
  constructor(url: string);
9
10
  isAbsolute(): boolean;
10
11
  isProtocolRelative(): boolean;
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;
@@ -52,8 +54,9 @@ export class Url {
52
54
  const port = this.port !== null ? `:${this.port}` : '';
53
55
  const origin = `${this.host !== null ? `${protocol}//` : ''}${host}${port}`;
54
56
  const path = this.path.length ? `/${this.path.join('/')}` : '';
57
+ const trailingSlash = this.hasTrailingSlash ? '/' : '';
55
58
  const query = Object.keys(this.query).length !== 0 ? `?${new URLSearchParams(this.query).toString()}` : '';
56
59
  const hash = this.hash !== null ? `#${this.hash}` : '';
57
- return `${!rootRelative ? origin : ''}${path}${query}${hash}`;
60
+ return `${!rootRelative ? origin : ''}${path}${trailingSlash}${query}${hash}`;
58
61
  }
59
62
  }
@@ -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 this.getSinglePayload(subscription, client.accountability, schema, event)
112
- : await this.getMultiPayload(subscription, client.accountability, schema, event);
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 this.getSinglePayload(subscription, accountability, schema)
156
- : await this.getMultiPayload(subscription, accountability, schema);
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.0.2",
3
+ "version": "12.1.0",
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.4.2",
147
- "@directus/constants": "10.2.1",
148
- "@directus/errors": "0.0.1",
149
- "@directus/extensions-sdk": "10.1.5",
150
- "@directus/pressure": "1.0.5",
151
- "@directus/schema": "10.0.1",
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.4",
154
- "@directus/storage-driver-azure": "10.0.6",
155
- "@directus/storage-driver-cloudinary": "10.0.6",
156
- "@directus/storage-driver-gcs": "10.0.6",
157
- "@directus/storage-driver-local": "10.0.6",
158
- "@directus/storage-driver-s3": "10.0.6",
159
- "@directus/utils": "10.0.6",
160
- "@directus/validation": "0.0.1"
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.1",
208
- "@directus/tsconfig": "0.0.7",
209
- "@directus/types": "10.1.2"
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",