directus 9.8.0 → 9.10.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.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/__mocks__/cache.d.ts +5 -0
  3. package/dist/__mocks__/cache.js +7 -0
  4. package/dist/app.js +3 -0
  5. package/dist/auth/drivers/ldap.js +10 -11
  6. package/dist/auth/drivers/oauth2.js +11 -6
  7. package/dist/auth/drivers/openid.js +9 -6
  8. package/dist/cli/commands/schema/apply.js +9 -3
  9. package/dist/controllers/assets.js +5 -0
  10. package/dist/controllers/files.d.ts +2 -0
  11. package/dist/controllers/files.js +13 -5
  12. package/dist/database/helpers/date/dialects/mssql.d.ts +4 -0
  13. package/dist/database/helpers/date/dialects/mssql.js +12 -0
  14. package/dist/database/helpers/date/dialects/mysql.d.ts +5 -0
  15. package/dist/database/helpers/date/dialects/mysql.js +16 -0
  16. package/dist/database/helpers/date/dialects/oracle.d.ts +4 -0
  17. package/dist/database/helpers/date/dialects/oracle.js +15 -0
  18. package/dist/database/helpers/date/dialects/sqlite.d.ts +1 -0
  19. package/dist/database/helpers/date/dialects/sqlite.js +8 -0
  20. package/dist/database/helpers/date/index.d.ts +3 -3
  21. package/dist/database/helpers/date/index.js +6 -6
  22. package/dist/database/helpers/date/types.d.ts +3 -0
  23. package/dist/database/helpers/date/types.js +10 -0
  24. package/dist/database/helpers/fn/dialects/postgres.js +5 -1
  25. package/dist/database/helpers/index.d.ts +1 -1
  26. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.d.ts +3 -0
  27. package/dist/database/migrations/20220402A-remove-default-value-panel-icon.js +22 -0
  28. package/dist/database/run-ast.js +3 -3
  29. package/dist/database/system-data/fields/collections.yaml +1 -1
  30. package/dist/database/system-data/fields/settings.yaml +0 -1
  31. package/dist/database/system-data/fields/users.yaml +3 -0
  32. package/dist/env.js +9 -3
  33. package/dist/exceptions/index.d.ts +1 -0
  34. package/dist/exceptions/index.js +1 -0
  35. package/dist/exceptions/token-expired.d.ts +4 -0
  36. package/dist/exceptions/token-expired.js +10 -0
  37. package/dist/middleware/cache.js +10 -0
  38. package/dist/services/authorization.js +72 -30
  39. package/dist/services/collections.d.ts +2 -0
  40. package/dist/services/collections.js +10 -0
  41. package/dist/services/fields.js +26 -1
  42. package/dist/services/files.d.ts +5 -1
  43. package/dist/services/files.js +59 -40
  44. package/dist/services/graphql.d.ts +2 -3
  45. package/dist/services/graphql.js +65 -11
  46. package/dist/services/import-export.js +2 -0
  47. package/dist/services/items.js +12 -5
  48. package/dist/services/payload.d.ts +2 -1
  49. package/dist/services/payload.js +22 -17
  50. package/dist/services/specifications.js +1 -3
  51. package/dist/services/users.js +4 -1
  52. package/dist/types/files.d.ts +8 -0
  53. package/dist/utils/apply-query.d.ts +2 -1
  54. package/dist/utils/apply-query.js +134 -156
  55. package/dist/utils/apply-snapshot.d.ts +3 -1
  56. package/dist/utils/apply-snapshot.js +34 -5
  57. package/dist/utils/get-ast-from-query.js +15 -3
  58. package/dist/utils/get-column-path.d.ts +16 -0
  59. package/dist/utils/get-column-path.js +46 -0
  60. package/dist/utils/get-graphql-type.js +1 -0
  61. package/dist/utils/get-local-type.js +5 -0
  62. package/dist/utils/get-relation-info.d.ts +7 -0
  63. package/dist/utils/get-relation-info.js +45 -0
  64. package/dist/utils/get-relation-type.d.ts +1 -1
  65. package/dist/utils/get-schema.js +3 -0
  66. package/dist/utils/jwt.js +1 -1
  67. package/dist/utils/merge-permissions-for-share.js +1 -1
  68. package/dist/utils/reduce-schema.js +18 -11
  69. package/dist/utils/validate-query.js +19 -15
  70. package/example.env +4 -0
  71. package/package.json +18 -19
package/README.md CHANGED
@@ -16,7 +16,7 @@ view, author, and manage your raw database content. Our performant and flexible
16
16
  schema, and includes rule-based permissions, event/web hooks, custom endpoints, numerous auth options, configurable
17
17
  storage adapters, and much more.
18
18
 
19
- Current database support includes: PostgreSQL, MySQL, SQLite, MS-SQL Server, OracleDB, MariaDB, and varients such as AWS
19
+ Current database support includes: PostgreSQL, MySQL, SQLite, MS-SQL Server, OracleDB, MariaDB, and variants such as AWS
20
20
  Aurora/Redshift or Google Cloud Platform SQL.
21
21
 
22
22
  Learn more at...
@@ -0,0 +1,5 @@
1
+ /// <reference types="jest" />
2
+ export declare const getCache: jest.Mock<any, any>;
3
+ export declare const flushCaches: jest.Mock<any, any>;
4
+ export declare const clearSystemCache: jest.Mock<any, any>;
5
+ export declare const setSystemCache: jest.Mock<any, any>;
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setSystemCache = exports.clearSystemCache = exports.flushCaches = exports.getCache = void 0;
4
+ exports.getCache = jest.fn().mockReturnValue({ cache: undefined, systemCache: undefined, lockCache: undefined });
5
+ exports.flushCaches = jest.fn();
6
+ exports.clearSystemCache = jest.fn();
7
+ exports.setSystemCache = jest.fn();
package/dist/app.js CHANGED
@@ -119,6 +119,9 @@ async function createApp() {
119
119
  connectSrc: ["'self'", 'https://*'],
120
120
  },
121
121
  }, (0, get_config_from_env_1.getConfigFromEnv)('CONTENT_SECURITY_POLICY_'))));
122
+ if (env_1.default.HSTS_ENABLED) {
123
+ app.use(helmet_1.default.hsts((0, get_config_from_env_1.getConfigFromEnv)('HSTS_', ['HSTS_ENABLED'])));
124
+ }
122
125
  await emitter_1.default.emitInit('app.before', { app });
123
126
  await emitter_1.default.emitInit('middlewares.before', { app });
124
127
  app.use(logger_1.expressLogger);
@@ -71,12 +71,8 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
71
71
  res.on('searchEntry', () => {
72
72
  resolve();
73
73
  });
74
- res.on('error', (err) => {
75
- if (!(err instanceof ldapjs_1.OperationsError)) {
76
- reject(handleError(err));
77
- return;
78
- }
79
- // Rebind on OperationsError
74
+ res.on('error', () => {
75
+ // Attempt to rebind on search error
80
76
  this.bindClient.bind(bindDn, bindPassword, (err) => {
81
77
  if (err) {
82
78
  const error = handleError(err);
@@ -93,8 +89,8 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
93
89
  });
94
90
  });
95
91
  res.on('end', (result) => {
92
+ // Handle edge case where authenticated bind user cannot read their own DN
96
93
  if ((result === null || result === void 0 ? void 0 : result.status) === 0) {
97
- // Handle edge case where authenticated bind user could not fetch their own DN
98
94
  reject(new exceptions_1.UnexpectedResponseException('Failed to find bind user record'));
99
95
  }
100
96
  });
@@ -177,12 +173,12 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
177
173
  return user === null || user === void 0 ? void 0 : user.id;
178
174
  }
179
175
  async getUserID(payload) {
180
- var _a;
176
+ var _a, _b, _c;
181
177
  if (!payload.identifier) {
182
178
  throw new exceptions_1.InvalidCredentialsException();
183
179
  }
184
180
  await this.validateBindClient();
185
- const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute } = this.config;
181
+ const { userDn, userScope, userAttribute, groupDn, groupScope, groupAttribute, defaultRoleId } = this.config;
186
182
  const userInfo = await this.fetchUserInfo(userDn, new ldapjs_1.EqualityFilter({
187
183
  attribute: userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn',
188
184
  value: payload.identifier,
@@ -209,7 +205,10 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
209
205
  }
210
206
  const userId = await this.fetchUserId(userInfo.dn);
211
207
  if (userId) {
212
- await this.usersService.updateOne(userId, { role: (_a = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _a !== void 0 ? _a : null });
208
+ // Only sync roles if the AD groups are configured
209
+ if (groupDn) {
210
+ await this.usersService.updateOne(userId, { role: (_b = (_a = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _a !== void 0 ? _a : defaultRoleId) !== null && _b !== void 0 ? _b : null });
211
+ }
213
212
  return userId;
214
213
  }
215
214
  if (!userInfo) {
@@ -221,7 +220,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
221
220
  last_name: userInfo.lastName,
222
221
  email: userInfo.email,
223
222
  external_identifier: userInfo.dn,
224
- role: userRole === null || userRole === void 0 ? void 0 : userRole.id,
223
+ role: (_c = userRole === null || userRole === void 0 ? void 0 : userRole.id) !== null && _c !== void 0 ? _c : defaultRoleId,
225
224
  });
226
225
  return (await this.fetchUserId(userInfo.dn));
227
226
  }
@@ -8,6 +8,7 @@ const express_1 = require("express");
8
8
  const openid_client_1 = require("openid-client");
9
9
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
10
  const ms_1 = __importDefault(require("ms"));
11
+ const flat_1 = __importDefault(require("flat"));
11
12
  const local_1 = require("./local");
12
13
  const auth_1 = require("../../auth");
13
14
  const env_1 = __importDefault(require("../../env"));
@@ -77,7 +78,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
77
78
  return user === null || user === void 0 ? void 0 : user.id;
78
79
  }
79
80
  async getUserID(payload) {
80
- var _a;
81
+ var _a, _b;
81
82
  if (!payload.code || !payload.codeVerifier) {
82
83
  logger_1.default.trace('[OAuth2] No code or codeVerifier in payload');
83
84
  throw new exceptions_1.InvalidCredentialsException();
@@ -91,12 +92,14 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
91
92
  catch (e) {
92
93
  throw handleError(e);
93
94
  }
94
- const { emailKey, identifierKey, allowPublicRegistration } = this.config;
95
+ // Flatten response to support dot indexes
96
+ userInfo = (0, flat_1.default)(userInfo);
97
+ const { provider, emailKey, identifierKey, allowPublicRegistration } = this.config;
95
98
  const email = userInfo[emailKey !== null && emailKey !== void 0 ? emailKey : 'email'];
96
99
  // Fallback to email if explicit identifier not found
97
- const identifier = (_a = userInfo[identifierKey]) !== null && _a !== void 0 ? _a : email;
100
+ const identifier = (_b = ((_a = userInfo[identifierKey]) !== null && _a !== void 0 ? _a : email)) === null || _b === void 0 ? void 0 : _b.toString();
98
101
  if (!identifier) {
99
- logger_1.default.warn(`[OAuth2] Failed to find user identifier for provider "${this.config.provider}"`);
102
+ logger_1.default.warn(`[OAuth2] Failed to find user identifier for provider "${provider}"`);
100
103
  throw new exceptions_1.InvalidCredentialsException();
101
104
  }
102
105
  const userId = await this.fetchUserId(identifier);
@@ -111,11 +114,13 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
111
114
  }
112
115
  // Is public registration allowed?
113
116
  if (!allowPublicRegistration) {
114
- logger_1.default.trace(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${this.config.provider}"`);
117
+ logger_1.default.trace(`[OAuth2] User doesn't exist, and public registration not allowed for provider "${provider}"`);
115
118
  throw new exceptions_1.InvalidCredentialsException();
116
119
  }
117
120
  await this.usersService.createOne({
118
- provider: this.config.provider,
121
+ provider,
122
+ first_name: userInfo[this.config.firstNameKey],
123
+ last_name: userInfo[this.config.lastNameKey],
119
124
  email: email,
120
125
  external_identifier: identifier,
121
126
  role: this.config.defaultRoleId,
@@ -8,6 +8,7 @@ const express_1 = require("express");
8
8
  const openid_client_1 = require("openid-client");
9
9
  const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
10
10
  const ms_1 = __importDefault(require("ms"));
11
+ const flat_1 = __importDefault(require("flat"));
11
12
  const local_1 = require("./local");
12
13
  const auth_1 = require("../../auth");
13
14
  const env_1 = __importDefault(require("../../env"));
@@ -84,7 +85,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
84
85
  return user === null || user === void 0 ? void 0 : user.id;
85
86
  }
86
87
  async getUserID(payload) {
87
- var _a;
88
+ var _a, _b;
88
89
  if (!payload.code || !payload.codeVerifier) {
89
90
  logger_1.default.trace('[OpenID] No code or codeVerifier in payload');
90
91
  throw new exceptions_1.InvalidCredentialsException();
@@ -105,12 +106,14 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
105
106
  catch (e) {
106
107
  throw handleError(e);
107
108
  }
108
- const { identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
109
+ // Flatten response to support dot indexes
110
+ userInfo = (0, flat_1.default)(userInfo);
111
+ const { provider, identifierKey, allowPublicRegistration, requireVerifiedEmail } = this.config;
109
112
  const email = userInfo.email;
110
113
  // Fallback to email if explicit identifier not found
111
- const identifier = (_a = userInfo[identifierKey !== null && identifierKey !== void 0 ? identifierKey : 'sub']) !== null && _a !== void 0 ? _a : email;
114
+ const identifier = (_b = (_a = userInfo[identifierKey !== null && identifierKey !== void 0 ? identifierKey : 'sub']) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : email;
112
115
  if (!identifier) {
113
- logger_1.default.warn(`[OpenID] Failed to find user identifier for provider "${this.config.provider}"`);
116
+ logger_1.default.warn(`[OpenID] Failed to find user identifier for provider "${provider}"`);
114
117
  throw new exceptions_1.InvalidCredentialsException();
115
118
  }
116
119
  const userId = await this.fetchUserId(identifier);
@@ -126,11 +129,11 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
126
129
  const isEmailVerified = !requireVerifiedEmail || userInfo.email_verified;
127
130
  // Is public registration allowed?
128
131
  if (!allowPublicRegistration || !isEmailVerified) {
129
- logger_1.default.trace(`[OpenID] User doesn't exist, and public registration not allowed for provider "${this.config.provider}"`);
132
+ logger_1.default.trace(`[OpenID] User doesn't exist, and public registration not allowed for provider "${provider}"`);
130
133
  throw new exceptions_1.InvalidCredentialsException();
131
134
  }
132
135
  await this.usersService.createOne({
133
- provider: this.config.provider,
136
+ provider,
134
137
  first_name: userInfo.given_name,
135
138
  last_name: userInfo.family_name,
136
139
  email: email,
@@ -93,13 +93,19 @@ async function apply(snapshotPath, options) {
93
93
  if (snapshotDiff.fields.length > 0) {
94
94
  message += '\n\n' + chalk_1.default.black.underline.bold('Fields:');
95
95
  for (const { collection, field, diff } of snapshotDiff.fields) {
96
- if (((_e = diff[0]) === null || _e === void 0 ? void 0 : _e.kind) === 'E') {
96
+ if (((_e = diff[0]) === null || _e === void 0 ? void 0 : _e.kind) === 'E' || (0, apply_snapshot_1.isNestedMetaUpdate)(diff[0])) {
97
97
  message += `\n - ${chalk_1.default.blue('Update')} ${collection}.${field}`;
98
98
  for (const change of diff) {
99
+ const path = change.path.slice(1).join('.');
99
100
  if (change.kind === 'E') {
100
- const path = change.path.slice(1).join('.');
101
101
  message += `\n - Set ${path} to ${change.rhs}`;
102
102
  }
103
+ else if (change.kind === 'D') {
104
+ message += `\n - Remove ${path}`;
105
+ }
106
+ else if (change.kind === 'N') {
107
+ message += `\n - Add ${path} and set it to ${change.rhs}`;
108
+ }
103
109
  }
104
110
  }
105
111
  else if (((_f = diff[0]) === null || _f === void 0 ? void 0 : _f.kind) === 'D') {
@@ -143,7 +149,7 @@ async function apply(snapshotPath, options) {
143
149
  }
144
150
  }
145
151
  }
146
- message += 'The following changes will be applied:\n\n' + chalk_1.default.black(message);
152
+ message = 'The following changes will be applied:\n\n' + chalk_1.default.black(message);
147
153
  if (dryRun) {
148
154
  logger_1.default.info(message);
149
155
  process.exit(0);
@@ -126,6 +126,11 @@ router.get('/:pk',
126
126
  res.setHeader('Content-Type', file.type);
127
127
  res.setHeader('Accept-Ranges', 'bytes');
128
128
  res.setHeader('Cache-Control', `${access}, max-age=${(0, ms_1.default)(env_1.default.ASSETS_CACHE_TTL) / 1000}`);
129
+ const unixTime = Date.parse(file.modified_on);
130
+ if (!Number.isNaN(unixTime)) {
131
+ const lastModifiedDate = new Date(unixTime);
132
+ res.setHeader('Last-Modified', lastModifiedDate.toUTCString());
133
+ }
129
134
  if (range) {
130
135
  res.setHeader('Content-Range', `bytes ${range.start}-${range.end || stat.size - 1}/${stat.size}`);
131
136
  res.status(206);
@@ -1,2 +1,4 @@
1
+ import { RequestHandler } from 'express';
1
2
  declare const router: import("express-serve-static-core").Router;
3
+ export declare const multipartHandler: RequestHandler;
2
4
  export default router;
@@ -3,7 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.multipartHandler = void 0;
6
7
  const format_title_1 = __importDefault(require("@directus/format-title"));
8
+ const utils_1 = require("@directus/shared/utils");
7
9
  const busboy_1 = __importDefault(require("busboy"));
8
10
  const express_1 = __importDefault(require("express"));
9
11
  const joi_1 = __importDefault(require("joi"));
@@ -15,10 +17,9 @@ const use_collection_1 = __importDefault(require("../middleware/use-collection")
15
17
  const validate_batch_1 = require("../middleware/validate-batch");
16
18
  const services_1 = require("../services");
17
19
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
18
- const utils_1 = require("@directus/shared/utils");
19
20
  const router = express_1.default.Router();
20
21
  router.use((0, use_collection_1.default)('directus_files'));
21
- const multipartHandler = (0, async_handler_1.default)(async (req, res, next) => {
22
+ const multipartHandler = (req, res, next) => {
22
23
  if (req.is('multipart/form-data') === false)
23
24
  return next();
24
25
  let headers;
@@ -57,6 +58,9 @@ const multipartHandler = (0, async_handler_1.default)(async (req, res, next) =>
57
58
  payload[fieldname] = fieldValue;
58
59
  });
59
60
  busboy.on('file', async (fieldname, fileStream, filename, encoding, mimetype) => {
61
+ if (!filename) {
62
+ return busboy.emit('error', new exceptions_1.InvalidPayloadException(`File is missing filename`));
63
+ }
60
64
  fileCount++;
61
65
  if (!payload.title) {
62
66
  payload.title = (0, format_title_1.default)(path_1.default.parse(filename).name);
@@ -87,12 +91,16 @@ const multipartHandler = (0, async_handler_1.default)(async (req, res, next) =>
87
91
  req.pipe(busboy);
88
92
  function tryDone() {
89
93
  if (savedFiles.length === fileCount) {
94
+ if (fileCount === 0) {
95
+ return next(new exceptions_1.InvalidPayloadException(`No files where included in the body`));
96
+ }
90
97
  res.locals.savedFiles = savedFiles;
91
98
  return next();
92
99
  }
93
100
  }
94
- });
95
- router.post('/', multipartHandler, (0, async_handler_1.default)(async (req, res, next) => {
101
+ };
102
+ exports.multipartHandler = multipartHandler;
103
+ router.post('/', (0, async_handler_1.default)(exports.multipartHandler), (0, async_handler_1.default)(async (req, res, next) => {
96
104
  if (req.is('multipart/form-data') === false) {
97
105
  throw new exceptions_1.UnsupportedMediaTypeException(`Unsupported Content-Type header`);
98
106
  }
@@ -214,7 +222,7 @@ router.patch('/', (0, validate_batch_1.validateBatch)('update'), (0, async_handl
214
222
  }
215
223
  return next();
216
224
  }), respond_1.respond);
217
- router.patch('/:pk', multipartHandler, (0, async_handler_1.default)(async (req, res, next) => {
225
+ router.patch('/:pk', (0, async_handler_1.default)(exports.multipartHandler), (0, async_handler_1.default)(async (req, res, next) => {
218
226
  const service = new services_1.FilesService({
219
227
  accountability: req.accountability,
220
228
  schema: req.schema,
@@ -0,0 +1,4 @@
1
+ import { DateHelper } from '../types';
2
+ export declare class DateHelperMSSQL extends DateHelper {
3
+ writeTimestamp(date: string): Date;
4
+ }
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DateHelperMSSQL = void 0;
4
+ const types_1 = require("../types");
5
+ const date_fns_1 = require("date-fns");
6
+ class DateHelperMSSQL extends types_1.DateHelper {
7
+ writeTimestamp(date) {
8
+ const parsedDate = (0, date_fns_1.parseISO)(date);
9
+ return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000);
10
+ }
11
+ }
12
+ exports.DateHelperMSSQL = DateHelperMSSQL;
@@ -0,0 +1,5 @@
1
+ import { DateHelper } from '../types';
2
+ export declare class DateHelperMySQL extends DateHelper {
3
+ readTimestampString(date: string): string;
4
+ writeTimestamp(date: string): Date;
5
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DateHelperMySQL = void 0;
4
+ const types_1 = require("../types");
5
+ const date_fns_1 = require("date-fns");
6
+ class DateHelperMySQL extends types_1.DateHelper {
7
+ readTimestampString(date) {
8
+ const parsedDate = new Date(date);
9
+ return new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000).toISOString();
10
+ }
11
+ writeTimestamp(date) {
12
+ const parsedDate = (0, date_fns_1.parseISO)(date);
13
+ return new Date(parsedDate.getTime() + parsedDate.getTimezoneOffset() * 60000);
14
+ }
15
+ }
16
+ exports.DateHelperMySQL = DateHelperMySQL;
@@ -0,0 +1,4 @@
1
+ import { DateHelper } from '../types';
2
+ export declare class DateHelperOracle extends DateHelper {
3
+ fieldFlagForField(fieldType: string): string;
4
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DateHelperOracle = void 0;
4
+ const types_1 = require("../types");
5
+ class DateHelperOracle extends types_1.DateHelper {
6
+ fieldFlagForField(fieldType) {
7
+ switch (fieldType) {
8
+ case 'dateTime':
9
+ return 'cast-datetime';
10
+ default:
11
+ return '';
12
+ }
13
+ }
14
+ }
15
+ exports.DateHelperOracle = DateHelperOracle;
@@ -1,4 +1,5 @@
1
1
  import { DateHelper } from '../types';
2
2
  export declare class DateHelperSQLite extends DateHelper {
3
3
  parse(date: string): string;
4
+ fieldFlagForField(fieldType: string): string;
4
5
  }
@@ -7,5 +7,13 @@ class DateHelperSQLite extends types_1.DateHelper {
7
7
  const newDate = new Date(date);
8
8
  return (newDate.getTime() - newDate.getTimezoneOffset() * 60 * 1000).toString();
9
9
  }
10
+ fieldFlagForField(fieldType) {
11
+ switch (fieldType) {
12
+ case 'timestamp':
13
+ return 'cast-timestamp';
14
+ default:
15
+ return '';
16
+ }
17
+ }
10
18
  }
11
19
  exports.DateHelperSQLite = DateHelperSQLite;
@@ -1,7 +1,7 @@
1
1
  export { DateHelperDefault as postgres } from './dialects/default';
2
2
  export { DateHelperDefault as redshift } from './dialects/default';
3
3
  export { DateHelperDefault as cockroachdb } from './dialects/default';
4
- export { DateHelperDefault as oracle } from './dialects/default';
5
- export { DateHelperDefault as mysql } from './dialects/default';
6
- export { DateHelperDefault as mssql } from './dialects/default';
4
+ export { DateHelperOracle as oracle } from './dialects/oracle';
5
+ export { DateHelperMySQL as mysql } from './dialects/mysql';
6
+ export { DateHelperMSSQL as mssql } from './dialects/mssql';
7
7
  export { DateHelperSQLite as sqlite } from './dialects/sqlite';
@@ -7,11 +7,11 @@ var default_2 = require("./dialects/default");
7
7
  Object.defineProperty(exports, "redshift", { enumerable: true, get: function () { return default_2.DateHelperDefault; } });
8
8
  var default_3 = require("./dialects/default");
9
9
  Object.defineProperty(exports, "cockroachdb", { enumerable: true, get: function () { return default_3.DateHelperDefault; } });
10
- var default_4 = require("./dialects/default");
11
- Object.defineProperty(exports, "oracle", { enumerable: true, get: function () { return default_4.DateHelperDefault; } });
12
- var default_5 = require("./dialects/default");
13
- Object.defineProperty(exports, "mysql", { enumerable: true, get: function () { return default_5.DateHelperDefault; } });
14
- var default_6 = require("./dialects/default");
15
- Object.defineProperty(exports, "mssql", { enumerable: true, get: function () { return default_6.DateHelperDefault; } });
10
+ var oracle_1 = require("./dialects/oracle");
11
+ Object.defineProperty(exports, "oracle", { enumerable: true, get: function () { return oracle_1.DateHelperOracle; } });
12
+ var mysql_1 = require("./dialects/mysql");
13
+ Object.defineProperty(exports, "mysql", { enumerable: true, get: function () { return mysql_1.DateHelperMySQL; } });
14
+ var mssql_1 = require("./dialects/mssql");
15
+ Object.defineProperty(exports, "mssql", { enumerable: true, get: function () { return mssql_1.DateHelperMSSQL; } });
16
16
  var sqlite_1 = require("./dialects/sqlite");
17
17
  Object.defineProperty(exports, "sqlite", { enumerable: true, get: function () { return sqlite_1.DateHelperSQLite; } });
@@ -1,4 +1,7 @@
1
1
  import { DatabaseHelper } from '../types';
2
2
  export declare abstract class DateHelper extends DatabaseHelper {
3
3
  parse(date: string): string;
4
+ readTimestampString(date: string): string;
5
+ writeTimestamp(date: string): Date;
6
+ fieldFlagForField(_fieldType: string): string;
4
7
  }
@@ -2,9 +2,19 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DateHelper = void 0;
4
4
  const types_1 = require("../types");
5
+ const date_fns_1 = require("date-fns");
5
6
  class DateHelper extends types_1.DatabaseHelper {
6
7
  parse(date) {
7
8
  return date;
8
9
  }
10
+ readTimestampString(date) {
11
+ return date;
12
+ }
13
+ writeTimestamp(date) {
14
+ return (0, date_fns_1.parseISO)(date);
15
+ }
16
+ fieldFlagForField(_fieldType) {
17
+ return '';
18
+ }
9
19
  }
10
20
  exports.DateHelper = DateHelper;
@@ -31,7 +31,11 @@ class FnHelperPostgres extends types_1.FnHelper {
31
31
  var _a, _b, _c, _d, _e;
32
32
  const type = (_e = (_d = (_c = (_b = (_a = this.schema.collections) === null || _a === void 0 ? void 0 : _a[table]) === null || _b === void 0 ? void 0 : _b.fields) === null || _c === void 0 ? void 0 : _c[column]) === null || _d === void 0 ? void 0 : _d.type) !== null && _e !== void 0 ? _e : 'unknown';
33
33
  if (type === 'json') {
34
- return this.knex.raw('json_array_length(??.??)', [table, column]);
34
+ const { dbType } = this.schema.collections[table].fields[column];
35
+ return this.knex.raw(dbType === 'jsonb' ? 'jsonb_array_length(??.??)' : 'json_array_length(??.??)', [
36
+ table,
37
+ column,
38
+ ]);
35
39
  }
36
40
  if (type === 'alias') {
37
41
  return this._relationalCount(table, column);
@@ -5,7 +5,7 @@ import * as fnHelpers from './fn';
5
5
  import * as geometryHelpers from './geometry';
6
6
  import * as schemaHelpers from './schema';
7
7
  export declare function getHelpers(database: Knex): {
8
- date: dateHelpers.postgres | dateHelpers.sqlite;
8
+ date: dateHelpers.postgres | dateHelpers.oracle | dateHelpers.mysql | dateHelpers.mssql | dateHelpers.sqlite;
9
9
  st: geometryHelpers.postgres | geometryHelpers.redshift | geometryHelpers.oracle | geometryHelpers.sqlite | geometryHelpers.mysql | geometryHelpers.mssql;
10
10
  schema: schemaHelpers.postgres | schemaHelpers.cockroachdb | schemaHelpers.oracle;
11
11
  };
@@ -0,0 +1,3 @@
1
+ import { Knex } from 'knex';
2
+ export declare function up(knex: Knex): Promise<void>;
3
+ export declare function down(knex: Knex): Promise<void>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ const helpers_1 = require("../helpers");
5
+ async function up(knex) {
6
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
7
+ await helper.changeToString('directus_panels', 'icon', {
8
+ nullable: true,
9
+ default: null,
10
+ length: 30,
11
+ });
12
+ }
13
+ exports.up = up;
14
+ async function down(knex) {
15
+ const helper = (0, helpers_1.getHelpers)(knex).schema;
16
+ await helper.changeToString('directus_panels', 'icon', {
17
+ nullable: true,
18
+ default: 'insert_chart',
19
+ length: 30,
20
+ });
21
+ }
22
+ exports.down = down;
@@ -107,7 +107,7 @@ async function parseCurrentLevel(schema, collection, children, query) {
107
107
  if (!child.relation)
108
108
  continue;
109
109
  if (child.type === 'm2o') {
110
- columnsToSelectInternal.push(child.fieldKey);
110
+ columnsToSelectInternal.push(child.relation.field);
111
111
  }
112
112
  if (child.type === 'a2o') {
113
113
  columnsToSelectInternal.push(child.relation.field);
@@ -126,7 +126,7 @@ async function parseCurrentLevel(schema, collection, children, query) {
126
126
  const columnsToSelect = [...new Set(columnsToSelectInternal)];
127
127
  const fieldNodes = columnsToSelect.map((column) => {
128
128
  var _a;
129
- return (_a = children.find((childNode) => childNode.type === 'field' && childNode.fieldKey === column)) !== null && _a !== void 0 ? _a : {
129
+ return (_a = children.find((childNode) => childNode.type === 'field' && (childNode.fieldKey === column || childNode.name === column))) !== null && _a !== void 0 ? _a : {
130
130
  type: 'field',
131
131
  name: column,
132
132
  fieldKey: column,
@@ -221,7 +221,7 @@ function mergeWithParentItems(schema, nestedItem, parentItem, nestedNode) {
221
221
  for (const parentItem of parentItems) {
222
222
  const itemChild = nestedItems.find((nestedItem) => {
223
223
  return (nestedItem[schema.collections[nestedNode.relation.related_collection].primary] ==
224
- parentItem[nestedNode.fieldKey]);
224
+ parentItem[nestedNode.relation.field]);
225
225
  });
226
226
  parentItem[nestedNode.fieldKey] = itemChild || null;
227
227
  }
@@ -193,7 +193,7 @@ fields:
193
193
 
194
194
  - field: item_duplication_fields
195
195
  special:
196
- - json
196
+ - cast-json
197
197
  interface: system-field-tree
198
198
  options:
199
199
  collectionField: collection
@@ -39,7 +39,6 @@ fields:
39
39
  options:
40
40
  iconRight: language
41
41
  placeholder: en-US
42
- includeProjectDefault: false
43
42
  translations:
44
43
  language: en-US
45
44
  translations: Default Language
@@ -72,6 +72,8 @@ fields:
72
72
  - field: language
73
73
  interface: system-language
74
74
  width: half
75
+ options:
76
+ includeProjectDefault: true
75
77
 
76
78
  - field: theme
77
79
  interface: select-dropdown
@@ -152,6 +154,7 @@ fields:
152
154
  - field: last_access
153
155
  width: half
154
156
  display: datetime
157
+ readonly: true
155
158
  display_options:
156
159
  relative: true
157
160
 
package/dist/env.js CHANGED
@@ -14,7 +14,7 @@ const lodash_1 = require("lodash");
14
14
  const path_1 = __importDefault(require("path"));
15
15
  const require_yaml_1 = require("./utils/require-yaml");
16
16
  const utils_1 = require("@directus/shared/utils");
17
- const acceptedEnvTypes = ['string', 'number', 'regex', 'array'];
17
+ const acceptedEnvTypes = ['string', 'number', 'regex', 'array', 'json'];
18
18
  const defaults = {
19
19
  CONFIG_PATH: path_1.default.resolve(process.cwd(), '.env'),
20
20
  HOST: '0.0.0.0',
@@ -69,6 +69,8 @@ const defaults = {
69
69
  SERVE_APP: true,
70
70
  RELATIONAL_BATCH_SIZE: 25000,
71
71
  EXPORT_BATCH_SIZE: 5000,
72
+ FILE_METADATA_ALLOW_LIST: 'ifd0.Make,ifd0.Model,exif.FNumber,exif.ExposureTime,exif.FocalLength,exif.ISO',
73
+ GRAPHQL_INTROSPECTION: true,
72
74
  };
73
75
  // Allows us to force certain environment variable into a type, instead of relying
74
76
  // on the auto-parsed type in processValues. ref #3705
@@ -82,11 +84,13 @@ const typeMap = {
82
84
  DB_PORT: 'number',
83
85
  DB_EXCLUDE_TABLES: 'array',
84
86
  IMPORT_IP_DENY_LIST: 'array',
87
+ FILE_METADATA_ALLOW_LIST: 'array',
88
+ GRAPHQL_INTROSPECTION: 'boolean',
85
89
  };
86
90
  let env = {
87
91
  ...defaults,
88
- ...getEnv(),
89
92
  ...process.env,
93
+ ...getEnv(),
90
94
  };
91
95
  process.env = env;
92
96
  env = processValues(env);
@@ -98,8 +102,8 @@ exports.default = env;
98
102
  function refreshEnv() {
99
103
  env = {
100
104
  ...defaults,
101
- ...getEnv(),
102
105
  ...process.env,
106
+ ...getEnv(),
103
107
  };
104
108
  process.env = env;
105
109
  env = processValues(env);
@@ -207,6 +211,8 @@ function processValues(env) {
207
211
  case 'json':
208
212
  env[key] = tryJSON(value);
209
213
  break;
214
+ case 'boolean':
215
+ env[key] = value === 'true' || value === true || value === '1' || value === 1;
210
216
  }
211
217
  continue;
212
218
  }
@@ -13,6 +13,7 @@ export * from './method-not-allowed';
13
13
  export * from './range-not-satisfiable';
14
14
  export * from './route-not-found';
15
15
  export * from './service-unavailable';
16
+ export * from './token-expired';
16
17
  export * from './unprocessable-entity';
17
18
  export * from './unsupported-media-type';
18
19
  export * from './user-suspended';