directus 9.0.0 → 9.1.2

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 (48) hide show
  1. package/dist/app.js +8 -4
  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 +3 -3
  5. package/dist/auth/drivers/openid.d.ts +1 -1
  6. package/dist/auth/drivers/openid.js +9 -3
  7. package/dist/controllers/notifications.d.ts +2 -0
  8. package/dist/controllers/notifications.js +147 -0
  9. package/dist/database/helpers/geometry.js +4 -1
  10. package/dist/database/migrations/20211103B-update-special-geometry.js +2 -2
  11. package/dist/database/migrations/20211118A-add-notifications.d.ts +3 -0
  12. package/dist/database/migrations/20211118A-add-notifications.js +28 -0
  13. package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +14 -0
  14. package/dist/database/system-data/collections/collections.yaml +2 -0
  15. package/dist/database/system-data/fields/notifications.yaml +12 -0
  16. package/dist/database/system-data/fields/settings.yaml +7 -7
  17. package/dist/database/system-data/fields/users.yaml +5 -0
  18. package/dist/database/system-data/fields/webhooks.yaml +4 -4
  19. package/dist/database/system-data/relations/relations.yaml +6 -0
  20. package/dist/mailer.js +12 -3
  21. package/dist/middleware/get-permissions.js +3 -102
  22. package/dist/server.js +2 -2
  23. package/dist/services/activity.d.ts +7 -5
  24. package/dist/services/activity.js +82 -3
  25. package/dist/services/authentication.js +15 -1
  26. package/dist/services/collections.js +12 -1
  27. package/dist/services/graphql.js +3 -0
  28. package/dist/services/index.d.ts +1 -0
  29. package/dist/services/index.js +1 -0
  30. package/dist/services/mail/index.js +2 -2
  31. package/dist/services/mail/templates/base.liquid +153 -85
  32. package/dist/services/mail/templates/password-reset.liquid +3 -2
  33. package/dist/services/mail/templates/user-invitation.liquid +4 -4
  34. package/dist/services/notifications.d.ts +12 -0
  35. package/dist/services/notifications.js +41 -0
  36. package/dist/services/users.js +1 -0
  37. package/dist/types/collection.d.ts +1 -0
  38. package/dist/utils/apply-query.js +10 -7
  39. package/dist/utils/apply-snapshot.js +2 -2
  40. package/dist/utils/get-local-type.js +12 -7
  41. package/dist/utils/get-permissions.d.ts +3 -0
  42. package/dist/utils/get-permissions.js +106 -0
  43. package/dist/utils/md.d.ts +4 -0
  44. package/dist/utils/md.js +15 -0
  45. package/dist/utils/sanitize-query.js +3 -3
  46. package/dist/utils/user-name.d.ts +2 -0
  47. package/dist/utils/user-name.js +16 -0
  48. package/package.json +27 -23
package/dist/app.js CHANGED
@@ -40,6 +40,7 @@ const graphql_1 = __importDefault(require("./controllers/graphql"));
40
40
  const items_1 = __importDefault(require("./controllers/items"));
41
41
  const not_found_1 = __importDefault(require("./controllers/not-found"));
42
42
  const panels_1 = __importDefault(require("./controllers/panels"));
43
+ const notifications_1 = __importDefault(require("./controllers/notifications"));
43
44
  const permissions_1 = __importDefault(require("./controllers/permissions"));
44
45
  const presets_1 = __importDefault(require("./controllers/presets"));
45
46
  const relations_1 = __importDefault(require("./controllers/relations"));
@@ -132,11 +133,13 @@ async function createApp() {
132
133
  // Set the App's base path according to the APIs public URL
133
134
  const html = await fs_extra_1.default.readFile(adminPath, 'utf8');
134
135
  const htmlWithBase = html.replace(/<base \/>/, `<base href="${adminUrl.toString({ rootRelative: true })}/" />`);
135
- app.get('/admin', (req, res) => res.send(htmlWithBase));
136
- app.use('/admin', express_1.default.static(path_1.default.join(adminPath, '..')));
137
- app.use('/admin/*', (req, res) => {
136
+ const noCacheIndexHtmlHandler = (req, res) => {
137
+ res.setHeader('Cache-Control', 'no-cache');
138
138
  res.send(htmlWithBase);
139
- });
139
+ };
140
+ app.get('/admin', noCacheIndexHtmlHandler);
141
+ app.use('/admin', express_1.default.static(path_1.default.join(adminPath, '..')));
142
+ app.use('/admin/*', noCacheIndexHtmlHandler);
140
143
  }
141
144
  // use the rate limiter - all routes for now
142
145
  if (env_1.default.RATE_LIMITER_ENABLED === true) {
@@ -161,6 +164,7 @@ async function createApp() {
161
164
  app.use('/files', files_1.default);
162
165
  app.use('/folders', folders_1.default);
163
166
  app.use('/items', items_1.default);
167
+ app.use('/notifications', notifications_1.default);
164
168
  app.use('/panels', panels_1.default);
165
169
  app.use('/permissions', permissions_1.default);
166
170
  app.use('/presets', presets_1.default);
@@ -91,21 +91,16 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
91
91
  });
92
92
  }
93
93
  async fetchUserDn(identifier) {
94
- const { userDn, userAttribute } = this.config;
94
+ const { userDn, userAttribute, userScope } = this.config;
95
95
  return new Promise((resolve, reject) => {
96
96
  // Search for the user in LDAP by attribute
97
- this.bindClient.search(userDn, {
98
- attributes: ['cn'],
99
- filter: `(${userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn'}=${identifier})`,
100
- scope: 'one',
101
- }, (err, res) => {
97
+ this.bindClient.search(userDn, { filter: `(${userAttribute !== null && userAttribute !== void 0 ? userAttribute : 'cn'}=${identifier})`, scope: userScope !== null && userScope !== void 0 ? userScope : 'one' }, (err, res) => {
102
98
  if (err) {
103
99
  reject(handleError(err));
104
100
  return;
105
101
  }
106
102
  res.on('searchEntry', ({ object }) => {
107
- const userCn = typeof object.cn === 'object' ? object.cn[0] : object.cn;
108
- resolve(`cn=${userCn},${userDn}`.toLowerCase());
103
+ resolve(object.dn.toLowerCase());
109
104
  });
110
105
  res.on('error', (err) => {
111
106
  reject(handleError(err));
@@ -147,7 +142,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
147
142
  });
148
143
  }
149
144
  async fetchUserGroups(userDn) {
150
- const { groupDn, groupAttribute } = this.config;
145
+ const { groupDn, groupAttribute, groupScope } = this.config;
151
146
  if (!groupDn) {
152
147
  return Promise.resolve([]);
153
148
  }
@@ -157,7 +152,7 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
157
152
  this.bindClient.search(groupDn, {
158
153
  attributes: ['cn'],
159
154
  filter: `(${groupAttribute !== null && groupAttribute !== void 0 ? groupAttribute : 'member'}=${userDn})`,
160
- scope: 'one',
155
+ scope: groupScope !== null && groupScope !== void 0 ? groupScope : 'one',
161
156
  }, (err, res) => {
162
157
  if (err) {
163
158
  reject(handleError(err));
@@ -244,12 +239,13 @@ class LDAPAuthDriver extends auth_1.AuthDriver {
244
239
  reject(handleError(err));
245
240
  });
246
241
  client.bind(user.external_identifier, password, (err) => {
247
- client.destroy();
248
242
  if (err) {
249
243
  reject(handleError(err));
250
- return;
251
244
  }
252
- resolve();
245
+ else {
246
+ resolve();
247
+ }
248
+ client.destroy();
253
249
  });
254
250
  });
255
251
  }
@@ -13,7 +13,7 @@ export declare class OAuth2AuthDriver extends LocalAuthDriver {
13
13
  generateAuthUrl(codeVerifier: string): string;
14
14
  private fetchUserId;
15
15
  getUserID(payload: Record<string, any>): Promise<string>;
16
- login(user: User, sessionData: SessionData): Promise<SessionData>;
16
+ login(user: User): Promise<SessionData>;
17
17
  refresh(user: User, sessionData: SessionData): Promise<SessionData>;
18
18
  }
19
19
  export declare function createOAuth2AuthRouter(providerName: string): Router;
@@ -80,7 +80,7 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
80
80
  tokenSet = await this.client.oauthCallback(this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: openid_client_1.generators.codeChallenge(payload.codeVerifier) });
81
81
  const issuer = this.client.issuer;
82
82
  if (issuer.metadata.userinfo_endpoint) {
83
- userInfo = await this.client.userinfo(tokenSet);
83
+ userInfo = await this.client.userinfo(tokenSet.access_token);
84
84
  }
85
85
  else if (tokenSet.id_token) {
86
86
  userInfo = tokenSet.claims();
@@ -123,8 +123,8 @@ class OAuth2AuthDriver extends local_1.LocalAuthDriver {
123
123
  });
124
124
  return (await this.fetchUserId(identifier));
125
125
  }
126
- async login(user, sessionData) {
127
- return this.refresh(user, sessionData);
126
+ async login(user) {
127
+ return this.refresh(user, null);
128
128
  }
129
129
  async refresh(user, sessionData) {
130
130
  let authData = user.auth_data;
@@ -13,7 +13,7 @@ export declare class OpenIDAuthDriver extends LocalAuthDriver {
13
13
  generateAuthUrl(codeVerifier: string): Promise<string>;
14
14
  private fetchUserId;
15
15
  getUserID(payload: Record<string, any>): Promise<string>;
16
- login(user: User, sessionData: SessionData): Promise<SessionData>;
16
+ login(user: User): Promise<SessionData>;
17
17
  refresh(user: User, sessionData: SessionData): Promise<SessionData>;
18
18
  }
19
19
  export declare function createOpenIDAuthRouter(providerName: string): Router;
@@ -31,6 +31,12 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
31
31
  this.client = new Promise((resolve, reject) => {
32
32
  openid_client_1.Issuer.discover(issuerUrl)
33
33
  .then((issuer) => {
34
+ const supportedTypes = issuer.metadata.response_types_supported;
35
+ if (!(supportedTypes === null || supportedTypes === void 0 ? void 0 : supportedTypes.includes('code'))) {
36
+ reject(new exceptions_1.InvalidConfigException('OpenID provider does not support required code flow', {
37
+ provider: additionalConfig.provider,
38
+ }));
39
+ }
34
40
  resolve(new issuer.Client({
35
41
  client_id: clientId,
36
42
  client_secret: clientSecret,
@@ -82,7 +88,7 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
82
88
  tokenSet = await client.callback(this.redirectUrl, { code: payload.code, state: payload.state }, { code_verifier: payload.codeVerifier, state: openid_client_1.generators.codeChallenge(payload.codeVerifier) });
83
89
  const issuer = client.issuer;
84
90
  if (issuer.metadata.userinfo_endpoint) {
85
- userInfo = await client.userinfo(tokenSet);
91
+ userInfo = await client.userinfo(tokenSet.access_token);
86
92
  }
87
93
  else {
88
94
  userInfo = tokenSet.claims();
@@ -125,8 +131,8 @@ class OpenIDAuthDriver extends local_1.LocalAuthDriver {
125
131
  });
126
132
  return (await this.fetchUserId(identifier));
127
133
  }
128
- async login(user, sessionData) {
129
- return this.refresh(user, sessionData);
134
+ async login(user) {
135
+ return this.refresh(user, null);
130
136
  }
131
137
  async refresh(user, sessionData) {
132
138
  let authData = user.auth_data;
@@ -0,0 +1,2 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const express_1 = __importDefault(require("express"));
7
+ const exceptions_1 = require("../exceptions");
8
+ const respond_1 = require("../middleware/respond");
9
+ const use_collection_1 = __importDefault(require("../middleware/use-collection"));
10
+ const validate_batch_1 = require("../middleware/validate-batch");
11
+ const services_1 = require("../services");
12
+ const async_handler_1 = __importDefault(require("../utils/async-handler"));
13
+ const router = express_1.default.Router();
14
+ router.use((0, use_collection_1.default)('directus_notifications'));
15
+ router.post('/', (0, async_handler_1.default)(async (req, res, next) => {
16
+ const service = new services_1.NotificationsService({
17
+ accountability: req.accountability,
18
+ schema: req.schema,
19
+ });
20
+ const savedKeys = [];
21
+ if (Array.isArray(req.body)) {
22
+ const keys = await service.createMany(req.body);
23
+ savedKeys.push(...keys);
24
+ }
25
+ else {
26
+ const key = await service.createOne(req.body);
27
+ savedKeys.push(key);
28
+ }
29
+ try {
30
+ if (Array.isArray(req.body)) {
31
+ const records = await service.readMany(savedKeys, req.sanitizedQuery);
32
+ res.locals.payload = { data: records };
33
+ }
34
+ else {
35
+ const record = await service.readOne(savedKeys[0], req.sanitizedQuery);
36
+ res.locals.payload = { data: record };
37
+ }
38
+ }
39
+ catch (error) {
40
+ if (error instanceof exceptions_1.ForbiddenException) {
41
+ return next();
42
+ }
43
+ throw error;
44
+ }
45
+ return next();
46
+ }), respond_1.respond);
47
+ const readHandler = (0, async_handler_1.default)(async (req, res, next) => {
48
+ const service = new services_1.NotificationsService({
49
+ accountability: req.accountability,
50
+ schema: req.schema,
51
+ });
52
+ const metaService = new services_1.MetaService({
53
+ accountability: req.accountability,
54
+ schema: req.schema,
55
+ });
56
+ let result;
57
+ if (req.singleton) {
58
+ result = await service.readSingleton(req.sanitizedQuery);
59
+ }
60
+ else if (req.body.keys) {
61
+ result = await service.readMany(req.body.keys, req.sanitizedQuery);
62
+ }
63
+ else {
64
+ result = await service.readByQuery(req.sanitizedQuery);
65
+ }
66
+ const meta = await metaService.getMetaForQuery('directus_presets', req.sanitizedQuery);
67
+ res.locals.payload = { data: result, meta };
68
+ return next();
69
+ });
70
+ router.get('/', (0, validate_batch_1.validateBatch)('read'), readHandler, respond_1.respond);
71
+ router.search('/', (0, validate_batch_1.validateBatch)('read'), readHandler, respond_1.respond);
72
+ router.get('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
73
+ const service = new services_1.NotificationsService({
74
+ accountability: req.accountability,
75
+ schema: req.schema,
76
+ });
77
+ const record = await service.readOne(req.params.pk, req.sanitizedQuery);
78
+ res.locals.payload = { data: record || null };
79
+ return next();
80
+ }), respond_1.respond);
81
+ router.patch('/', (0, validate_batch_1.validateBatch)('update'), (0, async_handler_1.default)(async (req, res, next) => {
82
+ const service = new services_1.NotificationsService({
83
+ accountability: req.accountability,
84
+ schema: req.schema,
85
+ });
86
+ let keys = [];
87
+ if (req.body.keys) {
88
+ keys = await service.updateMany(req.body.keys, req.body.data);
89
+ }
90
+ else {
91
+ keys = await service.updateByQuery(req.body.query, req.body.data);
92
+ }
93
+ try {
94
+ const result = await service.readMany(keys, req.sanitizedQuery);
95
+ res.locals.payload = { data: result };
96
+ }
97
+ catch (error) {
98
+ if (error instanceof exceptions_1.ForbiddenException) {
99
+ return next();
100
+ }
101
+ throw error;
102
+ }
103
+ return next();
104
+ }), respond_1.respond);
105
+ router.patch('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
106
+ const service = new services_1.NotificationsService({
107
+ accountability: req.accountability,
108
+ schema: req.schema,
109
+ });
110
+ const primaryKey = await service.updateOne(req.params.pk, req.body);
111
+ try {
112
+ const record = await service.readOne(primaryKey, req.sanitizedQuery);
113
+ res.locals.payload = { data: record };
114
+ }
115
+ catch (error) {
116
+ if (error instanceof exceptions_1.ForbiddenException) {
117
+ return next();
118
+ }
119
+ throw error;
120
+ }
121
+ return next();
122
+ }), respond_1.respond);
123
+ router.delete('/', (0, validate_batch_1.validateBatch)('delete'), (0, async_handler_1.default)(async (req, res, next) => {
124
+ const service = new services_1.NotificationsService({
125
+ accountability: req.accountability,
126
+ schema: req.schema,
127
+ });
128
+ if (Array.isArray(req.body)) {
129
+ await service.deleteMany(req.body);
130
+ }
131
+ else if (req.body.keys) {
132
+ await service.deleteMany(req.body.keys);
133
+ }
134
+ else {
135
+ await service.deleteByQuery(req.body.query);
136
+ }
137
+ return next();
138
+ }), respond_1.respond);
139
+ router.delete('/:pk', (0, async_handler_1.default)(async (req, res, next) => {
140
+ const service = new services_1.NotificationsService({
141
+ accountability: req.accountability,
142
+ schema: req.schema,
143
+ });
144
+ await service.deleteOne(req.params.pk);
145
+ return next();
146
+ }), respond_1.respond);
147
+ exports.default = router;
@@ -99,7 +99,7 @@ class KnexSpatial_PG extends KnexSpatial {
99
99
  createColumn(table, field) {
100
100
  var _a;
101
101
  const type = (_a = field.type.split('.')[1]) !== null && _a !== void 0 ? _a : 'geometry';
102
- return table.specificType(field.field, `geometry(${type})`);
102
+ return table.specificType(field.field, `geometry(${type}, 4326)`);
103
103
  }
104
104
  _intersects_bbox(key, geojson) {
105
105
  const geometry = this.fromGeoJSON(geojson);
@@ -110,6 +110,9 @@ class KnexSpatial_MySQL extends KnexSpatial {
110
110
  collect(table, column) {
111
111
  return this.knex.raw(`concat('geometrycollection(', group_concat(? separator ', '), ')'`, this.asText(table, column));
112
112
  }
113
+ fromText(text) {
114
+ return this.knex.raw('st_geomfromtext(?)', text);
115
+ }
113
116
  }
114
117
  class KnexSpatial_Redshift extends KnexSpatial {
115
118
  createColumn(table, field) {
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.down = exports.up = void 0;
4
4
  async function up(knex) {
5
5
  await knex('directus_fields')
6
- .update({ special: knex.raw(`REPLACE(special, 'geometry,', 'geometry.')`) })
6
+ .update({ special: knex.raw(`REPLACE(??, 'geometry,', 'geometry.')`, ['special']) })
7
7
  .where('special', 'like', '%geometry,Point%')
8
8
  .orWhere('special', 'like', '%geometry,LineString%')
9
9
  .orWhere('special', 'like', '%geometry,Polygon%')
@@ -14,7 +14,7 @@ async function up(knex) {
14
14
  exports.up = up;
15
15
  async function down(knex) {
16
16
  await knex('directus_fields')
17
- .update({ special: knex.raw(`REPLACE(special, 'geometry.', 'geometry,')`) })
17
+ .update({ special: knex.raw(`REPLACE(??, 'geometry.', 'geometry,')`, ['special']) })
18
18
  .where('special', 'like', '%geometry.Point%')
19
19
  .orWhere('special', 'like', '%geometry.LineString%')
20
20
  .orWhere('special', 'like', '%geometry.Polygon%')
@@ -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,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.down = exports.up = void 0;
4
+ async function up(knex) {
5
+ await knex.schema.createTable('directus_notifications', (table) => {
6
+ table.increments();
7
+ table.timestamp('timestamp').notNullable();
8
+ table.string('status').defaultTo('inbox');
9
+ table.uuid('recipient').notNullable().references('id').inTable('directus_users').onDelete('CASCADE');
10
+ table.uuid('sender').notNullable().references('id').inTable('directus_users');
11
+ table.string('subject').notNullable();
12
+ table.text('message');
13
+ table.string('collection', 64);
14
+ table.string('item');
15
+ });
16
+ await knex.schema.alterTable('directus_users', (table) => {
17
+ table.boolean('email_notifications').defaultTo(true);
18
+ });
19
+ await knex('directus_users').update({ email_notifications: true });
20
+ }
21
+ exports.up = up;
22
+ async function down(knex) {
23
+ await knex.schema.dropTable('directus_notifications');
24
+ await knex.schema.alterTable('directus_users', (table) => {
25
+ table.dropColumn('email_notifications');
26
+ });
27
+ }
28
+ exports.down = down;
@@ -72,6 +72,20 @@
72
72
  - collection: directus_settings
73
73
  action: read
74
74
 
75
+ - collection: directus_notifications
76
+ action: read
77
+ permissions:
78
+ recipient:
79
+ _eq: $CURRENT_USER
80
+ fields: '*'
81
+
82
+ - collection: directus_notifications
83
+ action: update
84
+ permissions:
85
+ recipient:
86
+ _eq: $CURRENT_USER
87
+ fields: 'status'
88
+
75
89
  - collection: directus_users
76
90
  action: read
77
91
  permissions:
@@ -63,3 +63,5 @@ data:
63
63
  note: $t:directus_collection.directus_dashboards
64
64
  - collection: directus_panels
65
65
  note: $t:directus_collection.directus_panels
66
+ - collection: directus_notifications
67
+ note: $t:directus_collection.directus_notifications
@@ -0,0 +1,12 @@
1
+ table: directus_notifications
2
+
3
+ fields:
4
+ - field: id
5
+ - field: timestamp
6
+ special: date-created
7
+ - field: recipient
8
+ - field: sender
9
+ - field: subject
10
+ - field: message
11
+ - field: collection
12
+ - field: item
@@ -36,7 +36,7 @@ fields:
36
36
 
37
37
  - field: project_color
38
38
  interface: select-color
39
- note: $t:field_options.directus_settings.project_logo_note
39
+ note: $t:field_options.directus_settings.project_color_note
40
40
  translations:
41
41
  language: en-US
42
42
  translations: Brand Color
@@ -44,7 +44,7 @@ fields:
44
44
 
45
45
  - field: project_logo
46
46
  interface: file
47
- note: White 40x40 SVG/PNG
47
+ note: $t:field_options.directus_settings.project_logo_note
48
48
  translations:
49
49
  language: en-US
50
50
  translations: Brand Logo
@@ -77,7 +77,7 @@ fields:
77
77
  language: css
78
78
  lineNumber: true
79
79
  template: |
80
- #app {
80
+ #app, #main-content {
81
81
  --border-radius-outline: 0px !important;
82
82
  --border-radius: 0px !important;
83
83
  }
@@ -170,7 +170,7 @@ fields:
170
170
  onlyOnCreate: false
171
171
  width: full
172
172
  - field: fit
173
- name: Fit
173
+ name: $t:field_options.directus_settings.storage_asset_presets.fit_label
174
174
  type: string
175
175
  schema:
176
176
  is_nullable: false
@@ -217,7 +217,7 @@ fields:
217
217
  step: 1
218
218
  width: half
219
219
  - field: withoutEnlargement
220
- name: Upscaling
220
+ name: $t:field_options.directus_settings.storage_asset_presets.upscaling
221
221
  type: boolean
222
222
  schema:
223
223
  default_value: false
@@ -227,7 +227,7 @@ fields:
227
227
  options:
228
228
  label: $t:no_upscale
229
229
  - field: format
230
- name: Format
230
+ name: $t:format
231
231
  type: string
232
232
  schema:
233
233
  is_nullable: false
@@ -307,7 +307,7 @@ fields:
307
307
  meta:
308
308
  interface: text-input
309
309
  options:
310
- placeholder: Enter the basemap name...
310
+ placeholder: $t:field_options.directus_settings.basemaps_name_placeholder
311
311
  - field: type
312
312
  name: $t:type
313
313
  meta:
@@ -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:
@@ -40,19 +40,19 @@ fields:
40
40
  display_options:
41
41
  showAsDot: true
42
42
  choices:
43
- - text: $t:active
43
+ - text: $t:field_options.directus_webhooks.status_options_active
44
44
  value: active
45
45
  foreground: 'var(--primary-10)'
46
46
  background: 'var(--primary)'
47
- - text: $t:inactive
47
+ - text: $t:field_options.directus_webhooks.status_options_inactive
48
48
  value: inactive
49
49
  foreground: 'var(--foreground-normal)'
50
50
  background: 'var(--background-normal-alt)'
51
51
  options:
52
52
  choices:
53
- - text: $t:active
53
+ - text: $t:field_options.directus_webhooks.status_options_active
54
54
  value: active
55
- - text: $t:inactive
55
+ - text: $t:field_options.directus_webhooks.status_options_inactive
56
56
  value: inactive
57
57
  width: half
58
58
 
@@ -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/mailer.js CHANGED
@@ -11,14 +11,23 @@ let transporter;
11
11
  function getMailer() {
12
12
  if (transporter)
13
13
  return transporter;
14
- if (env_1.default.EMAIL_TRANSPORT === 'sendmail') {
14
+ const transportName = env_1.default.EMAIL_TRANSPORT.toLowerCase();
15
+ if (transportName === 'sendmail') {
15
16
  transporter = nodemailer_1.default.createTransport({
16
17
  sendmail: true,
17
18
  newline: env_1.default.EMAIL_SENDMAIL_NEW_LINE || 'unix',
18
19
  path: env_1.default.EMAIL_SENDMAIL_PATH || '/usr/sbin/sendmail',
19
20
  });
20
21
  }
21
- else if (env_1.default.EMAIL_TRANSPORT.toLowerCase() === 'smtp') {
22
+ else if (transportName === 'ses') {
23
+ const aws = require('@aws-sdk/client-ses');
24
+ const sesOptions = (0, get_config_from_env_1.getConfigFromEnv)('EMAIL_SES_');
25
+ const ses = new aws.SES(sesOptions);
26
+ transporter = nodemailer_1.default.createTransport({
27
+ SES: { ses, aws },
28
+ });
29
+ }
30
+ else if (transportName === 'smtp') {
22
31
  let auth = false;
23
32
  if (env_1.default.EMAIL_SMTP_USER || env_1.default.EMAIL_SMTP_PASSWORD) {
24
33
  auth = {
@@ -37,7 +46,7 @@ function getMailer() {
37
46
  tls,
38
47
  });
39
48
  }
40
- else if (env_1.default.EMAIL_TRANSPORT.toLowerCase() === 'mailgun') {
49
+ else if (transportName === 'mailgun') {
41
50
  const mg = require('nodemailer-mailgun-transport');
42
51
  transporter = nodemailer_1.default.createTransport(mg({
43
52
  auth: {