directus 9.0.1 → 9.2.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 (111) hide show
  1. package/dist/app.js +2 -0
  2. package/dist/auth/drivers/ldap.js +9 -13
  3. package/dist/auth/drivers/oauth2.d.ts +1 -1
  4. package/dist/auth/drivers/oauth2.js +2 -2
  5. package/dist/auth/drivers/openid.d.ts +1 -1
  6. package/dist/auth/drivers/openid.js +8 -2
  7. package/dist/auth.js +5 -3
  8. package/dist/cli/commands/database/install.js +2 -4
  9. package/dist/cli/commands/schema/apply.js +26 -10
  10. package/dist/controllers/assets.js +0 -27
  11. package/dist/controllers/auth.js +7 -2
  12. package/dist/controllers/extensions.js +1 -1
  13. package/dist/controllers/notifications.d.ts +2 -0
  14. package/dist/controllers/notifications.js +147 -0
  15. package/dist/database/{functions/types.d.ts → helpers/date/dialects/mssql.d.ts} +2 -1
  16. package/dist/database/{functions → helpers/date}/dialects/mssql.js +4 -6
  17. package/dist/database/{functions → helpers/date}/dialects/mysql.d.ts +2 -4
  18. package/dist/database/{functions → helpers/date}/dialects/mysql.js +4 -6
  19. package/dist/database/{functions/dialects/mssql.d.ts → helpers/date/dialects/oracle.d.ts} +2 -4
  20. package/dist/database/{functions → helpers/date}/dialects/oracle.js +4 -6
  21. package/dist/database/helpers/date/dialects/postgres.d.ts +12 -0
  22. package/dist/database/{functions → helpers/date}/dialects/postgres.js +4 -6
  23. package/dist/database/{functions → helpers/date}/dialects/sqlite.d.ts +3 -4
  24. package/dist/database/helpers/date/dialects/sqlite.js +35 -0
  25. package/dist/database/helpers/date/index.d.ts +6 -0
  26. package/dist/database/helpers/date/index.js +15 -0
  27. package/dist/database/helpers/date/types.d.ts +13 -0
  28. package/dist/database/helpers/date/types.js +10 -0
  29. package/dist/database/helpers/geometry/dialects/mssql.d.ts +14 -0
  30. package/dist/database/helpers/geometry/dialects/mssql.js +36 -0
  31. package/dist/database/helpers/geometry/dialects/mysql.d.ts +7 -0
  32. package/dist/database/helpers/geometry/dialects/mysql.js +16 -0
  33. package/dist/database/helpers/geometry/dialects/oracle.d.ts +15 -0
  34. package/dist/database/helpers/geometry/dialects/oracle.js +39 -0
  35. package/dist/database/helpers/geometry/dialects/postgres.d.ts +10 -0
  36. package/dist/database/helpers/geometry/dialects/postgres.js +23 -0
  37. package/dist/database/helpers/geometry/dialects/redshift.d.ts +7 -0
  38. package/dist/database/helpers/geometry/dialects/redshift.js +16 -0
  39. package/dist/database/helpers/geometry/dialects/sqlite.d.ts +6 -0
  40. package/dist/database/helpers/geometry/dialects/sqlite.js +14 -0
  41. package/dist/database/helpers/geometry/index.d.ts +6 -0
  42. package/dist/database/helpers/geometry/index.js +15 -0
  43. package/dist/database/helpers/{geometry.d.ts → geometry/types.d.ts} +3 -7
  44. package/dist/database/helpers/geometry/types.js +54 -0
  45. package/dist/database/helpers/index.d.ts +8 -0
  46. package/dist/database/helpers/index.js +33 -0
  47. package/dist/database/helpers/types.d.ts +5 -0
  48. package/dist/database/helpers/types.js +9 -0
  49. package/dist/database/index.js +6 -6
  50. package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
  51. package/dist/database/migrations/20211118A-add-notifications.js +28 -0
  52. package/dist/database/run-ast.js +5 -5
  53. package/dist/database/seeds/run.js +3 -3
  54. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
  55. package/dist/database/system-data/collections/collections.yaml +2 -0
  56. package/dist/database/system-data/fields/notifications.yaml +13 -0
  57. package/dist/database/system-data/fields/users.yaml +5 -0
  58. package/dist/database/system-data/relations/relations.yaml +6 -0
  59. package/dist/env.js +1 -0
  60. package/dist/extensions.js +17 -2
  61. package/dist/middleware/get-permissions.js +3 -102
  62. package/dist/middleware/sanitize-query.js +1 -1
  63. package/dist/services/activity.d.ts +7 -5
  64. package/dist/services/activity.js +87 -3
  65. package/dist/services/assets.js +14 -0
  66. package/dist/services/collections.js +12 -1
  67. package/dist/services/fields.d.ts +2 -0
  68. package/dist/services/fields.js +57 -26
  69. package/dist/services/files.d.ts +1 -1
  70. package/dist/services/files.js +13 -11
  71. package/dist/services/graphql.js +6 -0
  72. package/dist/services/index.d.ts +1 -0
  73. package/dist/services/index.js +1 -0
  74. package/dist/services/items.js +18 -29
  75. package/dist/services/mail/index.js +2 -2
  76. package/dist/services/mail/templates/base.liquid +153 -85
  77. package/dist/services/mail/templates/password-reset.liquid +3 -2
  78. package/dist/services/mail/templates/user-invitation.liquid +4 -4
  79. package/dist/services/notifications.d.ts +12 -0
  80. package/dist/services/notifications.js +41 -0
  81. package/dist/services/payload.d.ts +2 -0
  82. package/dist/services/payload.js +3 -3
  83. package/dist/services/users.js +1 -0
  84. package/dist/types/collection.d.ts +1 -0
  85. package/dist/utils/apply-query.js +9 -11
  86. package/dist/utils/apply-snapshot.js +27 -28
  87. package/dist/utils/get-column.js +2 -2
  88. package/dist/utils/get-default-index-name.js +2 -2
  89. package/dist/utils/get-local-type.js +11 -17
  90. package/dist/utils/get-permissions.d.ts +3 -0
  91. package/dist/utils/get-permissions.js +106 -0
  92. package/dist/utils/md.d.ts +4 -0
  93. package/dist/utils/md.js +15 -0
  94. package/dist/utils/merge-permissions.js +2 -2
  95. package/dist/utils/sanitize-query.js +4 -15
  96. package/dist/utils/user-name.d.ts +2 -0
  97. package/dist/utils/user-name.js +16 -0
  98. package/dist/utils/validate-query.js +1 -1
  99. package/dist/webhooks.js +16 -24
  100. package/package.json +27 -24
  101. package/dist/database/functions/dialects/oracle.d.ts +0 -14
  102. package/dist/database/functions/dialects/postgres.d.ts +0 -14
  103. package/dist/database/functions/dialects/sqlite.js +0 -33
  104. package/dist/database/functions/index.d.ts +0 -3
  105. package/dist/database/functions/index.js +0 -26
  106. package/dist/database/functions/types.js +0 -2
  107. package/dist/database/helpers/date.d.ts +0 -8
  108. package/dist/database/helpers/date.js +0 -44
  109. package/dist/database/helpers/geometry.js +0 -189
  110. package/dist/utils/get-simple-hash.d.ts +0 -5
  111. package/dist/utils/get-simple-hash.js +0 -15
@@ -90,6 +90,11 @@ fields:
90
90
  special: conceal
91
91
  width: half
92
92
 
93
+ - field: email_notifications
94
+ interface: boolean
95
+ width: half
96
+ special: boolean
97
+
93
98
  - field: admin_divider
94
99
  interface: presentation-divider
95
100
  options:
@@ -82,3 +82,9 @@ data:
82
82
  - many_collection: directus_panels
83
83
  many_field: user_created
84
84
  one_collection: directus_users
85
+ - many_collection: directus_notifications
86
+ many_field: recipient
87
+ one_collection: directus_users
88
+ - many_collection: directus_notifications
89
+ many_field: sender
90
+ one_collection: directus_users
package/dist/env.js CHANGED
@@ -50,6 +50,7 @@ const defaults = {
50
50
  CACHE_SCHEMA: true,
51
51
  CACHE_PERMISSIONS: true,
52
52
  AUTH_PROVIDERS: '',
53
+ AUTH_DISABLE_DEFAULT: false,
53
54
  EXTENSIONS_PATH: './extensions',
54
55
  EMAIL_FROM: 'no-reply@directus.io',
55
56
  EMAIL_TRANSPORT: 'sendmail',
@@ -31,6 +31,7 @@ const database_1 = __importDefault(require("./database"));
31
31
  const emitter_1 = __importDefault(require("./emitter"));
32
32
  const env_1 = __importDefault(require("./env"));
33
33
  const exceptions = __importStar(require("./exceptions"));
34
+ const sharedExceptions = __importStar(require("@directus/shared/exceptions"));
34
35
  const logger_1 = __importDefault(require("./logger"));
35
36
  const fs_extra_1 = __importDefault(require("fs-extra"));
36
37
  const get_schema_1 = require("./utils/get-schema");
@@ -231,7 +232,14 @@ class ExtensionManager {
231
232
  }
232
233
  },
233
234
  };
234
- register(registerFunctions, { services, exceptions, env: env_1.default, database: (0, database_1.default)(), logger: logger_1.default, getSchema: get_schema_1.getSchema });
235
+ register(registerFunctions, {
236
+ services,
237
+ exceptions: { ...exceptions, ...sharedExceptions },
238
+ env: env_1.default,
239
+ database: (0, database_1.default)(),
240
+ logger: logger_1.default,
241
+ getSchema: get_schema_1.getSchema,
242
+ });
235
243
  }
236
244
  registerEndpoint(endpoint, router) {
237
245
  const endpointPath = path_1.default.resolve(endpoint.path, endpoint.entrypoint || '');
@@ -241,7 +249,14 @@ class ExtensionManager {
241
249
  const routeName = typeof mod === 'function' ? endpoint.name : mod.id;
242
250
  const scopedRouter = express_1.default.Router();
243
251
  router.use(`/${routeName}`, scopedRouter);
244
- register(scopedRouter, { services, exceptions, env: env_1.default, database: (0, database_1.default)(), logger: logger_1.default, getSchema: get_schema_1.getSchema });
252
+ register(scopedRouter, {
253
+ services,
254
+ exceptions: { ...exceptions, ...sharedExceptions },
255
+ env: env_1.default,
256
+ database: (0, database_1.default)(),
257
+ logger: logger_1.default,
258
+ getSchema: get_schema_1.getSchema,
259
+ });
245
260
  this.apiEndpoints.push({
246
261
  path: endpointPath,
247
262
  });
@@ -3,112 +3,13 @@ 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
- const utils_1 = require("@directus/shared/utils");
7
- const lodash_1 = require("lodash");
8
- const database_1 = __importDefault(require("../database"));
9
- const app_access_permissions_1 = require("../database/system-data/app-access-permissions");
10
6
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
11
- const merge_permissions_1 = require("../utils/merge-permissions");
12
- const users_1 = require("../services/users");
13
- const roles_1 = require("../services/roles");
14
- const cache_1 = require("../cache");
15
- const object_hash_1 = __importDefault(require("object-hash"));
16
- const env_1 = __importDefault(require("../env"));
7
+ const get_permissions_1 = require("../utils/get-permissions");
17
8
  const getPermissions = (0, async_handler_1.default)(async (req, res, next) => {
18
- const database = (0, database_1.default)();
19
- const { systemCache } = (0, cache_1.getCache)();
20
- let permissions = [];
21
9
  if (!req.accountability) {
22
- throw new Error('"getPermissions" needs to be used after the "authenticate" middleware');
10
+ throw new Error('getPermissions middleware needs to be called after authenticate');
23
11
  }
24
- if (!req.schema) {
25
- throw new Error('"getPermissions" needs to be used after the "schema" middleware');
26
- }
27
- const { user, role, app, admin } = req.accountability;
28
- const cacheKey = `permissions-${(0, object_hash_1.default)({ user, role, app, admin })}`;
29
- if (env_1.default.CACHE_PERMISSIONS !== false) {
30
- const cachedPermissions = await systemCache.get(cacheKey);
31
- if (cachedPermissions) {
32
- req.accountability.permissions = cachedPermissions;
33
- return next();
34
- }
35
- }
36
- if (req.accountability.admin !== true) {
37
- const permissionsForRole = await database
38
- .select('*')
39
- .from('directus_permissions')
40
- .where({ role: req.accountability.role });
41
- const requiredPermissionData = {
42
- $CURRENT_USER: [],
43
- $CURRENT_ROLE: [],
44
- };
45
- permissions = permissionsForRole.map((permissionRaw) => {
46
- const permission = (0, lodash_1.cloneDeep)(permissionRaw);
47
- if (permission.permissions && typeof permission.permissions === 'string') {
48
- permission.permissions = JSON.parse(permission.permissions);
49
- }
50
- else if (permission.permissions === null) {
51
- permission.permissions = {};
52
- }
53
- if (permission.validation && typeof permission.validation === 'string') {
54
- permission.validation = JSON.parse(permission.validation);
55
- }
56
- else if (permission.validation === null) {
57
- permission.validation = {};
58
- }
59
- if (permission.presets && typeof permission.presets === 'string') {
60
- permission.presets = JSON.parse(permission.presets);
61
- }
62
- else if (permission.presets === null) {
63
- permission.presets = {};
64
- }
65
- if (permission.fields && typeof permission.fields === 'string') {
66
- permission.fields = permission.fields.split(',');
67
- }
68
- else if (permission.fields === null) {
69
- permission.fields = [];
70
- }
71
- const extractPermissionData = (val) => {
72
- if (typeof val === 'string' && val.startsWith('$CURRENT_USER.')) {
73
- requiredPermissionData.$CURRENT_USER.push(val.replace('$CURRENT_USER.', ''));
74
- }
75
- if (typeof val === 'string' && val.startsWith('$CURRENT_ROLE.')) {
76
- requiredPermissionData.$CURRENT_ROLE.push(val.replace('$CURRENT_ROLE.', ''));
77
- }
78
- return val;
79
- };
80
- (0, utils_1.deepMap)(permission.permissions, extractPermissionData);
81
- (0, utils_1.deepMap)(permission.validation, extractPermissionData);
82
- (0, utils_1.deepMap)(permission.presets, extractPermissionData);
83
- return permission;
84
- });
85
- if (req.accountability.app === true) {
86
- permissions = (0, merge_permissions_1.mergePermissions)(permissions, app_access_permissions_1.appAccessMinimalPermissions.map((perm) => ({ ...perm, role: req.accountability.role })));
87
- }
88
- const usersService = new users_1.UsersService({ schema: req.schema });
89
- const rolesService = new roles_1.RolesService({ schema: req.schema });
90
- const filterContext = {};
91
- if (req.accountability.user && requiredPermissionData.$CURRENT_USER.length > 0) {
92
- filterContext.$CURRENT_USER = await usersService.readOne(req.accountability.user, {
93
- fields: requiredPermissionData.$CURRENT_USER,
94
- });
95
- }
96
- if (req.accountability.role && requiredPermissionData.$CURRENT_ROLE.length > 0) {
97
- filterContext.$CURRENT_ROLE = await rolesService.readOne(req.accountability.role, {
98
- fields: requiredPermissionData.$CURRENT_ROLE,
99
- });
100
- }
101
- permissions = permissions.map((permission) => {
102
- permission.permissions = (0, utils_1.parseFilter)(permission.permissions, req.accountability, filterContext);
103
- permission.validation = (0, utils_1.parseFilter)(permission.validation, req.accountability, filterContext);
104
- permission.presets = (0, utils_1.parseFilter)(permission.presets, req.accountability, filterContext);
105
- return permission;
106
- });
107
- if (env_1.default.CACHE_PERMISSIONS !== false) {
108
- await systemCache.set(cacheKey, permissions);
109
- }
110
- }
111
- req.accountability.permissions = permissions;
12
+ req.accountability.permissions = await (0, get_permissions_1.getPermissions)(req.accountability, req.schema);
112
13
  return next();
113
14
  });
114
15
  exports.default = getPermissions;
@@ -6,7 +6,7 @@
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
7
  const sanitize_query_1 = require("../utils/sanitize-query");
8
8
  const validate_query_1 = require("../utils/validate-query");
9
- const sanitizeQueryMiddleware = (req, res, next) => {
9
+ const sanitizeQueryMiddleware = (req, _res, next) => {
10
10
  req.sanitizedQuery = {};
11
11
  if (!req.query)
12
12
  return;
@@ -1,8 +1,10 @@
1
- import { AbstractServiceOptions } from '../types';
2
- import { ItemsService } from './index';
3
- /**
4
- * @TODO only return activity of the collections you have access to
5
- */
1
+ import { AbstractServiceOptions, PrimaryKey, Item } from '../types';
2
+ import { ItemsService, MutationOptions } from './index';
3
+ import { NotificationsService } from './notifications';
4
+ import { UsersService } from './users';
6
5
  export declare class ActivityService extends ItemsService {
6
+ notificationsService: NotificationsService;
7
+ usersService: UsersService;
7
8
  constructor(options: AbstractServiceOptions);
9
+ createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
8
10
  }
@@ -1,13 +1,97 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.ActivityService = void 0;
7
+ const types_1 = require("../types");
4
8
  const index_1 = require("./index");
5
- /**
6
- * @TODO only return activity of the collections you have access to
7
- */
9
+ const notifications_1 = require("./notifications");
10
+ const users_1 = require("./users");
11
+ const authorization_1 = require("./authorization");
12
+ const get_permissions_1 = require("../utils/get-permissions");
13
+ const forbidden_1 = require("../exceptions/forbidden");
14
+ const logger_1 = __importDefault(require("../logger"));
15
+ const user_name_1 = require("../utils/user-name");
16
+ const lodash_1 = require("lodash");
17
+ const env_1 = __importDefault(require("../env"));
18
+ const uuid_validate_1 = __importDefault(require("uuid-validate"));
8
19
  class ActivityService extends index_1.ItemsService {
9
20
  constructor(options) {
10
21
  super('directus_activity', options);
22
+ this.notificationsService = new notifications_1.NotificationsService({ schema: this.schema });
23
+ this.usersService = new users_1.UsersService({ schema: this.schema });
24
+ }
25
+ async createOne(data, opts) {
26
+ var _a, _b, _c, _d, _e, _f, _g, _h;
27
+ if (data.action === types_1.Action.COMMENT && typeof data.comment === 'string') {
28
+ const usersRegExp = new RegExp(/@[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}/gi);
29
+ const mentions = (0, lodash_1.uniq)((_a = data.comment.match(usersRegExp)) !== null && _a !== void 0 ? _a : []);
30
+ const sender = await this.usersService.readOne(this.accountability.user, {
31
+ fields: ['id', 'first_name', 'last_name', 'email'],
32
+ });
33
+ for (const mention of mentions) {
34
+ const userID = mention.substring(1);
35
+ const user = await this.usersService.readOne(userID, {
36
+ fields: ['id', 'first_name', 'last_name', 'email', 'role.id', 'role.admin_access', 'role.app_access'],
37
+ });
38
+ const accountability = {
39
+ user: userID,
40
+ role: (_c = (_b = user.role) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : null,
41
+ admin: (_e = (_d = user.role) === null || _d === void 0 ? void 0 : _d.admin_access) !== null && _e !== void 0 ? _e : null,
42
+ app: (_g = (_f = user.role) === null || _f === void 0 ? void 0 : _f.app_access) !== null && _g !== void 0 ? _g : null,
43
+ };
44
+ accountability.permissions = await (0, get_permissions_1.getPermissions)(accountability, this.schema);
45
+ const authorizationService = new authorization_1.AuthorizationService({ schema: this.schema, accountability });
46
+ const usersService = new users_1.UsersService({ schema: this.schema, accountability });
47
+ try {
48
+ await authorizationService.checkAccess('read', data.collection, data.item);
49
+ const templateData = await usersService.readByQuery({
50
+ fields: ['id', 'first_name', 'last_name', 'email'],
51
+ filter: { id: { _in: mentions.map((mention) => mention.substring(1)) } },
52
+ });
53
+ const userPreviews = templateData.reduce((acc, user) => {
54
+ acc[user.id] = `<em>${(0, user_name_1.userName)(user)}</em>`;
55
+ return acc;
56
+ }, {});
57
+ let comment = data.comment;
58
+ for (const mention of mentions) {
59
+ const uuid = mention.substring(1);
60
+ // We only match on UUIDs in the first place. This is just an extra sanity check
61
+ if ((0, uuid_validate_1.default)(uuid) === false)
62
+ continue;
63
+ comment = comment.replace(new RegExp(mention, 'gm'), (_h = userPreviews[uuid]) !== null && _h !== void 0 ? _h : '@Unknown User');
64
+ }
65
+ comment = `> ${comment.replace(/\n+/gm, '\n> ')}`;
66
+ const message = `
67
+ Hello ${(0, user_name_1.userName)(user)},
68
+
69
+ ${(0, user_name_1.userName)(sender)} has mentioned you in a comment:
70
+
71
+ ${comment}
72
+
73
+ <a href="${env_1.default.PUBLIC_URL}/admin/content/${data.collection}/${data.item}">Click here to view.</a>
74
+ `;
75
+ await this.notificationsService.createOne({
76
+ recipient: userID,
77
+ sender: sender.id,
78
+ subject: `You were mentioned in ${data.collection}`,
79
+ message,
80
+ collection: data.collection,
81
+ item: data.item,
82
+ });
83
+ }
84
+ catch (err) {
85
+ if (err instanceof forbidden_1.ForbiddenException) {
86
+ logger_1.default.warn(`User ${userID} doesn't have proper permissions to receive notification for this item.`);
87
+ }
88
+ else {
89
+ throw err;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ return super.createOne(data, opts);
11
95
  }
12
96
  }
13
97
  exports.ActivityService = ActivityService;
@@ -34,6 +34,7 @@ const exceptions_1 = require("../exceptions");
34
34
  const storage_1 = __importDefault(require("../storage"));
35
35
  const authorization_1 = require("./authorization");
36
36
  const TransformationUtils = __importStar(require("../utils/transformations"));
37
+ const uuid_validate_1 = __importDefault(require("uuid-validate"));
37
38
  sharp_1.default.concurrency(1);
38
39
  // Note: don't put this in the service. The service can be initialized in multiple places, but they
39
40
  // should all share the same semaphore instance.
@@ -54,7 +55,20 @@ class AssetsService {
54
55
  if (systemPublicKeys.includes(id) === false && ((_a = this.accountability) === null || _a === void 0 ? void 0 : _a.admin) !== true) {
55
56
  await this.authorizationService.checkAccess('read', 'directus_files', id);
56
57
  }
58
+ /**
59
+ * This is a little annoying. Postgres will error out if you're trying to search in `where`
60
+ * with a wrong type. In case of directus_files where id is a uuid, we'll have to verify the
61
+ * validity of the uuid ahead of time.
62
+ */
63
+ const isValidUUID = (0, uuid_validate_1.default)(id, 4);
64
+ if (isValidUUID === false)
65
+ throw new exceptions_1.ForbiddenException();
57
66
  const file = (await this.knex.select('*').from('directus_files').where({ id }).first());
67
+ if (!file)
68
+ throw new exceptions_1.ForbiddenException();
69
+ const { exists } = await storage_1.default.disk(file.storage).exists(file.filename_disk);
70
+ if (!exists)
71
+ throw new exceptions_1.ForbiddenException();
58
72
  if (range) {
59
73
  if (range.start >= file.filesize || (range.end && range.end >= file.filesize)) {
60
74
  throw new exceptions_1.RangeNotSatisfiableException(range);
@@ -171,11 +171,22 @@ class CollectionsService {
171
171
  }));
172
172
  meta.push(...collections_1.systemCollectionRows);
173
173
  if (this.accountability && this.accountability.admin !== true) {
174
- const collectionsYouHavePermissionToRead = this.accountability
174
+ const collectionsGroups = meta.reduce((meta, item) => ({
175
+ ...meta,
176
+ [item.collection]: item.group,
177
+ }), {});
178
+ let collectionsYouHavePermissionToRead = this.accountability
175
179
  .permissions.filter((permission) => {
176
180
  return permission.action === 'read';
177
181
  })
178
182
  .map(({ collection }) => collection);
183
+ for (const collection of collectionsYouHavePermissionToRead) {
184
+ const group = collectionsGroups[collection];
185
+ if (group)
186
+ collectionsYouHavePermissionToRead.push(group);
187
+ delete collectionsGroups[collection];
188
+ }
189
+ collectionsYouHavePermissionToRead = [...new Set([...collectionsYouHavePermissionToRead])];
179
190
  tablesInDatabase = tablesInDatabase.filter((table) => {
180
191
  return collectionsYouHavePermissionToRead.includes(table.name);
181
192
  });
@@ -6,9 +6,11 @@ import { PayloadService } from '../services/payload';
6
6
  import { AbstractServiceOptions, SchemaOverview } from '../types';
7
7
  import { Accountability } from '@directus/shared/types';
8
8
  import { Field, RawField, Type } from '@directus/shared/types';
9
+ import { Helpers } from '../database/helpers';
9
10
  import Keyv from 'keyv';
10
11
  export declare class FieldsService {
11
12
  knex: Knex;
13
+ helpers: Helpers;
12
14
  accountability: Accountability | null;
13
15
  itemsService: ItemsService;
14
16
  payloadService: PayloadService;
@@ -39,10 +39,11 @@ const get_local_type_1 = __importDefault(require("../utils/get-local-type"));
39
39
  const utils_1 = require("@directus/shared/utils");
40
40
  const lodash_1 = require("lodash");
41
41
  const relations_1 = require("./relations");
42
- const geometry_1 = require("../database/helpers/geometry");
42
+ const helpers_1 = require("../database/helpers");
43
43
  class FieldsService {
44
44
  constructor(options) {
45
45
  this.knex = options.knex || (0, database_1.default)();
46
+ this.helpers = (0, helpers_1.getHelpers)(this.knex);
46
47
  this.schemaInspector = options.knex ? (0, schema_1.default)(options.knex) : (0, database_1.getSchemaInspector)();
47
48
  this.accountability = options.accountability || null;
48
49
  this.itemsService = new items_1.ItemsService('directus_fields', options);
@@ -211,23 +212,39 @@ class FieldsService {
211
212
  accountability: this.accountability,
212
213
  schema: this.schema,
213
214
  });
214
- if (field.type && constants_1.ALIAS_TYPES.includes(field.type) === false) {
215
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.create`, field, {
216
+ collection: collection,
217
+ }, {
218
+ database: trx,
219
+ schema: this.schema,
220
+ accountability: this.accountability,
221
+ });
222
+ if (hookAdjustedField.type && constants_1.ALIAS_TYPES.includes(hookAdjustedField.type) === false) {
215
223
  if (table) {
216
- this.addColumnToTable(table, field);
224
+ this.addColumnToTable(table, hookAdjustedField);
217
225
  }
218
226
  else {
219
227
  await trx.schema.alterTable(collection, (table) => {
220
- this.addColumnToTable(table, field);
228
+ this.addColumnToTable(table, hookAdjustedField);
221
229
  });
222
230
  }
223
231
  }
224
- if (field.meta) {
232
+ if (hookAdjustedField.meta) {
225
233
  await itemsService.createOne({
226
- ...field.meta,
234
+ ...hookAdjustedField.meta,
227
235
  collection: collection,
228
- field: field.field,
229
- });
236
+ field: hookAdjustedField.field,
237
+ }, { emitEvents: false });
230
238
  }
239
+ emitter_1.default.emitAction(`fields.create`, {
240
+ payload: hookAdjustedField,
241
+ key: hookAdjustedField.field,
242
+ collection: collection,
243
+ }, {
244
+ database: (0, database_1.default)(),
245
+ schema: this.schema,
246
+ accountability: this.accountability,
247
+ });
231
248
  });
232
249
  if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
233
250
  await this.cache.clear();
@@ -238,12 +255,23 @@ class FieldsService {
238
255
  if (this.accountability && this.accountability.admin !== true) {
239
256
  throw new exceptions_1.ForbiddenException();
240
257
  }
241
- if (field.schema) {
242
- const existingColumn = await this.schemaInspector.columnInfo(collection, field.field);
243
- if (!(0, lodash_1.isEqual)(existingColumn, field.schema)) {
258
+ const hookAdjustedField = await emitter_1.default.emitFilter(`fields.update`, field, {
259
+ keys: [field.field],
260
+ collection: collection,
261
+ }, {
262
+ database: this.knex,
263
+ schema: this.schema,
264
+ accountability: this.accountability,
265
+ });
266
+ const record = field.meta
267
+ ? await this.knex.select('id').from('directus_fields').where({ collection, field: field.field }).first()
268
+ : null;
269
+ if (hookAdjustedField.schema) {
270
+ const existingColumn = await this.schemaInspector.columnInfo(collection, hookAdjustedField.field);
271
+ if (!(0, lodash_1.isEqual)(existingColumn, hookAdjustedField.schema)) {
244
272
  try {
245
273
  await this.knex.schema.alterTable(collection, (table) => {
246
- if (!field.schema)
274
+ if (!hookAdjustedField.schema)
247
275
  return;
248
276
  this.addColumnToTable(table, field, existingColumn);
249
277
  });
@@ -253,31 +281,35 @@ class FieldsService {
253
281
  }
254
282
  }
255
283
  }
256
- if (field.meta) {
257
- const record = await this.knex
258
- .select('id')
259
- .from('directus_fields')
260
- .where({ collection, field: field.field })
261
- .first();
284
+ if (hookAdjustedField.meta) {
262
285
  if (record) {
263
286
  await this.itemsService.updateOne(record.id, {
264
- ...field.meta,
287
+ ...hookAdjustedField.meta,
265
288
  collection: collection,
266
- field: field.field,
267
- });
289
+ field: hookAdjustedField.field,
290
+ }, { emitEvents: false });
268
291
  }
269
292
  else {
270
293
  await this.itemsService.createOne({
271
- ...field.meta,
294
+ ...hookAdjustedField.meta,
272
295
  collection: collection,
273
- field: field.field,
274
- });
296
+ field: hookAdjustedField.field,
297
+ }, { emitEvents: false });
275
298
  }
276
299
  }
277
300
  if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
278
301
  await this.cache.clear();
279
302
  }
280
303
  await this.systemCache.clear();
304
+ emitter_1.default.emitAction(`fields.update`, {
305
+ payload: hookAdjustedField,
306
+ keys: [hookAdjustedField.field],
307
+ collection: collection,
308
+ }, {
309
+ database: (0, database_1.default)(),
310
+ schema: this.schema,
311
+ accountability: this.accountability,
312
+ });
281
313
  return field.field;
282
314
  }
283
315
  async deleteField(collection, field) {
@@ -402,8 +434,7 @@ class FieldsService {
402
434
  column = table.timestamp(field.field, { useTz: true });
403
435
  }
404
436
  else if (field.type.startsWith('geometry')) {
405
- const helper = (0, geometry_1.getGeometryHelper)();
406
- column = helper.createColumn(table, field);
437
+ column = this.helpers.st.createColumn(table, field);
407
438
  }
408
439
  else {
409
440
  // @ts-ignore
@@ -9,7 +9,7 @@ export declare class FilesService extends ItemsService {
9
9
  uploadOne(stream: NodeJS.ReadableStream, data: Partial<File> & {
10
10
  filename_download: string;
11
11
  storage: string;
12
- }, primaryKey?: PrimaryKey): Promise<PrimaryKey>;
12
+ }, primaryKey?: PrimaryKey, opts?: MutationOptions): Promise<PrimaryKey>;
13
13
  /**
14
14
  * Import a single file from an external URL
15
15
  */
@@ -26,7 +26,7 @@ class FilesService extends items_1.ItemsService {
26
26
  /**
27
27
  * Upload a single new file to the configured storage adapter
28
28
  */
29
- async uploadOne(stream, data, primaryKey) {
29
+ async uploadOne(stream, data, primaryKey, opts) {
30
30
  var _a, _b, _c, _d, _e, _f;
31
31
  const payload = (0, lodash_1.clone)(data);
32
32
  if ('folder' in payload === false) {
@@ -109,15 +109,17 @@ class FilesService extends items_1.ItemsService {
109
109
  if (this.cache && env_1.default.CACHE_AUTO_PURGE) {
110
110
  await this.cache.clear();
111
111
  }
112
- emitter_1.default.emitAction('files.upload', {
113
- payload,
114
- key: primaryKey,
115
- collection: this.collection,
116
- }, {
117
- database: this.knex,
118
- schema: this.schema,
119
- accountability: this.accountability,
120
- });
112
+ if ((opts === null || opts === void 0 ? void 0 : opts.emitEvents) !== false) {
113
+ emitter_1.default.emitAction('files.upload', {
114
+ payload,
115
+ key: primaryKey,
116
+ collection: this.collection,
117
+ }, {
118
+ database: this.knex,
119
+ schema: this.schema,
120
+ accountability: this.accountability,
121
+ });
122
+ }
121
123
  return primaryKey;
122
124
  }
123
125
  /**
@@ -126,7 +128,7 @@ class FilesService extends items_1.ItemsService {
126
128
  async importOne(importURL, body) {
127
129
  var _a, _b, _c;
128
130
  const fileCreatePermissions = (_b = (_a = this.accountability) === null || _a === void 0 ? void 0 : _a.permissions) === null || _b === void 0 ? void 0 : _b.find((permission) => permission.collection === 'directus_files' && permission.action === 'create');
129
- if (((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.admin) !== true && !fileCreatePermissions) {
131
+ if (this.accountability && ((_c = this.accountability) === null || _c === void 0 ? void 0 : _c.admin) !== true && !fileCreatePermissions) {
130
132
  throw new exceptions_1.ForbiddenException();
131
133
  }
132
134
  let fileResponse;
@@ -28,6 +28,7 @@ const folders_1 = require("./folders");
28
28
  const items_1 = require("./items");
29
29
  const permissions_1 = require("./permissions");
30
30
  const presets_1 = require("./presets");
31
+ const notifications_1 = require("./notifications");
31
32
  const relations_1 = require("./relations");
32
33
  const revisions_1 = require("./revisions");
33
34
  const roles_1 = require("./roles");
@@ -725,6 +726,9 @@ class GraphQLService {
725
726
  args: {
726
727
  groupBy: new graphql_1.GraphQLList(graphql_1.GraphQLString),
727
728
  filter: ReadableCollectionFilterTypes[collection.collection],
729
+ limit: {
730
+ type: graphql_1.GraphQLInt,
731
+ },
728
732
  search: {
729
733
  type: graphql_1.GraphQLString,
730
734
  },
@@ -1252,6 +1256,8 @@ class GraphQLService {
1252
1256
  return new permissions_1.PermissionsService(opts);
1253
1257
  case 'directus_presets':
1254
1258
  return new presets_1.PresetsService(opts);
1259
+ case 'directus_notifications':
1260
+ return new notifications_1.NotificationsService(opts);
1255
1261
  case 'directus_revisions':
1256
1262
  return new revisions_1.RevisionsService(opts);
1257
1263
  case 'directus_roles':
@@ -11,6 +11,7 @@ export * from './graphql';
11
11
  export * from './import';
12
12
  export * from './mail';
13
13
  export * from './meta';
14
+ export * from './notifications';
14
15
  export * from './panels';
15
16
  export * from './payload';
16
17
  export * from './permissions';
@@ -24,6 +24,7 @@ __exportStar(require("./graphql"), exports);
24
24
  __exportStar(require("./import"), exports);
25
25
  __exportStar(require("./mail"), exports);
26
26
  __exportStar(require("./meta"), exports);
27
+ __exportStar(require("./notifications"), exports);
27
28
  __exportStar(require("./panels"), exports);
28
29
  __exportStar(require("./payload"), exports);
29
30
  __exportStar(require("./permissions"), exports);