@directus/api 32.0.1 → 32.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.
@@ -1,42 +1,48 @@
1
1
  import { getRelation } from '@directus/utils';
2
2
  import { getRelationType } from '../../../utils/get-relation-type.js';
3
- export function filterReplaceM2A(filter_arg, collection, schema) {
3
+ export function filterReplaceM2A(filter_arg, collection, schema, options) {
4
4
  const filter = filter_arg;
5
5
  for (const key in filter) {
6
- const [field, any_collection] = key.split('__');
6
+ const parts = key.split('__');
7
+ let field = parts[0];
8
+ const any_collection = parts[1];
7
9
  if (!field)
8
10
  continue;
11
+ field = options?.aliasMap?.[field] ?? field;
9
12
  const relation = getRelation(schema.relations, collection, field);
10
13
  const type = relation ? getRelationType({ relation, collection, field }) : null;
11
14
  if (type === 'o2m' && relation) {
12
- filter[key] = filterReplaceM2A(filter[key], relation.collection, schema);
15
+ filter[key] = filterReplaceM2A(filter[key], relation.collection, schema, options);
13
16
  }
14
17
  else if (type === 'm2o' && relation) {
15
- filter[key] = filterReplaceM2A(filter[key], relation.related_collection, schema);
18
+ filter[key] = filterReplaceM2A(filter[key], relation.related_collection, schema, options);
16
19
  }
17
20
  else if (type === 'a2o' &&
18
21
  relation &&
19
22
  any_collection &&
20
23
  relation.meta?.one_allowed_collections?.includes(any_collection)) {
21
- filter[`${field}:${any_collection}`] = filterReplaceM2A(filter[key], any_collection, schema);
24
+ filter[`${field}:${any_collection}`] = filterReplaceM2A(filter[key], any_collection, schema, options);
22
25
  delete filter[key];
23
26
  }
24
27
  else if (Array.isArray(filter[key])) {
25
- filter[key] = filter[key].map((item) => filterReplaceM2A(item, collection, schema));
28
+ filter[key] = filter[key].map((item) => filterReplaceM2A(item, collection, schema, options));
26
29
  }
27
30
  else if (typeof filter[key] === 'object') {
28
- filter[key] = filterReplaceM2A(filter[key], collection, schema);
31
+ filter[key] = filterReplaceM2A(filter[key], collection, schema, options);
29
32
  }
30
33
  }
31
34
  return filter;
32
35
  }
33
- export function filterReplaceM2ADeep(deep_arg, collection, schema) {
36
+ export function filterReplaceM2ADeep(deep_arg, collection, schema, options) {
34
37
  const deep = deep_arg;
35
38
  for (const key in deep) {
36
39
  if (key.startsWith('_') === false) {
37
- const [field, any_collection] = key.split('__');
40
+ const parts = key.split('__');
41
+ let field = parts[0];
42
+ const any_collection = parts[1];
38
43
  if (!field)
39
44
  continue;
45
+ field = options?.aliasMap?.[field] || deep._alias?.[field] || field;
40
46
  const relation = getRelation(schema.relations, collection, field);
41
47
  if (!relation)
42
48
  continue;
@@ -443,6 +443,7 @@ export class ExportService {
443
443
  throw new Error('Failed to create temporary file for export');
444
444
  const mimeTypes = {
445
445
  csv: 'text/csv',
446
+ csv_utf8: 'text/csv; charset=utf-8',
446
447
  json: 'application/json',
447
448
  xml: 'text/xml',
448
449
  yaml: 'text/yaml',
@@ -485,7 +486,7 @@ export class ExportService {
485
486
  readCount += result.length;
486
487
  if (result.length) {
487
488
  let csvHeadings = null;
488
- if (format === 'csv') {
489
+ if (format.startsWith('csv')) {
489
490
  if (!query.fields)
490
491
  query.fields = ['*'];
491
492
  // to ensure the all headings are included in the CSV file, all possible fields need to be determined.
@@ -593,14 +594,15 @@ Your export of ${collection} is ready. <a href="${href}">Click here to view.</a>
593
594
  }
594
595
  return string;
595
596
  }
596
- if (format === 'csv') {
597
+ if (format.startsWith('csv')) {
597
598
  if (input.length === 0)
598
599
  return '';
599
600
  const transforms = [CSVTransforms.flatten({ separator: '.' })];
600
601
  const header = options?.includeHeader !== false;
602
+ const withBOM = format === 'csv_utf8';
601
603
  const transformOptions = options?.fields
602
- ? { transforms, header, fields: options?.fields }
603
- : { transforms, header };
604
+ ? { transforms, header, fields: options?.fields, withBOM }
605
+ : { transforms, header, withBOM };
604
606
  let string = new CSVParser(transformOptions).parse(input);
605
607
  if (options?.includeHeader === false) {
606
608
  string = '\n' + string;
@@ -7,13 +7,26 @@ export type EmailOptions = SendMailOptions & {
7
7
  data: Record<string, any>;
8
8
  };
9
9
  };
10
+ export type DefaultTemplateData = {
11
+ projectName: string;
12
+ projectColor: string;
13
+ projectLogo: string;
14
+ projectUrl: string;
15
+ };
10
16
  export declare class MailService {
11
17
  schema: SchemaOverview;
12
18
  accountability: Accountability | null;
13
19
  knex: Knex;
14
20
  mailer: Transporter;
15
21
  constructor(opts: AbstractServiceOptions);
16
- send<T>(options: EmailOptions): Promise<T | null>;
22
+ send<T>(data: EmailOptions, options?: {
23
+ defaultTemplateData: DefaultTemplateData;
24
+ }): Promise<T | null>;
17
25
  private renderTemplate;
18
- private getDefaultTemplateData;
26
+ getDefaultTemplateData(): Promise<{
27
+ projectName: any;
28
+ projectColor: any;
29
+ projectLogo: string;
30
+ projectUrl: any;
31
+ }>;
19
32
  }
@@ -37,14 +37,15 @@ export class MailService {
37
37
  });
38
38
  }
39
39
  }
40
- async send(options) {
40
+ async send(data, options) {
41
41
  await useEmailRateLimiterQueue();
42
- const payload = await emitter.emitFilter(`email.send`, options, {});
42
+ const payload = await emitter.emitFilter(`email.send`, data, {});
43
43
  if (!payload)
44
44
  return null;
45
45
  const { template, ...emailOptions } = payload;
46
- let { html } = options;
47
- const defaultTemplateData = await this.getDefaultTemplateData();
46
+ let { html } = data;
47
+ // option for providing tempalate data was added to prevent transaction race conditions with preceding promises
48
+ const defaultTemplateData = options?.defaultTemplateData ?? (await this.getDefaultTemplateData());
48
49
  if (isObject(emailOptions.from) && (!emailOptions.from.name || !emailOptions.from.address)) {
49
50
  throw new InvalidPayloadError({ reason: 'A name and address property are required in the "from" object' });
50
51
  }
@@ -48,6 +48,8 @@ export class NotificationsService extends ItemsService {
48
48
  },
49
49
  to: user['email'],
50
50
  subject: data.subject,
51
+ }, {
52
+ defaultTemplateData: await mailService.getDefaultTemplateData(),
51
53
  })
52
54
  .catch((error) => {
53
55
  logger.error(error, `Could not send notification via mail`);
@@ -1,7 +1,7 @@
1
1
  import type { TusDriver } from '@directus/storage';
2
2
  import type { Accountability, File, SchemaOverview } from '@directus/types';
3
- import stream from 'node:stream';
4
3
  import { DataStore, Upload } from '@tus/utils';
4
+ import stream from 'node:stream';
5
5
  export type TusDataStoreConfig = {
6
6
  constants: {
7
7
  ENABLED: boolean;
@@ -1,12 +1,12 @@
1
1
  import formatTitle from '@directus/format-title';
2
+ import { DataStore, ERRORS, Upload } from '@tus/utils';
3
+ import { omit } from 'lodash-es';
2
4
  import { extension } from 'mime-types';
3
5
  import { extname } from 'node:path';
4
6
  import stream from 'node:stream';
5
- import { DataStore, ERRORS, Upload } from '@tus/utils';
6
- import { ItemsService } from '../items.js';
7
- import { useLogger } from '../../logger/index.js';
8
7
  import getDatabase from '../../database/index.js';
9
- import { omit } from 'lodash-es';
8
+ import { useLogger } from '../../logger/index.js';
9
+ import { ItemsService } from '../items.js';
10
10
  export class TusDataStore extends DataStore {
11
11
  chunkSize;
12
12
  maxSize;
@@ -66,7 +66,7 @@ export class TusDataStore extends DataStore {
66
66
  upload.metadata['replace_id'] = upload.metadata['id'];
67
67
  }
68
68
  const fileData = {
69
- ...omit(upload.metadata, ['id']),
69
+ ...omit(upload.metadata, ['id', 'replace_id']),
70
70
  tus_id: upload.id,
71
71
  tus_data: upload,
72
72
  filesize: upload.size,
@@ -20,7 +20,7 @@ const querySchema = Joi.object({
20
20
  page: Joi.number().integer().min(0),
21
21
  meta: Joi.array().items(Joi.string().valid('total_count', 'filter_count')),
22
22
  search: Joi.string(),
23
- export: Joi.string().valid('csv', 'json', 'xml', 'yaml'),
23
+ export: Joi.string().valid('csv', 'csv_utf8', 'json', 'xml', 'yaml'),
24
24
  version: Joi.string(),
25
25
  versionRaw: Joi.boolean(),
26
26
  aggregate: Joi.object(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "32.0.1",
3
+ "version": "32.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",
@@ -59,9 +59,9 @@
59
59
  ],
60
60
  "dependencies": {
61
61
  "@authenio/samlify-node-xmllint": "2.0.0",
62
- "@aws-sdk/client-sesv2": "3.918.0",
62
+ "@aws-sdk/client-sesv2": "3.928.0",
63
63
  "@godaddy/terminus": "4.12.1",
64
- "@modelcontextprotocol/sdk": "1.20.2",
64
+ "@modelcontextprotocol/sdk": "1.21.1",
65
65
  "@rollup/plugin-alias": "5.1.1",
66
66
  "@rollup/plugin-node-resolve": "16.0.3",
67
67
  "@rollup/plugin-virtual": "3.0.2",
@@ -93,17 +93,17 @@
93
93
  "flat": "6.0.1",
94
94
  "fs-extra": "11.3.2",
95
95
  "glob-to-regexp": "0.4.1",
96
- "graphql": "16.11.0",
96
+ "graphql": "16.12.0",
97
97
  "graphql-compose": "9.1.0",
98
98
  "graphql-ws": "6.0.6",
99
99
  "helmet": "8.1.0",
100
100
  "icc": "3.0.0",
101
- "inquirer": "12.10.0",
101
+ "inquirer": "12.11.0",
102
102
  "ioredis": "5.8.2",
103
103
  "ip-matching": "2.1.2",
104
104
  "isolated-vm": "5.0.3",
105
105
  "joi": "18.0.1",
106
- "js-yaml": "4.1.0",
106
+ "js-yaml": "4.1.1",
107
107
  "js2xmlparser": "5.0.0",
108
108
  "json2csv": "5.0.7",
109
109
  "jsonwebtoken": "9.0.2",
@@ -120,7 +120,7 @@
120
120
  "ms": "2.1.3",
121
121
  "nanoid": "5.1.6",
122
122
  "node-machine-id": "1.1.12",
123
- "cron": "4.3.3",
123
+ "cron": "4.3.4",
124
124
  "nodemailer": "7.0.10",
125
125
  "object-hash": "3.0.0",
126
126
  "openapi3-ts": "4.5.0",
@@ -143,7 +143,7 @@
143
143
  "rollup": "4.52.5",
144
144
  "samlify": "2.10.1",
145
145
  "sanitize-html": "2.17.0",
146
- "sharp": "0.34.4",
146
+ "sharp": "0.34.5",
147
147
  "snappy": "7.3.3",
148
148
  "stream-json": "1.9.1",
149
149
  "tar": "7.5.2",
@@ -153,30 +153,30 @@
153
153
  "ws": "8.18.3",
154
154
  "zod": "4.1.12",
155
155
  "zod-validation-error": "4.0.2",
156
+ "@directus/app": "14.2.0",
157
+ "@directus/env": "5.3.2",
156
158
  "@directus/constants": "14.0.0",
157
- "@directus/app": "14.1.1",
158
159
  "@directus/errors": "2.0.5",
159
- "@directus/env": "5.3.1",
160
- "@directus/extensions": "3.0.13",
161
- "@directus/extensions-registry": "3.0.13",
162
- "@directus/extensions-sdk": "17.0.1",
160
+ "@directus/extensions-registry": "3.0.14",
161
+ "@directus/extensions-sdk": "17.0.3",
163
162
  "@directus/format-title": "12.1.1",
164
- "@directus/memory": "3.0.11",
165
- "@directus/pressure": "3.0.11",
166
- "@directus/schema-builder": "0.0.8",
163
+ "@directus/extensions": "3.0.14",
164
+ "@directus/pressure": "3.0.12",
167
165
  "@directus/schema": "13.0.4",
168
- "@directus/specs": "11.1.1",
166
+ "@directus/memory": "3.0.12",
167
+ "@directus/schema-builder": "0.0.9",
169
168
  "@directus/storage": "12.0.3",
170
- "@directus/storage-driver-azure": "12.0.11",
171
- "@directus/storage-driver-cloudinary": "12.0.11",
172
- "@directus/storage-driver-gcs": "12.0.11",
169
+ "@directus/specs": "11.2.0",
170
+ "@directus/storage-driver-azure": "12.0.12",
171
+ "@directus/storage-driver-cloudinary": "12.0.12",
172
+ "@directus/storage-driver-gcs": "12.0.12",
173
+ "@directus/storage-driver-s3": "12.0.12",
173
174
  "@directus/storage-driver-local": "12.0.3",
174
- "@directus/storage-driver-s3": "12.0.11",
175
- "@directus/storage-driver-supabase": "3.0.11",
176
- "@directus/system-data": "3.4.1",
177
- "@directus/utils": "13.0.12",
178
- "@directus/validation": "2.0.11",
179
- "directus": "11.13.1"
175
+ "@directus/storage-driver-supabase": "3.0.12",
176
+ "@directus/system-data": "3.4.2",
177
+ "@directus/validation": "2.0.12",
178
+ "@directus/utils": "13.0.13",
179
+ "directus": "11.13.3"
180
180
  },
181
181
  "devDependencies": {
182
182
  "@directus/tsconfig": "3.0.0",
@@ -205,7 +205,7 @@
205
205
  "@types/node": "22.13.14",
206
206
  "@types/nodemailer": "7.0.3",
207
207
  "@types/object-hash": "3.0.6",
208
- "@types/papaparse": "5.3.16",
208
+ "@types/papaparse": "5.5.0",
209
209
  "@types/proxy-addr": "2.0.3",
210
210
  "@types/qs": "6.14.0",
211
211
  "@types/sanitize-html": "2.16.0",
@@ -219,8 +219,8 @@
219
219
  "knex-mock-client": "3.0.2",
220
220
  "typescript": "5.9.3",
221
221
  "vitest": "3.2.4",
222
- "@directus/schema-builder": "0.0.8",
223
- "@directus/types": "13.3.1"
222
+ "@directus/types": "13.4.0",
223
+ "@directus/schema-builder": "0.0.9"
224
224
  },
225
225
  "optionalDependencies": {
226
226
  "@keyv/redis": "3.0.1",
@@ -1,4 +0,0 @@
1
- /**
2
- * Checks if the defined redirect after successful SSO login is in the allow list
3
- */
4
- export declare function isLoginRedirectAllowed(redirect: unknown, provider: string): boolean;