directus 9.4.1 → 9.5.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.
Files changed (133) hide show
  1. package/README.md +1 -1
  2. package/dist/app.js +20 -1
  3. package/dist/auth/auth.d.ts +2 -1
  4. package/dist/auth/drivers/ldap.js +2 -1
  5. package/dist/auth/drivers/local.js +2 -1
  6. package/dist/auth/drivers/oauth2.js +12 -21
  7. package/dist/auth/drivers/openid.js +14 -3
  8. package/dist/cache.js +1 -3
  9. package/dist/cli/commands/init/questions.d.ts +3 -0
  10. package/dist/cli/commands/init/questions.js +2 -0
  11. package/dist/cli/index.js +1 -1
  12. package/dist/cli/utils/create-db-connection.d.ts +1 -1
  13. package/dist/cli/utils/create-db-connection.js +11 -1
  14. package/dist/cli/utils/drivers.d.ts +1 -0
  15. package/dist/cli/utils/drivers.js +2 -1
  16. package/dist/controllers/activity.js +2 -1
  17. package/dist/controllers/auth.js +5 -4
  18. package/dist/controllers/extensions.js +1 -1
  19. package/dist/controllers/files.js +3 -0
  20. package/dist/controllers/utils.js +2 -0
  21. package/dist/database/helpers/date/index.d.ts +1 -0
  22. package/dist/database/helpers/date/index.js +3 -1
  23. package/dist/database/helpers/geometry/index.d.ts +1 -0
  24. package/dist/database/helpers/geometry/index.js +3 -1
  25. package/dist/database/helpers/index.d.ts +2 -0
  26. package/dist/database/helpers/index.js +2 -0
  27. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +16 -0
  28. package/dist/database/helpers/schema/dialects/cockroachdb.js +16 -0
  29. package/dist/database/helpers/schema/dialects/default.d.ts +3 -0
  30. package/dist/database/helpers/schema/dialects/default.js +7 -0
  31. package/dist/database/helpers/schema/dialects/oracle.d.ts +12 -0
  32. package/dist/database/helpers/schema/dialects/oracle.js +13 -0
  33. package/dist/database/helpers/schema/index.d.ts +7 -0
  34. package/dist/database/helpers/schema/index.js +17 -0
  35. package/dist/database/helpers/schema/types.d.ts +25 -0
  36. package/dist/database/helpers/schema/types.js +89 -0
  37. package/dist/database/index.d.ts +1 -1
  38. package/dist/database/index.js +66 -17
  39. package/dist/database/migrations/20201105B-change-webhook-url-type.js +6 -25
  40. package/dist/database/migrations/20210312A-webhooks-collections-text.js +6 -25
  41. package/dist/database/migrations/20210415A-make-filesize-nullable.js +9 -4
  42. package/dist/database/migrations/20210506A-rename-interfaces.js +1 -1
  43. package/dist/database/migrations/20210510A-restructure-relations.js +12 -4
  44. package/dist/database/migrations/20210525A-add-insights.js +2 -2
  45. package/dist/database/migrations/20210626A-change-filesize-bigint.js +5 -7
  46. package/dist/database/migrations/20210903A-add-auth-provider.js +11 -2
  47. package/dist/database/migrations/20210907A-webhooks-collections-not-null.js +6 -20
  48. package/dist/database/migrations/20210920A-webhooks-url-not-null.js +10 -14
  49. package/dist/database/migrations/20211211A-add-shares.js +2 -2
  50. package/dist/database/migrations/20211230A-add-project-descriptor.d.ts +3 -0
  51. package/dist/database/migrations/20211230A-add-project-descriptor.js +15 -0
  52. package/dist/database/migrations/run.js +1 -1
  53. package/dist/database/run-ast.d.ts +1 -1
  54. package/dist/database/seeds/01-collections.yaml +1 -0
  55. package/dist/database/seeds/02-roles.yaml +1 -0
  56. package/dist/database/seeds/03-users.yaml +1 -0
  57. package/dist/database/system-data/fields/settings.yaml +11 -1
  58. package/dist/database/system-data/relations/index.d.ts +1 -1
  59. package/dist/emitter.d.ts +3 -4
  60. package/dist/emitter.js +2 -8
  61. package/dist/env.js +3 -0
  62. package/dist/exceptions/database/translate.js +1 -0
  63. package/dist/exceptions/index.d.ts +1 -0
  64. package/dist/exceptions/index.js +1 -0
  65. package/dist/exceptions/unsupported-media-type.d.ts +4 -0
  66. package/dist/exceptions/unsupported-media-type.js +10 -0
  67. package/dist/extensions.d.ts +14 -8
  68. package/dist/extensions.js +136 -69
  69. package/dist/logger.js +22 -1
  70. package/dist/middleware/authenticate.js +2 -1
  71. package/dist/middleware/extract-token.js +1 -1
  72. package/dist/middleware/rate-limiter.js +2 -1
  73. package/dist/services/assets.js +3 -3
  74. package/dist/services/authentication.d.ts +2 -2
  75. package/dist/services/authentication.js +7 -2
  76. package/dist/services/authorization.d.ts +2 -3
  77. package/dist/services/collections.d.ts +2 -2
  78. package/dist/services/collections.js +18 -18
  79. package/dist/services/fields.d.ts +2 -3
  80. package/dist/services/fields.js +19 -15
  81. package/dist/services/graphql.d.ts +3 -2
  82. package/dist/services/graphql.js +31 -5
  83. package/dist/services/import.d.ts +2 -2
  84. package/dist/services/import.js +2 -1
  85. package/dist/services/items.d.ts +2 -2
  86. package/dist/services/items.js +22 -18
  87. package/dist/services/mail/index.d.ts +2 -2
  88. package/dist/services/meta.d.ts +2 -2
  89. package/dist/services/payload.d.ts +2 -2
  90. package/dist/services/payload.js +7 -3
  91. package/dist/services/relations.d.ts +2 -2
  92. package/dist/services/server.d.ts +2 -2
  93. package/dist/services/server.js +1 -0
  94. package/dist/services/specifications.d.ts +2 -2
  95. package/dist/services/users.d.ts +2 -3
  96. package/dist/services/utils.d.ts +2 -2
  97. package/dist/types/ast.d.ts +1 -2
  98. package/dist/types/auth.d.ts +1 -3
  99. package/dist/types/index.d.ts +0 -3
  100. package/dist/types/index.js +0 -3
  101. package/dist/types/services.d.ts +1 -3
  102. package/dist/types/snapshot.d.ts +1 -2
  103. package/dist/utils/apply-query.d.ts +1 -2
  104. package/dist/utils/apply-query.js +1 -1
  105. package/dist/utils/apply-snapshot.d.ts +2 -1
  106. package/dist/utils/get-ast-from-query.d.ts +2 -3
  107. package/dist/utils/get-ip-from-req.d.ts +2 -0
  108. package/dist/utils/get-ip-from-req.js +24 -0
  109. package/dist/utils/get-local-type.js +1 -1
  110. package/dist/utils/get-permissions.d.ts +1 -2
  111. package/dist/utils/get-permissions.js +1 -1
  112. package/dist/utils/get-relation-type.d.ts +1 -1
  113. package/dist/utils/get-schema.d.ts +1 -2
  114. package/dist/utils/get-snapshot.d.ts +2 -1
  115. package/dist/utils/md.js +1 -1
  116. package/dist/utils/merge-permissions-for-share.d.ts +1 -2
  117. package/dist/utils/reduce-schema.d.ts +1 -2
  118. package/example.env +8 -0
  119. package/package.json +22 -15
  120. package/dist/cli/index.test.d.ts +0 -1
  121. package/dist/cli/index.test.js +0 -58
  122. package/dist/middleware/cache.test.d.ts +0 -1
  123. package/dist/middleware/cache.test.js +0 -62
  124. package/dist/tests/database/migrations/run.test.d.ts +0 -1
  125. package/dist/tests/database/migrations/run.test.js +0 -29
  126. package/dist/types/extensions.d.ts +0 -43
  127. package/dist/types/extensions.js +0 -2
  128. package/dist/types/relation.d.ts +0 -21
  129. package/dist/types/relation.js +0 -2
  130. package/dist/types/schema.d.ts +0 -32
  131. package/dist/types/schema.js +0 -2
  132. package/dist/utils/get-cache-key.test.d.ts +0 -1
  133. package/dist/utils/get-cache-key.test.js +0 -53
@@ -0,0 +1,15 @@
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.alterTable('directus_settings', (table) => {
6
+ table.string('project_descriptor', 100).nullable();
7
+ });
8
+ }
9
+ exports.up = up;
10
+ async function down(knex) {
11
+ await knex.schema.alterTable('directus_settings', (table) => {
12
+ table.dropColumn('project_descriptor');
13
+ });
14
+ }
15
+ exports.down = down;
@@ -13,7 +13,7 @@ async function run(database, direction, log = true) {
13
13
  let migrationFiles = await fs_extra_1.default.readdir(__dirname);
14
14
  const customMigrationsPath = path_1.default.resolve(env_1.default.EXTENSIONS_PATH, 'migrations');
15
15
  let customMigrationFiles = ((await fs_extra_1.default.pathExists(customMigrationsPath)) && (await fs_extra_1.default.readdir(customMigrationsPath))) || [];
16
- migrationFiles = migrationFiles.filter((file) => file.startsWith('run') === false && file.endsWith('.d.ts') === false);
16
+ migrationFiles = migrationFiles.filter((file) => /^[0-9]+[A-Z]-[^.]+\.(?:js|ts)$/.test(file));
17
17
  customMigrationFiles = customMigrationFiles.filter((file) => file.endsWith('.js'));
18
18
  const completedMigrations = await database.select('*').from('directus_migrations').orderBy('version');
19
19
  const migrations = [
@@ -1,5 +1,5 @@
1
1
  import { Knex } from 'knex';
2
- import { Item, SchemaOverview } from '../types';
2
+ import { Item, SchemaOverview } from '@directus/shared/types';
3
3
  import { AST, NestedCollectionNode } from '../types/ast';
4
4
  declare type RunASTOptions = {
5
5
  /**
@@ -5,6 +5,7 @@ columns:
5
5
  type: string
6
6
  length: 64
7
7
  primary: true
8
+ nullable: false
8
9
  icon:
9
10
  type: string
10
11
  length: 30
@@ -4,6 +4,7 @@ columns:
4
4
  id:
5
5
  type: uuid
6
6
  primary: true
7
+ nullable: false
7
8
  name:
8
9
  type: string
9
10
  length: 100
@@ -4,6 +4,7 @@ columns:
4
4
  id:
5
5
  type: uuid
6
6
  primary: true
7
+ nullable: false
7
8
  first_name:
8
9
  type: string
9
10
  length: 50
@@ -14,6 +14,16 @@ fields:
14
14
  translations: Name
15
15
  width: half
16
16
 
17
+ - field: project_descriptor
18
+ interface: input
19
+ options:
20
+ iconRight: title
21
+ placeholder: $t:field_options.directus_settings.project_name_placeholder
22
+ translations:
23
+ language: en-US
24
+ translations: Name
25
+ width: half
26
+
17
27
  - field: project_url
18
28
  interface: input
19
29
  options:
@@ -22,7 +32,7 @@ fields:
22
32
  translations:
23
33
  language: en-US
24
34
  translations: Website
25
- width: half
35
+ width: full
26
36
 
27
37
  - field: branding_divider
28
38
  interface: presentation-divider
@@ -1,2 +1,2 @@
1
- import { RelationMeta } from '../../../types';
1
+ import { RelationMeta } from '@directus/shared/types';
2
2
  export declare const systemRelationRows: RelationMeta[];
package/dist/emitter.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import { ActionHandler, FilterHandler, HookContext, InitHandler } from './types';
1
+ import { ActionHandler, EventContext, FilterHandler, InitHandler } from '@directus/shared/types';
2
2
  export declare class Emitter {
3
3
  private filterEmitter;
4
4
  private actionEmitter;
5
5
  private initEmitter;
6
6
  constructor();
7
- emitFilter<T>(event: string, payload: T, meta: Record<string, any>, context: HookContext): Promise<T>;
8
- emitAction(event: string, meta: Record<string, any>, context: HookContext): void;
7
+ emitFilter<T>(event: string | string[], payload: T, meta: Record<string, any>, context: EventContext): Promise<T>;
8
+ emitAction(event: string | string[], meta: Record<string, any>, context: EventContext): void;
9
9
  emitInit(event: string, meta: Record<string, any>): Promise<void>;
10
10
  onFilter(event: string, handler: FilterHandler): void;
11
11
  onAction(event: string, handler: ActionHandler): void;
@@ -14,7 +14,6 @@ export declare class Emitter {
14
14
  offAction(event: string, handler: ActionHandler): void;
15
15
  offInit(event: string, handler: InitHandler): void;
16
16
  offAll(): void;
17
- private eventsToEmit;
18
17
  }
19
18
  declare const emitter: Emitter;
20
19
  export default emitter;
package/dist/emitter.js CHANGED
@@ -20,7 +20,7 @@ class Emitter {
20
20
  this.initEmitter = new eventemitter2_1.EventEmitter2(emitterOptions);
21
21
  }
22
22
  async emitFilter(event, payload, meta, context) {
23
- const events = this.eventsToEmit(event, meta);
23
+ const events = Array.isArray(event) ? event : [event];
24
24
  const listeners = events.flatMap((event) => this.filterEmitter.listeners(event));
25
25
  let updatedPayload = payload;
26
26
  for (const listener of listeners) {
@@ -32,7 +32,7 @@ class Emitter {
32
32
  return updatedPayload;
33
33
  }
34
34
  emitAction(event, meta, context) {
35
- const events = this.eventsToEmit(event, meta);
35
+ const events = Array.isArray(event) ? event : [event];
36
36
  for (const event of events) {
37
37
  this.actionEmitter.emitAsync(event, meta, context).catch((err) => {
38
38
  logger_1.default.warn(`An error was thrown while executing action "${event}"`);
@@ -72,12 +72,6 @@ class Emitter {
72
72
  this.actionEmitter.removeAllListeners();
73
73
  this.initEmitter.removeAllListeners();
74
74
  }
75
- eventsToEmit(event, meta) {
76
- if (event.startsWith('items')) {
77
- return [event, `${meta.collection}.${event}`];
78
- }
79
- return [event];
80
- }
81
75
  }
82
76
  exports.Emitter = Emitter;
83
77
  const emitter = new Emitter();
package/dist/env.js CHANGED
@@ -52,6 +52,7 @@ const defaults = {
52
52
  AUTH_PROVIDERS: '',
53
53
  AUTH_DISABLE_DEFAULT: false,
54
54
  EXTENSIONS_PATH: './extensions',
55
+ EXTENSIONS_AUTO_RELOAD: false,
55
56
  EMAIL_FROM: 'no-reply@directus.io',
56
57
  EMAIL_TRANSPORT: 'sendmail',
57
58
  EMAIL_SENDMAIL_NEW_LINE: 'unix',
@@ -61,6 +62,8 @@ const defaults = {
61
62
  ASSETS_TRANSFORM_MAX_CONCURRENT: 1,
62
63
  ASSETS_TRANSFORM_IMAGE_MAX_DIMENSION: 6000,
63
64
  ASSETS_TRANSFORM_MAX_OPERATIONS: 5,
65
+ IP_TRUST_PROXY: true,
66
+ IP_CUSTOM_HEADER: false,
64
67
  SERVE_APP: true,
65
68
  };
66
69
  // Allows us to force certain environment variable into a type, instead of relying
@@ -46,6 +46,7 @@ async function translateDatabaseError(error) {
46
46
  case 'mysql':
47
47
  defaultError = (0, mysql_1.extractError)(error);
48
48
  break;
49
+ case 'cockroachdb':
49
50
  case 'postgres':
50
51
  defaultError = (0, postgres_1.extractError)(error);
51
52
  break;
@@ -14,5 +14,6 @@ export * from './range-not-satisfiable';
14
14
  export * from './route-not-found';
15
15
  export * from './service-unavailable';
16
16
  export * from './unprocessable-entity';
17
+ export * from './unsupported-media-type';
17
18
  export * from './user-suspended';
18
19
  export * from './unexpected-response';
@@ -26,5 +26,6 @@ __exportStar(require("./range-not-satisfiable"), exports);
26
26
  __exportStar(require("./route-not-found"), exports);
27
27
  __exportStar(require("./service-unavailable"), exports);
28
28
  __exportStar(require("./unprocessable-entity"), exports);
29
+ __exportStar(require("./unsupported-media-type"), exports);
29
30
  __exportStar(require("./user-suspended"), exports);
30
31
  __exportStar(require("./unexpected-response"), exports);
@@ -0,0 +1,4 @@
1
+ import { BaseException } from '@directus/shared/exceptions';
2
+ export declare class UnsupportedMediaTypeException extends BaseException {
3
+ constructor(message: string, extensions?: Record<string, unknown>);
4
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UnsupportedMediaTypeException = void 0;
4
+ const exceptions_1 = require("@directus/shared/exceptions");
5
+ class UnsupportedMediaTypeException extends exceptions_1.BaseException {
6
+ constructor(message, extensions) {
7
+ super(message, 415, 'UNSUPPORTED_MEDIA_TYPE', extensions);
8
+ }
9
+ }
10
+ exports.UnsupportedMediaTypeException = UnsupportedMediaTypeException;
@@ -1,23 +1,29 @@
1
1
  import { Router } from 'express';
2
2
  import { AppExtensionType, ExtensionType } from '@directus/shared/types';
3
3
  export declare function getExtensionManager(): ExtensionManager;
4
+ declare type Options = {
5
+ schedule: boolean;
6
+ watch: boolean;
7
+ };
4
8
  declare class ExtensionManager {
5
- private isInitialized;
9
+ private isLoaded;
10
+ private options;
6
11
  private extensions;
7
12
  private appExtensions;
8
- private apiHooks;
9
- private apiEndpoints;
13
+ private apiExtensions;
10
14
  private apiEmitter;
11
15
  private endpointRouter;
12
- private isScheduleHookEnabled;
16
+ private watcher;
13
17
  constructor();
14
- initialize({ schedule }?: {
15
- schedule: boolean;
16
- }): Promise<void>;
18
+ initialize(options?: Partial<Options>): Promise<void>;
17
19
  reload(): Promise<void>;
18
- listExtensions(type?: ExtensionType): string[];
20
+ getExtensionsList(type?: ExtensionType): string[];
19
21
  getAppExtensions(type: AppExtensionType): string | undefined;
20
22
  getEndpointRouter(): Router;
23
+ private load;
24
+ private unload;
25
+ private initializeWatcher;
26
+ private updateWatchedExtensions;
21
27
  private getExtensions;
22
28
  private generateExtensionBundles;
23
29
  private getSharedDepsMapping;
@@ -45,6 +45,8 @@ const plugin_alias_1 = __importDefault(require("@rollup/plugin-alias"));
45
45
  const url_1 = require("./utils/url");
46
46
  const get_module_default_1 = __importDefault(require("./utils/get-module-default"));
47
47
  const lodash_1 = require("lodash");
48
+ const chokidar_1 = __importDefault(require("chokidar"));
49
+ const utils_1 = require("@directus/shared/utils");
48
50
  let extensionManager;
49
51
  function getExtensionManager() {
50
52
  if (extensionManager) {
@@ -54,21 +56,73 @@ function getExtensionManager() {
54
56
  return extensionManager;
55
57
  }
56
58
  exports.getExtensionManager = getExtensionManager;
59
+ const defaultOptions = {
60
+ schedule: true,
61
+ watch: env_1.default.EXTENSIONS_AUTO_RELOAD && env_1.default.NODE_ENV !== 'development',
62
+ };
57
63
  class ExtensionManager {
58
64
  constructor() {
59
- this.isInitialized = false;
65
+ this.isLoaded = false;
60
66
  this.extensions = [];
61
67
  this.appExtensions = {};
62
- this.apiHooks = [];
63
- this.apiEndpoints = [];
64
- this.isScheduleHookEnabled = true;
68
+ this.apiExtensions = { hooks: [], endpoints: [] };
69
+ this.watcher = null;
70
+ this.options = defaultOptions;
65
71
  this.apiEmitter = new emitter_1.Emitter();
66
72
  this.endpointRouter = (0, express_1.Router)();
67
73
  }
68
- async initialize({ schedule } = { schedule: true }) {
69
- this.isScheduleHookEnabled = schedule;
70
- if (this.isInitialized)
71
- return;
74
+ async initialize(options = {}) {
75
+ this.options = {
76
+ ...defaultOptions,
77
+ ...options,
78
+ };
79
+ this.initializeWatcher();
80
+ if (!this.isLoaded) {
81
+ await this.load();
82
+ this.updateWatchedExtensions(this.extensions);
83
+ const loadedExtensions = this.getExtensionsList();
84
+ if (loadedExtensions.length > 0) {
85
+ logger_1.default.info(`Loaded extensions: ${loadedExtensions.join(', ')}`);
86
+ }
87
+ }
88
+ }
89
+ async reload() {
90
+ if (this.isLoaded) {
91
+ logger_1.default.info('Reloading extensions');
92
+ const prevExtensions = (0, lodash_1.clone)(this.extensions);
93
+ await this.unload();
94
+ await this.load();
95
+ const added = this.extensions.filter((extension) => !prevExtensions.some((prevExtension) => extension.path === prevExtension.path));
96
+ const removed = prevExtensions.filter((prevExtension) => !this.extensions.some((extension) => prevExtension.path === extension.path));
97
+ this.updateWatchedExtensions(added, removed);
98
+ const addedExtensions = added.map((extension) => extension.name);
99
+ const removedExtensions = removed.map((extension) => extension.name);
100
+ if (addedExtensions.length > 0) {
101
+ logger_1.default.info(`Added extensions: ${addedExtensions.join(', ')}`);
102
+ }
103
+ if (removedExtensions.length > 0) {
104
+ logger_1.default.info(`Removed extensions: ${removedExtensions.join(', ')}`);
105
+ }
106
+ }
107
+ else {
108
+ logger_1.default.warn('Extensions have to be loaded before they can be reloaded');
109
+ }
110
+ }
111
+ getExtensionsList(type) {
112
+ if (type === undefined) {
113
+ return this.extensions.map((extension) => extension.name);
114
+ }
115
+ else {
116
+ return this.extensions.filter((extension) => extension.type === type).map((extension) => extension.name);
117
+ }
118
+ }
119
+ getAppExtensions(type) {
120
+ return this.appExtensions[type];
121
+ }
122
+ getEndpointRouter() {
123
+ return this.endpointRouter;
124
+ }
125
+ async load() {
72
126
  try {
73
127
  await (0, node_1.ensureExtensionDirs)(env_1.default.EXTENSIONS_PATH, env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES);
74
128
  this.extensions = await this.getExtensions();
@@ -82,38 +136,42 @@ class ExtensionManager {
82
136
  if (env_1.default.SERVE_APP) {
83
137
  this.appExtensions = await this.generateExtensionBundles();
84
138
  }
85
- const loadedExtensions = this.listExtensions();
86
- if (loadedExtensions.length > 0) {
87
- logger_1.default.info(`Loaded extensions: ${loadedExtensions.join(', ')}`);
88
- }
89
- this.isInitialized = true;
139
+ this.isLoaded = true;
90
140
  }
91
- async reload() {
92
- if (!this.isInitialized)
93
- return;
94
- logger_1.default.info('Reloading extensions');
141
+ async unload() {
95
142
  this.unregisterHooks();
96
143
  this.unregisterEndpoints();
97
144
  this.apiEmitter.offAll();
98
145
  if (env_1.default.SERVE_APP) {
99
146
  this.appExtensions = {};
100
147
  }
101
- this.isInitialized = false;
102
- await this.initialize();
148
+ this.isLoaded = false;
103
149
  }
104
- listExtensions(type) {
105
- if (type === undefined) {
106
- return this.extensions.map((extension) => extension.name);
107
- }
108
- else {
109
- return this.extensions.filter((extension) => extension.type === type).map((extension) => extension.name);
150
+ initializeWatcher() {
151
+ if (this.options.watch && !this.watcher) {
152
+ logger_1.default.info('Watching extensions for changes...');
153
+ const localExtensionPaths = (env_1.default.SERVE_APP ? constants_1.EXTENSION_TYPES : constants_1.API_EXTENSION_TYPES).map((type) => path_1.default.posix.join(path_1.default.relative('.', env_1.default.EXTENSIONS_PATH).split(path_1.default.sep).join(path_1.default.posix.sep), (0, utils_1.pluralize)(type), '*', 'index.js'));
154
+ this.watcher = chokidar_1.default.watch([path_1.default.resolve('.', 'package.json'), ...localExtensionPaths], {
155
+ ignoreInitial: true,
156
+ });
157
+ this.watcher
158
+ .on('add', () => this.reload())
159
+ .on('change', () => this.reload())
160
+ .on('unlink', () => this.reload());
110
161
  }
111
162
  }
112
- getAppExtensions(type) {
113
- return this.appExtensions[type];
114
- }
115
- getEndpointRouter() {
116
- return this.endpointRouter;
163
+ updateWatchedExtensions(added, removed = []) {
164
+ if (this.watcher) {
165
+ const toPackageExtensionPaths = (extensions) => extensions
166
+ .filter((extension) => !extension.local)
167
+ .map((extension) => extension.type !== 'pack'
168
+ ? path_1.default.resolve(extension.path, extension.entrypoint || '')
169
+ : path_1.default.resolve(extension.path, 'package.json'));
170
+ const addedPackageExtensionPaths = toPackageExtensionPaths(added);
171
+ const removedPackageExtensionPaths = toPackageExtensionPaths(removed);
172
+ this.watcher.add(addedPackageExtensionPaths);
173
+ this.watcher.unwatch(removedPackageExtensionPaths);
174
+ }
117
175
  }
118
176
  async getExtensions() {
119
177
  const packageExtensions = await (0, node_1.getPackageExtensions)('.', env_1.default.SERVE_APP ? constants_1.EXTENSION_PACKAGE_TYPES : constants_1.API_EXTENSION_PACKAGE_TYPES);
@@ -129,15 +187,21 @@ class ExtensionManager {
129
187
  const bundles = {};
130
188
  for (const extensionType of constants_1.APP_EXTENSION_TYPES) {
131
189
  const entry = (0, node_1.generateExtensionsEntry)(extensionType, this.extensions);
132
- const bundle = await (0, rollup_1.rollup)({
133
- input: 'entry',
134
- external: Object.values(sharedDepsMapping),
135
- makeAbsoluteExternalsRelative: false,
136
- plugins: [(0, plugin_virtual_1.default)({ entry }), (0, plugin_alias_1.default)({ entries: internalImports })],
137
- });
138
- const { output } = await bundle.generate({ format: 'es', compact: true });
139
- bundles[extensionType] = output[0].code;
140
- await bundle.close();
190
+ try {
191
+ const bundle = await (0, rollup_1.rollup)({
192
+ input: 'entry',
193
+ external: Object.values(sharedDepsMapping),
194
+ makeAbsoluteExternalsRelative: false,
195
+ plugins: [(0, plugin_virtual_1.default)({ entry }), (0, plugin_alias_1.default)({ entries: internalImports })],
196
+ });
197
+ const { output } = await bundle.generate({ format: 'es', compact: true });
198
+ bundles[extensionType] = output[0].code;
199
+ await bundle.close();
200
+ }
201
+ catch (error) {
202
+ logger_1.default.warn(`Couldn't bundle App extensions`);
203
+ logger_1.default.warn(error);
204
+ }
141
205
  }
142
206
  return bundles;
143
207
  }
@@ -185,38 +249,39 @@ class ExtensionManager {
185
249
  const hookPath = path_1.default.resolve(hook.path, hook.entrypoint || '');
186
250
  const hookInstance = require(hookPath);
187
251
  const register = (0, get_module_default_1.default)(hookInstance);
252
+ const hookHandler = {
253
+ path: hookPath,
254
+ events: [],
255
+ };
188
256
  const registerFunctions = {
189
257
  filter: (event, handler) => {
190
258
  emitter_1.default.onFilter(event, handler);
191
- this.apiHooks.push({
259
+ hookHandler.events.push({
192
260
  type: 'filter',
193
- path: hookPath,
194
- event,
261
+ name: event,
195
262
  handler,
196
263
  });
197
264
  },
198
265
  action: (event, handler) => {
199
266
  emitter_1.default.onAction(event, handler);
200
- this.apiHooks.push({
267
+ hookHandler.events.push({
201
268
  type: 'action',
202
- path: hookPath,
203
- event,
269
+ name: event,
204
270
  handler,
205
271
  });
206
272
  },
207
273
  init: (event, handler) => {
208
274
  emitter_1.default.onInit(event, handler);
209
- this.apiHooks.push({
275
+ hookHandler.events.push({
210
276
  type: 'init',
211
- path: hookPath,
212
- event,
277
+ name: event,
213
278
  handler,
214
279
  });
215
280
  },
216
281
  schedule: (cron, handler) => {
217
282
  if ((0, node_cron_1.validate)(cron)) {
218
283
  const task = (0, node_cron_1.schedule)(cron, async () => {
219
- if (this.isScheduleHookEnabled) {
284
+ if (this.options.schedule) {
220
285
  try {
221
286
  await handler();
222
287
  }
@@ -225,9 +290,8 @@ class ExtensionManager {
225
290
  }
226
291
  }
227
292
  });
228
- this.apiHooks.push({
293
+ hookHandler.events.push({
229
294
  type: 'schedule',
230
- path: hookPath,
231
295
  task,
232
296
  });
233
297
  }
@@ -245,6 +309,7 @@ class ExtensionManager {
245
309
  logger: logger_1.default,
246
310
  getSchema: get_schema_1.getSchema,
247
311
  });
312
+ this.apiExtensions.hooks.push(hookHandler);
248
313
  }
249
314
  registerEndpoint(endpoint, router) {
250
315
  const endpointPath = path_1.default.resolve(endpoint.path, endpoint.entrypoint || '');
@@ -263,35 +328,37 @@ class ExtensionManager {
263
328
  logger: logger_1.default,
264
329
  getSchema: get_schema_1.getSchema,
265
330
  });
266
- this.apiEndpoints.push({
331
+ this.apiExtensions.endpoints.push({
267
332
  path: endpointPath,
268
333
  });
269
334
  }
270
335
  unregisterHooks() {
271
- for (const hook of this.apiHooks) {
272
- switch (hook.type) {
273
- case 'filter':
274
- emitter_1.default.offFilter(hook.event, hook.handler);
275
- break;
276
- case 'action':
277
- emitter_1.default.offAction(hook.event, hook.handler);
278
- break;
279
- case 'init':
280
- emitter_1.default.offInit(hook.event, hook.handler);
281
- break;
282
- case 'schedule':
283
- hook.task.destroy();
284
- break;
336
+ for (const hook of this.apiExtensions.hooks) {
337
+ for (const event of hook.events) {
338
+ switch (event.type) {
339
+ case 'filter':
340
+ emitter_1.default.offFilter(event.name, event.handler);
341
+ break;
342
+ case 'action':
343
+ emitter_1.default.offAction(event.name, event.handler);
344
+ break;
345
+ case 'init':
346
+ emitter_1.default.offInit(event.name, event.handler);
347
+ break;
348
+ case 'schedule':
349
+ event.task.stop();
350
+ break;
351
+ }
285
352
  }
286
353
  delete require.cache[require.resolve(hook.path)];
287
354
  }
288
- this.apiHooks = [];
355
+ this.apiExtensions.hooks = [];
289
356
  }
290
357
  unregisterEndpoints() {
291
- for (const endpoint of this.apiEndpoints) {
358
+ for (const endpoint of this.apiExtensions.endpoints) {
292
359
  delete require.cache[require.resolve(endpoint.path)];
293
360
  }
294
361
  this.endpointRouter.stack = [];
295
- this.apiEndpoints = [];
362
+ this.apiExtensions.endpoints = [];
296
363
  }
297
364
  }
package/dist/logger.js CHANGED
@@ -25,6 +25,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
25
25
  exports.expressLogger = void 0;
26
26
  const pino_1 = __importDefault(require("pino"));
27
27
  const pino_http_1 = __importStar(require("pino-http"));
28
+ const get_config_from_env_1 = require("./utils/get-config-from-env");
28
29
  const url_1 = require("url");
29
30
  const env_1 = __importDefault(require("./env"));
30
31
  const pinoOptions = {
@@ -38,9 +39,29 @@ if (env_1.default.LOG_STYLE !== 'raw') {
38
39
  pinoOptions.prettyPrint = true;
39
40
  pinoOptions.prettifier = require('pino-colada');
40
41
  }
41
- const logger = (0, pino_1.default)(pinoOptions);
42
+ const loggerEnvConfig = (0, get_config_from_env_1.getConfigFromEnv)('LOGGER_', 'LOGGER_HTTP');
43
+ // Expose custom log levels into formatter function
44
+ if (loggerEnvConfig.levels) {
45
+ const customLogLevels = {};
46
+ for (const el of loggerEnvConfig.levels.split(',')) {
47
+ const key_val = el.split(':');
48
+ customLogLevels[key_val[0].trim()] = key_val[1].trim();
49
+ }
50
+ pinoOptions.formatters = {
51
+ level(label, number) {
52
+ return {
53
+ severity: customLogLevels[label] || 'info',
54
+ level: number,
55
+ };
56
+ },
57
+ };
58
+ delete loggerEnvConfig.levels;
59
+ }
60
+ const logger = (0, pino_1.default)(Object.assign(pinoOptions, loggerEnvConfig));
61
+ const httpLoggerEnvConfig = (0, get_config_from_env_1.getConfigFromEnv)('LOGGER_HTTP', ['LOGGER_HTTP_LOGGER']);
42
62
  exports.expressLogger = (0, pino_http_1.default)({
43
63
  logger,
64
+ ...httpLoggerEnvConfig,
44
65
  }, {
45
66
  serializers: {
46
67
  req(request) {
@@ -27,6 +27,7 @@ const database_1 = __importDefault(require("../database"));
27
27
  const env_1 = __importDefault(require("../env"));
28
28
  const exceptions_1 = require("../exceptions");
29
29
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
30
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
30
31
  const is_directus_jwt_1 = __importDefault(require("../utils/is-directus-jwt"));
31
32
  /**
32
33
  * Verify the passed JWT and assign the user ID and role to `req`
@@ -37,7 +38,7 @@ const authenticate = (0, async_handler_1.default)(async (req, res, next) => {
37
38
  role: null,
38
39
  admin: false,
39
40
  app: false,
40
- ip: req.ip.startsWith('::ffff:') ? req.ip.substring(7) : req.ip,
41
+ ip: (0, get_ip_from_req_1.getIPFromReq)(req),
41
42
  userAgent: req.get('user-agent'),
42
43
  };
43
44
  const database = (0, database_1.default)();
@@ -15,7 +15,7 @@ const extractToken = (req, res, next) => {
15
15
  }
16
16
  if (req.headers && req.headers.authorization) {
17
17
  const parts = req.headers.authorization.split(' ');
18
- if (parts.length === 2 && parts[0] === 'Bearer') {
18
+ if (parts.length === 2 && parts[0].toLowerCase() === 'bearer') {
19
19
  token = parts[1];
20
20
  }
21
21
  }
@@ -9,6 +9,7 @@ const env_1 = __importDefault(require("../env"));
9
9
  const exceptions_1 = require("../exceptions");
10
10
  const rate_limiter_1 = require("../rate-limiter");
11
11
  const async_handler_1 = __importDefault(require("../utils/async-handler"));
12
+ const get_ip_from_req_1 = require("../utils/get-ip-from-req");
12
13
  const validate_env_1 = require("../utils/validate-env");
13
14
  let checkRateLimit = (req, res, next) => next();
14
15
  if (env_1.default.RATE_LIMITER_ENABLED === true) {
@@ -16,7 +17,7 @@ if (env_1.default.RATE_LIMITER_ENABLED === true) {
16
17
  exports.rateLimiter = (0, rate_limiter_1.createRateLimiter)();
17
18
  checkRateLimit = (0, async_handler_1.default)(async (req, res, next) => {
18
19
  try {
19
- await exports.rateLimiter.consume(req.ip, 1);
20
+ await exports.rateLimiter.consume((0, get_ip_from_req_1.getIPFromReq)(req), 1);
20
21
  }
21
22
  catch (rateLimiterRes) {
22
23
  if (rateLimiterRes instanceof Error)