@directus/api 20.0.0 → 20.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.
@@ -72,5 +72,6 @@ async function createDefaultAdmin(schema) {
72
72
  adminPassword = nanoid(12);
73
73
  logger.info(`No admin password provided. Defaulting to "${adminPassword}"`);
74
74
  }
75
- await usersService.createOne({ email: adminEmail, password: adminPassword, role, ...defaultAdminUser });
75
+ const token = env['ADMIN_TOKEN'] ?? null;
76
+ await usersService.createOne({ email: adminEmail, password: adminPassword, token, role, ...defaultAdminUser });
76
77
  }
@@ -210,10 +210,18 @@ async function getDBQuery(schema, knex, table, fieldNodes, query) {
210
210
  }
211
211
  innerQuerySortRecords.push({ alias: sortAlias, order: sortRecord.order });
212
212
  });
213
- dbQuery.orderByRaw(orderByString, orderByFields);
214
213
  if (hasMultiRelationalSort) {
215
214
  dbQuery = helpers.schema.applyMultiRelationalSort(knex, dbQuery, table, primaryKey, orderByString, orderByFields);
215
+ // Start order by with directus_row_number. The directus_row_number is derived from a window function that
216
+ // is ordered by the sort fields within every primary key partition. That ensures that the result with the
217
+ // row number = 1 is the top-most row of every partition, according to the selected sort fields.
218
+ // Since the only relevant result is the first row of this partition, adding the directus_row_number to the
219
+ // order by here ensures that all rows with a directus_row_number = 1 show up first in the inner query result,
220
+ // and are correctly truncated by the limit, but not earlier.
221
+ orderByString = `?? asc, ${orderByString}`;
222
+ orderByFields.unshift(knex.ref('directus_row_number'));
216
223
  }
224
+ dbQuery.orderByRaw(orderByString, orderByFields);
217
225
  }
218
226
  else {
219
227
  sortRecords.map((sortRecord) => {
@@ -236,7 +244,7 @@ async function getDBQuery(schema, knex, table, fieldNodes, query) {
236
244
  .select(fieldNodes.map(preProcess))
237
245
  .from(table)
238
246
  .innerJoin(knex.raw('??', dbQuery.as('inner')), `${table}.${primaryKey}`, `inner.${primaryKey}`);
239
- if (sortRecords && needsInnerQuery) {
247
+ if (sortRecords) {
240
248
  innerQuerySortRecords.map((innerQuerySortRecord) => {
241
249
  wrapperQuery.orderBy(`inner.${innerQuerySortRecord.alias}`, innerQuerySortRecord.order);
242
250
  });
@@ -9,6 +9,7 @@ import { PassThrough as PassThroughStream, Transform as TransformStream } from '
9
9
  import zlib from 'node:zlib';
10
10
  import path from 'path';
11
11
  import url from 'url';
12
+ import { RESUMABLE_UPLOADS } from '../constants.js';
12
13
  import emitter from '../emitter.js';
13
14
  import { useLogger } from '../logger.js';
14
15
  import { getAxios } from '../request/index.js';
@@ -215,17 +216,19 @@ export class FilesService extends ItemsService {
215
216
  }
216
217
  async readByQuery(query, opts) {
217
218
  const filteredQuery = cloneDeep(query);
218
- const filterPartialUploads = { tus_id: { _null: true } };
219
- if (!filteredQuery.filter) {
220
- filteredQuery.filter = filterPartialUploads;
221
- }
222
- else if ('_and' in filteredQuery.filter && Array.isArray(filteredQuery.filter['_and'])) {
223
- filteredQuery.filter['_and'].push(filterPartialUploads);
224
- }
225
- else {
226
- filteredQuery.filter = {
227
- _and: [filteredQuery.filter, filterPartialUploads],
228
- };
219
+ if (RESUMABLE_UPLOADS.ENABLED === true) {
220
+ const filterPartialUploads = { tus_id: { _null: true } };
221
+ if (!filteredQuery.filter) {
222
+ filteredQuery.filter = filterPartialUploads;
223
+ }
224
+ else if ('_and' in filteredQuery.filter && Array.isArray(filteredQuery.filter['_and'])) {
225
+ filteredQuery.filter['_and'].push(filterPartialUploads);
226
+ }
227
+ else {
228
+ filteredQuery.filter = {
229
+ _and: [filteredQuery.filter, filterPartialUploads],
230
+ };
231
+ }
229
232
  }
230
233
  return super.readByQuery(filteredQuery, opts);
231
234
  }
@@ -11,14 +11,14 @@ export type MutationTracker = {
11
11
  trackMutations: (count: number) => void;
12
12
  getCount: () => number;
13
13
  };
14
- export declare class ItemsService<Item extends AnyItem = AnyItem> implements AbstractService {
15
- collection: string;
14
+ export declare class ItemsService<Item extends AnyItem = AnyItem, Collection extends string = string> implements AbstractService {
15
+ collection: Collection;
16
16
  knex: Knex;
17
17
  accountability: Accountability | null;
18
18
  eventScope: string;
19
19
  schema: SchemaOverview;
20
20
  cache: Keyv<any> | null;
21
- constructor(collection: string, options: AbstractServiceOptions);
21
+ constructor(collection: Collection, options: AbstractServiceOptions);
22
22
  /**
23
23
  * Create a fork of the current service, allowing instantiation with different options.
24
24
  */
@@ -109,6 +109,7 @@ export class TusDataStore extends DataStore {
109
109
  }
110
110
  }
111
111
  async write(readable, tus_id, offset) {
112
+ const logger = useLogger();
112
113
  const fileData = await this.getFileById(tus_id);
113
114
  const filePath = fileData.filename_disk;
114
115
  const sudoService = new ItemsService('directus_files', {
@@ -146,6 +147,7 @@ export class TusDataStore extends DataStore {
146
147
  return newOffset;
147
148
  }
148
149
  catch (err) {
150
+ logger.error(err, 'Error writing chunk for upload "%s" at offset %d', tus_id, offset);
149
151
  if ('status_code' in err && err.status_code === 500) {
150
152
  throw err;
151
153
  }
@@ -32,7 +32,7 @@ export default abstract class SocketController {
32
32
  protected getRateLimiter(): RateLimiterAbstract | null;
33
33
  private catchInvalidMessages;
34
34
  protected handleUpgrade(request: IncomingMessage, socket: internal.Duplex, head: Buffer): Promise<void>;
35
- protected handleTokenUpgrade({ request, socket, head }: UpgradeContext, token: string): Promise<void>;
35
+ protected handleTokenUpgrade({ request, socket, head }: UpgradeContext, token: string | null): Promise<void>;
36
36
  protected handleHandshakeUpgrade({ request, socket, head }: UpgradeContext): Promise<void>;
37
37
  createClient(ws: WebSocket, { accountability, expires_at }: AuthenticationState): WebSocketClient;
38
38
  protected parseMessage(data: string): WebSocketMessage;
@@ -101,13 +101,14 @@ export default class SocketController {
101
101
  const cookies = request.headers.cookie ? cookie.parse(request.headers.cookie) : {};
102
102
  const context = { request, socket, head };
103
103
  const sessionCookieName = env['SESSION_COOKIE_NAME'];
104
- if (cookies[sessionCookieName]) {
105
- const token = cookies[sessionCookieName];
106
- await this.handleTokenUpgrade(context, token);
107
- return;
108
- }
109
- if (this.authentication.mode === 'strict') {
110
- const token = query['access_token'];
104
+ if (this.authentication.mode === 'strict' || query['access_token'] || cookies[sessionCookieName]) {
105
+ let token = null;
106
+ if (typeof query['access_token'] === 'string') {
107
+ token = query['access_token'];
108
+ }
109
+ else if (typeof cookies[sessionCookieName] === 'string') {
110
+ token = cookies[sessionCookieName] ?? null;
111
+ }
111
112
  await this.handleTokenUpgrade(context, token);
112
113
  return;
113
114
  }
@@ -122,16 +123,19 @@ export default class SocketController {
122
123
  });
123
124
  }
124
125
  async handleTokenUpgrade({ request, socket, head }, token) {
125
- let accountability, expires_at;
126
- try {
127
- accountability = await getAccountabilityForToken(token);
128
- expires_at = getExpiresAtForToken(token);
129
- }
130
- catch {
131
- accountability = null;
132
- expires_at = null;
126
+ let accountability = null;
127
+ let expires_at = null;
128
+ if (token) {
129
+ try {
130
+ accountability = await getAccountabilityForToken(token);
131
+ expires_at = getExpiresAtForToken(token);
132
+ }
133
+ catch {
134
+ accountability = null;
135
+ expires_at = null;
136
+ }
133
137
  }
134
- if (!accountability || !accountability.user) {
138
+ if (!token || !accountability || !accountability.user) {
135
139
  logger.debug('WebSocket upgrade denied - ' + JSON.stringify(accountability || 'invalid'));
136
140
  socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
137
141
  socket.destroy();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "20.0.0",
3
+ "version": "20.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",
@@ -99,7 +99,7 @@
99
99
  "graphql-ws": "5.16.0",
100
100
  "helmet": "7.1.0",
101
101
  "icc": "3.0.0",
102
- "inquirer": "9.2.23",
102
+ "inquirer": "9.3.2",
103
103
  "ioredis": "5.4.1",
104
104
  "ip-matching": "2.1.2",
105
105
  "isolated-vm": "4.7.2",
@@ -135,7 +135,7 @@
135
135
  "pino-http": "9.0.0",
136
136
  "pino-http-print": "3.1.0",
137
137
  "pino-pretty": "11.2.1",
138
- "qs": "6.12.1",
138
+ "qs": "6.12.2",
139
139
  "rate-limiter-flexible": "5.0.3",
140
140
  "rollup": "4.17.2",
141
141
  "samlify": "2.8.10",
@@ -146,32 +146,32 @@
146
146
  "tar": "7.4.0",
147
147
  "tsx": "4.12.0",
148
148
  "wellknown": "0.5.0",
149
- "ws": "8.17.1",
149
+ "ws": "8.18.0",
150
150
  "zod": "3.23.8",
151
151
  "zod-validation-error": "3.3.0",
152
- "@directus/app": "12.2.0",
152
+ "@directus/app": "12.2.1",
153
153
  "@directus/constants": "11.0.4",
154
+ "@directus/env": "1.3.0",
154
155
  "@directus/extensions": "1.0.9",
155
- "@directus/errors": "0.3.3",
156
- "@directus/env": "1.2.0",
157
156
  "@directus/extensions-registry": "1.0.9",
158
- "@directus/extensions-sdk": "11.0.9",
159
157
  "@directus/format-title": "10.1.2",
158
+ "@directus/errors": "0.3.3",
160
159
  "@directus/memory": "1.0.10",
160
+ "@directus/extensions-sdk": "11.0.9",
161
161
  "@directus/pressure": "1.0.21",
162
162
  "@directus/schema": "11.0.3",
163
163
  "@directus/specs": "10.2.10",
164
164
  "@directus/storage-driver-azure": "10.0.23",
165
+ "@directus/storage": "10.1.0",
165
166
  "@directus/storage-driver-cloudinary": "10.0.23",
166
167
  "@directus/storage-driver-gcs": "10.0.24",
167
- "@directus/storage": "10.1.0",
168
168
  "@directus/storage-driver-local": "10.1.0",
169
169
  "@directus/storage-driver-s3": "10.1.0",
170
- "@directus/system-data": "1.1.0",
171
170
  "@directus/storage-driver-supabase": "1.0.15",
171
+ "@directus/system-data": "1.1.0",
172
172
  "@directus/utils": "11.0.10",
173
173
  "@directus/validation": "0.0.18",
174
- "directus": "10.13.0"
174
+ "directus": "10.13.1"
175
175
  },
176
176
  "devDependencies": {
177
177
  "@ngneat/falso": "7.2.0",
@@ -185,7 +185,7 @@
185
185
  "@types/destroy": "1.0.3",
186
186
  "@types/encodeurl": "1.0.2",
187
187
  "@types/express": "4.17.21",
188
- "@types/express-serve-static-core": "4.19.3",
188
+ "@types/express-serve-static-core": "4.19.5",
189
189
  "@types/fs-extra": "11.0.4",
190
190
  "@types/glob-to-regexp": "0.4.4",
191
191
  "@types/inquirer": "9.0.7",
@@ -212,12 +212,12 @@
212
212
  "knex-mock-client": "2.0.1",
213
213
  "typescript": "5.4.5",
214
214
  "vitest": "1.5.3",
215
- "@directus/random": "0.2.8",
216
215
  "@directus/tsconfig": "1.0.1",
216
+ "@directus/random": "0.2.8",
217
217
  "@directus/types": "11.2.0"
218
218
  },
219
219
  "optionalDependencies": {
220
- "@keyv/redis": "2.8.4",
220
+ "@keyv/redis": "2.8.5",
221
221
  "mysql": "2.18.1",
222
222
  "nodemailer-mailgun-transport": "2.1.5",
223
223
  "nodemailer-sendgrid": "1.0.3",