directus 9.9.0 → 9.9.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.
@@ -58,6 +58,9 @@ const multipartHandler = (req, res, next) => {
58
58
  payload[fieldname] = fieldValue;
59
59
  });
60
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
+ }
61
64
  fileCount++;
62
65
  if (!payload.title) {
63
66
  payload.title = (0, format_title_1.default)(path_1.default.parse(filename).name);
@@ -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);
@@ -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
package/dist/env.js CHANGED
@@ -87,8 +87,8 @@ const typeMap = {
87
87
  };
88
88
  let env = {
89
89
  ...defaults,
90
- ...getEnv(),
91
90
  ...process.env,
91
+ ...getEnv(),
92
92
  };
93
93
  process.env = env;
94
94
  env = processValues(env);
@@ -100,8 +100,8 @@ exports.default = env;
100
100
  function refreshEnv() {
101
101
  env = {
102
102
  ...defaults,
103
- ...getEnv(),
104
103
  ...process.env,
104
+ ...getEnv(),
105
105
  };
106
106
  process.env = env;
107
107
  env = processValues(env);
@@ -239,6 +239,8 @@ class CollectionsService {
239
239
  */
240
240
  async readOne(collectionKey) {
241
241
  const result = await this.readMany([collectionKey]);
242
+ if (result.length === 0)
243
+ throw new exceptions_1.ForbiddenException();
242
244
  return result[0];
243
245
  }
244
246
  /**
@@ -195,6 +195,8 @@ class FieldsService {
195
195
  catch {
196
196
  // Do nothing
197
197
  }
198
+ if (!column && !fieldInfo)
199
+ throw new exceptions_1.ForbiddenException();
198
200
  const type = (0, get_local_type_1.default)(column, fieldInfo);
199
201
  const data = {
200
202
  collection,
@@ -1175,6 +1175,7 @@ class GraphQLService {
1175
1175
  continue;
1176
1176
  selection = selection;
1177
1177
  let current;
1178
+ let currentAlias = null;
1178
1179
  // Union type (Many-to-Any)
1179
1180
  if (selection.kind === 'InlineFragment') {
1180
1181
  if (selection.typeCondition.name.value.startsWith('__'))
@@ -1187,8 +1188,20 @@ class GraphQLService {
1187
1188
  if (selection.name.value.startsWith('__'))
1188
1189
  continue;
1189
1190
  current = selection.name.value;
1191
+ if (selection.alias) {
1192
+ currentAlias = selection.alias.value;
1193
+ }
1190
1194
  if (parent) {
1191
1195
  current = `${parent}.${current}`;
1196
+ if (currentAlias) {
1197
+ currentAlias = `${parent}.${currentAlias}`;
1198
+ // add nested aliases into deep query
1199
+ if (selection.selectionSet) {
1200
+ if (!query.deep)
1201
+ query.deep = {};
1202
+ (0, lodash_1.set)(query.deep, parent, (0, lodash_1.merge)((0, lodash_1.get)(query.deep, parent), { _alias: { [selection.alias.value]: selection.name.value } }));
1203
+ }
1204
+ }
1192
1205
  }
1193
1206
  }
1194
1207
  if (selection.selectionSet) {
@@ -1203,7 +1216,7 @@ class GraphQLService {
1203
1216
  }
1204
1217
  }
1205
1218
  else {
1206
- children = parseFields(selection.selectionSet.selections, current);
1219
+ children = parseFields(selection.selectionSet.selections, currentAlias !== null && currentAlias !== void 0 ? currentAlias : current);
1207
1220
  }
1208
1221
  fields.push(...children);
1209
1222
  }
@@ -354,13 +354,14 @@ class ItemsService {
354
354
  knex: trx,
355
355
  schema: this.schema,
356
356
  });
357
- const revisionIDs = await revisionsService.createMany(await Promise.all(activity.map(async (activity, index) => ({
357
+ const revisions = (await Promise.all(activity.map(async (activity, index) => ({
358
358
  activity: activity,
359
359
  collection: this.collection,
360
360
  item: keys[index],
361
361
  data: snapshots && Array.isArray(snapshots) ? JSON.stringify(snapshots[index]) : JSON.stringify(snapshots),
362
362
  delta: await payloadService.prepareDelta(payloadWithTypeCasting),
363
- }))));
363
+ })))).filter((revision) => revision.delta);
364
+ const revisionIDs = await revisionsService.createMany(revisions);
364
365
  for (let i = 0; i < revisionIDs.length; i++) {
365
366
  const revisionID = revisionIDs[i];
366
367
  if (opts === null || opts === void 0 ? void 0 : opts.onRevisionCreate) {
@@ -529,7 +530,8 @@ class ItemsService {
529
530
  defaults[name] = null;
530
531
  continue;
531
532
  }
532
- defaults[name] = field.defaultValue;
533
+ if (field.defaultValue)
534
+ defaults[name] = field.defaultValue;
533
535
  }
534
536
  return defaults;
535
537
  }
@@ -65,6 +65,6 @@ export declare class PayloadService {
65
65
  * Transforms the input partial payload to match the output structure, to have consistency
66
66
  * between delta and data
67
67
  */
68
- prepareDelta(data: Partial<Item>): Promise<string>;
68
+ prepareDelta(data: Partial<Item>): Promise<string | null>;
69
69
  }
70
70
  export {};
@@ -578,6 +578,8 @@ class PayloadService {
578
578
  }
579
579
  }
580
580
  payload = await this.processValues('read', payload);
581
+ if (Object.keys(payload).length === 0)
582
+ return null;
581
583
  return JSON.stringify(payload);
582
584
  }
583
585
  }
@@ -315,9 +315,7 @@ class OASSpecsService {
315
315
  schema: {
316
316
  properties: {
317
317
  data: {
318
- items: {
319
- $ref: `#/components/schemas/${tag.name}`,
320
- },
318
+ $ref: `#/components/schemas/${tag.name}`,
321
319
  },
322
320
  },
323
321
  },
@@ -140,8 +140,11 @@ class UsersService extends items_1.ItemsService {
140
140
  * Update many users by primary key
141
141
  */
142
142
  async updateMany(keys, data, opts) {
143
+ var _a, _b;
143
144
  if (data.role) {
144
- const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', data.role).first();
145
+ // data.role will be an object with id with GraphQL mutations
146
+ const roleId = (_b = (_a = data.role) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : data.role;
147
+ const newRole = await this.knex.select('admin_access').from('directus_roles').where('id', roleId).first();
145
148
  if (!(newRole === null || newRole === void 0 ? void 0 : newRole.admin_access)) {
146
149
  await this.checkRemainingAdminExistence(keys);
147
150
  }
@@ -228,7 +228,7 @@ function applyFilter(knex, schema, rootQuery, rootFilter, collection, subQuery =
228
228
  subQueryKnex
229
229
  .select({ [field]: column })
230
230
  .from(collection)
231
- .whereNotNull(field);
231
+ .whereNotNull(column);
232
232
  applyQuery(knex, relation.collection, subQueryKnex, { filter }, schema, true);
233
233
  };
234
234
  if (((_a = Object.keys(value)) === null || _a === void 0 ? void 0 : _a[0]) === '_none') {
@@ -75,9 +75,17 @@ async function getASTFromQuery(collection, query, schema, options) {
75
75
  const relationalStructure = {};
76
76
  for (const fieldKey of fields) {
77
77
  let name = fieldKey;
78
- const isAlias = (_a = (query.alias && name in query.alias)) !== null && _a !== void 0 ? _a : false;
79
- if (isAlias) {
80
- name = query.alias[fieldKey];
78
+ if (query.alias) {
79
+ // check for field alias (is is one of the key)
80
+ if (name in query.alias) {
81
+ name = query.alias[fieldKey];
82
+ }
83
+ // check for junction alias (it is one of the value instead of the key)
84
+ if (Object.values(query.alias).includes(name)) {
85
+ const aliasKey = Object.keys(query.alias).find((key) => { var _a; return ((_a = query.alias) === null || _a === void 0 ? void 0 : _a[key]) === name; });
86
+ if (aliasKey && fieldKey !== aliasKey)
87
+ name = aliasKey;
88
+ }
81
89
  }
82
90
  const isRelational = name.includes('.') ||
83
91
  // We'll always treat top level o2m fields as a related item. This is an alias field, otherwise it won't return
@@ -162,6 +170,10 @@ async function getASTFromQuery(collection, query, schema, options) {
162
170
  if (permissions && permissions.some((permission) => permission.collection === relatedCollection) === false) {
163
171
  continue;
164
172
  }
173
+ // update query alias for children parseFields
174
+ const deepAlias = (_a = getDeepQuery((deep === null || deep === void 0 ? void 0 : deep[fieldKey]) || {})) === null || _a === void 0 ? void 0 : _a.alias;
175
+ if (!(0, lodash_1.isEmpty)(deepAlias))
176
+ query.alias = deepAlias;
165
177
  child = {
166
178
  type: relationType,
167
179
  name: relatedCollection,
@@ -8,6 +8,7 @@ const schema_1 = __importDefault(require("@directus/schema"));
8
8
  const utils_1 = require("@directus/shared/utils");
9
9
  const lodash_1 = require("lodash");
10
10
  const cache_1 = require("../cache");
11
+ const constants_1 = require("../constants");
11
12
  const database_1 = __importDefault(require("../database"));
12
13
  const collections_1 = require("../database/system-data/collections");
13
14
  const fields_1 = require("../database/system-data/fields");
@@ -113,6 +114,8 @@ async function getDatabaseSchema(database, schemaInspector) {
113
114
  const existing = result.collections[field.collection].fields[field.field];
114
115
  const column = schemaOverview[field.collection].columns[field.field];
115
116
  const special = field.special ? (0, utils_1.toArray)(field.special) : [];
117
+ if (constants_1.ALIAS_TYPES.some((type) => special.includes(type)) === false && !existing)
118
+ continue;
116
119
  const type = (existing && (0, get_local_type_1.default)(column, { special })) || 'alias';
117
120
  let validation = (_a = field.validation) !== null && _a !== void 0 ? _a : null;
118
121
  if (validation && typeof validation === 'string')
@@ -10,7 +10,7 @@ const lodash_1 = require("lodash");
10
10
  * @returns Reduced schema
11
11
  */
12
12
  function reduceSchema(schema, permissions, actions = ['create', 'read', 'update', 'delete']) {
13
- var _a, _b, _c;
13
+ var _a, _b, _c, _d, _e;
14
14
  const reduced = {
15
15
  collections: {},
16
16
  relations: [],
@@ -25,19 +25,27 @@ function reduceSchema(schema, permissions, actions = ['create', 'read', 'update'
25
25
  return acc;
26
26
  }, {})) !== null && _a !== void 0 ? _a : {};
27
27
  for (const [collectionName, collection] of Object.entries(schema.collections)) {
28
- if (permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === collectionName && actions.includes(permission.action))) {
29
- const fields = {};
30
- for (const [fieldName, field] of Object.entries(schema.collections[collectionName].fields)) {
31
- if (((_b = allowedFieldsInCollection[collectionName]) === null || _b === void 0 ? void 0 : _b.includes('*')) ||
32
- ((_c = allowedFieldsInCollection[collectionName]) === null || _c === void 0 ? void 0 : _c.includes(fieldName))) {
33
- fields[fieldName] = field;
34
- }
28
+ if (!(permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === collectionName && actions.includes(permission.action)))) {
29
+ continue;
30
+ }
31
+ const fields = {};
32
+ for (const [fieldName, field] of Object.entries(schema.collections[collectionName].fields)) {
33
+ if (!((_b = allowedFieldsInCollection[collectionName]) === null || _b === void 0 ? void 0 : _b.includes('*')) &&
34
+ !((_c = allowedFieldsInCollection[collectionName]) === null || _c === void 0 ? void 0 : _c.includes(fieldName))) {
35
+ continue;
36
+ }
37
+ const relatedCollection = ((_d = schema.relations.find((relation) => relation.collection === collectionName && relation.field === fieldName)) === null || _d === void 0 ? void 0 : _d.related_collection) ||
38
+ ((_e = schema.relations.find((relation) => { var _a; return relation.related_collection === collectionName && ((_a = relation.meta) === null || _a === void 0 ? void 0 : _a.one_field) === fieldName; })) === null || _e === void 0 ? void 0 : _e.collection);
39
+ if (relatedCollection &&
40
+ !(permissions === null || permissions === void 0 ? void 0 : permissions.some((permission) => permission.collection === relatedCollection && actions.includes(permission.action)))) {
41
+ continue;
35
42
  }
36
- reduced.collections[collectionName] = {
37
- ...collection,
38
- fields,
39
- };
43
+ fields[fieldName] = field;
40
44
  }
45
+ reduced.collections[collectionName] = {
46
+ ...collection,
47
+ fields,
48
+ };
41
49
  }
42
50
  reduced.relations = schema.relations.filter((relation) => {
43
51
  var _a, _b, _c;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "directus",
3
- "version": "9.9.0",
3
+ "version": "9.9.1",
4
4
  "license": "GPL-3.0-only",
5
5
  "homepage": "https://github.com/directus/directus#readme",
6
6
  "description": "Directus is a real-time API and App dashboard for managing SQL database content.",
@@ -78,16 +78,16 @@
78
78
  ],
79
79
  "dependencies": {
80
80
  "@aws-sdk/client-ses": "^3.40.0",
81
- "@directus/app": "9.9.0",
82
- "@directus/drive": "9.9.0",
83
- "@directus/drive-azure": "9.9.0",
84
- "@directus/drive-gcs": "9.9.0",
85
- "@directus/drive-s3": "9.9.0",
86
- "@directus/extensions-sdk": "9.9.0",
87
- "@directus/format-title": "9.9.0",
88
- "@directus/schema": "9.9.0",
89
- "@directus/shared": "9.9.0",
90
- "@directus/specs": "9.9.0",
81
+ "@directus/app": "9.9.1",
82
+ "@directus/drive": "9.9.1",
83
+ "@directus/drive-azure": "9.9.1",
84
+ "@directus/drive-gcs": "9.9.1",
85
+ "@directus/drive-s3": "9.9.1",
86
+ "@directus/extensions-sdk": "9.9.1",
87
+ "@directus/format-title": "9.9.1",
88
+ "@directus/schema": "9.9.1",
89
+ "@directus/shared": "9.9.1",
90
+ "@directus/specs": "9.9.1",
91
91
  "@godaddy/terminus": "^4.9.0",
92
92
  "@rollup/plugin-alias": "^3.1.9",
93
93
  "@rollup/plugin-virtual": "^2.0.3",
@@ -173,7 +173,7 @@
173
173
  "sqlite3": "^5.0.2",
174
174
  "tedious": "^13.0.0"
175
175
  },
176
- "gitHead": "fb252ca4cef57de1aa8d1914db6bc85730902e81",
176
+ "gitHead": "ed780aceba707c714e0b0aa01a953d141a5c800e",
177
177
  "devDependencies": {
178
178
  "@types/async": "3.2.10",
179
179
  "@types/body-parser": "1.19.2",