@directus/api 14.1.2 → 16.0.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.
- package/dist/app.js +8 -6
- package/dist/auth/drivers/ldap.js +7 -4
- package/dist/auth/drivers/local.js +3 -2
- package/dist/auth/drivers/oauth2.js +11 -5
- package/dist/auth/drivers/openid.js +11 -5
- package/dist/auth/drivers/saml.js +6 -4
- package/dist/auth.js +7 -4
- package/dist/bus/index.d.ts +1 -0
- package/dist/bus/index.js +1 -0
- package/dist/bus/lib/use-bus.d.ts +9 -0
- package/dist/bus/lib/use-bus.js +21 -0
- package/dist/cache.js +9 -9
- package/dist/cli/commands/bootstrap/index.js +6 -2
- package/dist/cli/commands/count/index.js +2 -1
- package/dist/cli/commands/database/install.js +2 -1
- package/dist/cli/commands/database/migrate.js +2 -1
- package/dist/cli/commands/roles/create.js +2 -1
- package/dist/cli/commands/schema/apply.js +46 -34
- package/dist/cli/commands/schema/snapshot.js +6 -5
- package/dist/cli/commands/users/create.js +4 -3
- package/dist/cli/commands/users/passwd.js +5 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/load-extensions.js +4 -2
- package/dist/cli/utils/create-env/env-stub.liquid +1 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +4 -1
- package/dist/controllers/assets.js +5 -3
- package/dist/controllers/auth.js +5 -4
- package/dist/controllers/extensions.js +18 -6
- package/dist/controllers/files.js +3 -3
- package/dist/controllers/schema.js +3 -2
- package/dist/controllers/shares.js +3 -3
- package/dist/database/helpers/index.d.ts +1 -1
- package/dist/database/index.d.ts +2 -1
- package/dist/database/index.js +11 -3
- package/dist/database/migrations/20210518A-add-foreign-key-constraints.js +3 -1
- package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -1
- package/dist/database/migrations/20210802A-replace-groups.js +2 -1
- package/dist/database/migrations/20230721A-require-shares-fields.js +2 -1
- package/dist/database/migrations/20231215A-add-focalpoints.d.ts +3 -0
- package/dist/database/migrations/20231215A-add-focalpoints.js +12 -0
- package/dist/database/migrations/run.js +2 -1
- package/dist/database/run-ast.js +5 -2
- package/dist/database/system-data/app-access-permissions/app-access-permissions.yaml +0 -7
- package/dist/database/system-data/fields/files.yaml +16 -0
- package/dist/database/system-data/relations/relations.yaml +4 -0
- package/dist/emitter.d.ts +1 -0
- package/dist/emitter.js +4 -1
- package/dist/extensions/lib/get-extensions-path.d.ts +1 -1
- package/dist/extensions/lib/get-extensions-path.js +2 -1
- package/dist/extensions/lib/get-extensions.d.ts +1 -1
- package/dist/extensions/lib/get-extensions.js +32 -8
- package/dist/extensions/lib/get-shared-deps-mapping.js +7 -5
- package/dist/extensions/lib/sandbox/register/call-reference.js +4 -2
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -0
- package/dist/extensions/lib/sandbox/sdk/generators/log.js +2 -1
- package/dist/extensions/lib/sync-extensions.js +6 -4
- package/dist/extensions/manager.d.ts +5 -0
- package/dist/extensions/manager.js +84 -34
- package/dist/flows.js +13 -7
- package/dist/logger.d.ts +7 -6
- package/dist/logger.js +116 -91
- package/dist/mailer.js +4 -2
- package/dist/middleware/cache.js +4 -2
- package/dist/middleware/check-ip.js +25 -6
- package/dist/middleware/cors.js +2 -1
- package/dist/middleware/error-handler.js +5 -5
- package/dist/middleware/rate-limiter-global.js +4 -2
- package/dist/middleware/rate-limiter-ip.js +16 -12
- package/dist/middleware/respond.js +4 -2
- package/dist/operations/log/index.js +2 -1
- package/dist/rate-limiter.d.ts +2 -1
- package/dist/rate-limiter.js +5 -2
- package/dist/redis/index.d.ts +3 -0
- package/dist/redis/index.js +3 -0
- package/dist/redis/lib/create-redis.d.ts +7 -0
- package/dist/redis/lib/create-redis.js +12 -0
- package/dist/redis/lib/use-redis.d.ts +16 -0
- package/dist/redis/lib/use-redis.js +22 -0
- package/dist/redis/utils/redis-config-available.d.ts +4 -0
- package/dist/redis/utils/redis-config-available.js +8 -0
- package/dist/request/request-interceptor.js +7 -5
- package/dist/request/response-interceptor.js +2 -2
- package/dist/request/validate-ip.d.ts +1 -1
- package/dist/request/validate-ip.js +23 -7
- package/dist/server.d.ts +2 -0
- package/dist/server.js +11 -7
- package/dist/services/activity.js +5 -4
- package/dist/services/assets.d.ts +2 -0
- package/dist/services/assets.js +9 -4
- package/dist/services/authentication.js +17 -9
- package/dist/services/collections.js +5 -4
- package/dist/services/extensions.d.ts +15 -9
- package/dist/services/extensions.js +75 -40
- package/dist/services/fields.js +9 -4
- package/dist/services/files.d.ts +2 -2
- package/dist/services/files.js +22 -14
- package/dist/services/graphql/index.js +96 -18
- package/dist/services/graphql/subscription.js +2 -2
- package/dist/services/graphql/types/bigint.js +16 -5
- package/dist/services/graphql/utils/process-error.d.ts +4 -1
- package/dist/services/graphql/utils/process-error.js +10 -8
- package/dist/services/import-export/index.js +5 -3
- package/dist/services/items.js +12 -8
- package/dist/services/mail/index.js +4 -2
- package/dist/services/notifications.js +7 -3
- package/dist/services/payload.js +3 -3
- package/dist/services/relations.js +19 -10
- package/dist/services/server.js +7 -7
- package/dist/services/shares.js +3 -2
- package/dist/services/specifications.js +5 -4
- package/dist/services/users.js +24 -13
- package/dist/services/versions.js +6 -5
- package/dist/services/webhooks.d.ts +2 -2
- package/dist/services/webhooks.js +2 -2
- package/dist/services/websocket.d.ts +1 -1
- package/dist/services/websocket.js +4 -3
- package/dist/storage/register-drivers.js +2 -1
- package/dist/storage/register-locations.js +2 -1
- package/dist/synchronization.js +3 -1
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +4 -0
- package/dist/telemetry/lib/get-report.d.ts +5 -0
- package/dist/telemetry/lib/get-report.js +42 -0
- package/dist/telemetry/lib/init-telemetry.d.ts +11 -0
- package/dist/telemetry/lib/init-telemetry.js +30 -0
- package/dist/telemetry/lib/send-report.d.ts +5 -0
- package/dist/telemetry/lib/send-report.js +23 -0
- package/dist/telemetry/lib/track.d.ts +10 -0
- package/dist/telemetry/lib/track.js +30 -0
- package/dist/telemetry/types/report.d.ts +58 -0
- package/dist/telemetry/types/report.js +1 -0
- package/dist/telemetry/utils/get-item-count.d.ts +26 -0
- package/dist/telemetry/utils/get-item-count.js +36 -0
- package/dist/telemetry/utils/get-random-wait-time.d.ts +5 -0
- package/dist/telemetry/utils/get-random-wait-time.js +5 -0
- package/dist/telemetry/utils/get-user-count.d.ts +7 -0
- package/dist/telemetry/utils/get-user-count.js +30 -0
- package/dist/telemetry/utils/get-user-item-count.d.ts +13 -0
- package/dist/telemetry/utils/get-user-item-count.js +18 -0
- package/dist/types/assets.d.ts +2 -0
- package/dist/utils/apply-diff.js +2 -1
- package/dist/utils/apply-query.js +2 -2
- package/dist/utils/delete-from-require-cache.js +2 -1
- package/dist/utils/get-accountability-for-token.js +3 -2
- package/dist/utils/get-auth-providers.js +2 -1
- package/dist/utils/get-cache-headers.js +5 -2
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-config-from-env.js +2 -1
- package/dist/utils/get-default-value.js +4 -3
- package/dist/utils/get-ip-from-req.d.ts +1 -1
- package/dist/utils/get-ip-from-req.js +5 -3
- package/dist/utils/get-permissions.js +5 -3
- package/dist/utils/get-schema.js +5 -2
- package/dist/utils/get-snapshot-diff.js +7 -9
- package/dist/utils/get-snapshot.js +5 -5
- package/dist/utils/get-versioned-hash.js +1 -1
- package/dist/utils/ip-in-networks.d.ts +6 -0
- package/dist/utils/ip-in-networks.js +13 -0
- package/dist/utils/is-url-allowed.js +2 -1
- package/dist/utils/job-queue.d.ts +1 -0
- package/dist/utils/job-queue.js +3 -0
- package/dist/utils/md.d.ts +1 -1
- package/dist/utils/md.js +3 -2
- package/dist/utils/sanitize-query.js +7 -2
- package/dist/utils/sanitize-schema.d.ts +1 -1
- package/dist/utils/should-clear-cache.js +2 -1
- package/dist/utils/should-skip-cache.js +2 -1
- package/dist/utils/transformations.js +95 -12
- package/dist/utils/validate-env.js +4 -2
- package/dist/utils/validate-query.js +8 -3
- package/dist/utils/validate-snapshot.js +3 -3
- package/dist/utils/validate-storage.js +4 -2
- package/dist/webhooks.js +4 -3
- package/dist/websocket/controllers/base.d.ts +2 -0
- package/dist/websocket/controllers/base.js +12 -6
- package/dist/websocket/controllers/graphql.d.ts +2 -0
- package/dist/websocket/controllers/graphql.js +5 -3
- package/dist/websocket/controllers/hooks.js +3 -2
- package/dist/websocket/controllers/index.d.ts +2 -0
- package/dist/websocket/controllers/index.js +4 -2
- package/dist/websocket/controllers/rest.d.ts +2 -0
- package/dist/websocket/controllers/rest.js +4 -2
- package/dist/websocket/errors.js +2 -1
- package/dist/websocket/handlers/heartbeat.js +4 -3
- package/dist/websocket/handlers/subscribe.d.ts +2 -2
- package/dist/websocket/handlers/subscribe.js +5 -4
- package/dist/websocket/types.d.ts +3 -1
- package/package.json +114 -115
- package/dist/__utils__/items-utils.d.ts +0 -2
- package/dist/__utils__/items-utils.js +0 -31
- package/dist/__utils__/mock-env.d.ts +0 -18
- package/dist/__utils__/mock-env.js +0 -41
- package/dist/__utils__/schemas.d.ts +0 -13
- package/dist/__utils__/schemas.js +0 -301
- package/dist/__utils__/snapshots.d.ts +0 -5
- package/dist/__utils__/snapshots.js +0 -903
- package/dist/env.d.ts +0 -13
- package/dist/env.js +0 -505
- package/dist/messenger.d.ts +0 -24
- package/dist/messenger.js +0 -64
- package/dist/utils/package.d.ts +0 -2
- package/dist/utils/package.js +0 -6
- package/dist/utils/telemetry.d.ts +0 -1
- package/dist/utils/telemetry.js +0 -23
- package/dist/utils/to-boolean.d.ts +0 -4
- package/dist/utils/to-boolean.js +0 -6
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import formatTitle from '@directus/format-title';
|
|
2
3
|
import { spec } from '@directus/specs';
|
|
4
|
+
import { version } from 'directus/version';
|
|
3
5
|
import { cloneDeep, mergeWith } from 'lodash-es';
|
|
4
6
|
import { OAS_REQUIRED_SCHEMAS } from '../constants.js';
|
|
5
7
|
import getDatabase from '../database/index.js';
|
|
6
|
-
import env from '../env.js';
|
|
7
8
|
import { getRelationType } from '../utils/get-relation-type.js';
|
|
8
|
-
import { version } from '../utils/package.js';
|
|
9
|
-
import { GraphQLService } from './graphql/index.js';
|
|
10
9
|
import { reduceSchema } from '../utils/reduce-schema.js';
|
|
10
|
+
import { GraphQLService } from './graphql/index.js';
|
|
11
|
+
const env = useEnv();
|
|
11
12
|
export class SpecificationService {
|
|
12
13
|
accountability;
|
|
13
14
|
knex;
|
|
@@ -19,7 +20,7 @@ export class SpecificationService {
|
|
|
19
20
|
this.knex = knex || getDatabase();
|
|
20
21
|
this.schema = schema;
|
|
21
22
|
this.oas = new OASSpecsService({ knex, schema, accountability });
|
|
22
|
-
this.graphql = new GraphQLSpecsService({ knex, schema });
|
|
23
|
+
this.graphql = new GraphQLSpecsService({ knex, schema, accountability });
|
|
23
24
|
}
|
|
24
25
|
}
|
|
25
26
|
class OASSpecsService {
|
package/dist/services/users.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
|
|
1
3
|
import { getSimpleHash, toArray } from '@directus/utils';
|
|
2
4
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
|
|
3
5
|
import Joi from 'joi';
|
|
@@ -5,9 +7,6 @@ import jwt from 'jsonwebtoken';
|
|
|
5
7
|
import { cloneDeep, isEmpty } from 'lodash-es';
|
|
6
8
|
import { performance } from 'perf_hooks';
|
|
7
9
|
import getDatabase from '../database/index.js';
|
|
8
|
-
import env from '../env.js';
|
|
9
|
-
import { ForbiddenError } from '@directus/errors';
|
|
10
|
-
import { InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
|
|
11
10
|
import isUrlAllowed from '../utils/is-url-allowed.js';
|
|
12
11
|
import { verifyJWT } from '../utils/jwt.js';
|
|
13
12
|
import { stall } from '../utils/stall.js';
|
|
@@ -15,6 +14,7 @@ import { Url } from '../utils/url.js';
|
|
|
15
14
|
import { ItemsService } from './items.js';
|
|
16
15
|
import { MailService } from './mail/index.js';
|
|
17
16
|
import { SettingsService } from './settings.js';
|
|
17
|
+
const env = useEnv();
|
|
18
18
|
export class UsersService extends ItemsService {
|
|
19
19
|
constructor(options) {
|
|
20
20
|
super('directus_users', options);
|
|
@@ -116,7 +116,7 @@ export class UsersService extends ItemsService {
|
|
|
116
116
|
*/
|
|
117
117
|
async getUserByEmail(email) {
|
|
118
118
|
return await this.knex
|
|
119
|
-
.select('id', 'role', 'status', 'password')
|
|
119
|
+
.select('id', 'role', 'status', 'password', 'email')
|
|
120
120
|
.from('directus_users')
|
|
121
121
|
.whereRaw(`LOWER(??) = ?`, ['email', email.toLowerCase()])
|
|
122
122
|
.first();
|
|
@@ -213,9 +213,20 @@ export class UsersService extends ItemsService {
|
|
|
213
213
|
async updateMany(keys, data, opts) {
|
|
214
214
|
try {
|
|
215
215
|
if (data['role']) {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
216
|
+
/*
|
|
217
|
+
* data['role'] has the following cases:
|
|
218
|
+
* - a string with existing role id
|
|
219
|
+
* - an object with existing role id for GraphQL mutations
|
|
220
|
+
* - an object with data for new role
|
|
221
|
+
*/
|
|
222
|
+
const role = data['role']?.id ?? data['role'];
|
|
223
|
+
let newRole;
|
|
224
|
+
if (typeof role === 'string') {
|
|
225
|
+
newRole = await this.knex.select('admin_access').from('directus_roles').where('id', role).first();
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
newRole = role;
|
|
229
|
+
}
|
|
219
230
|
if (!newRole?.admin_access) {
|
|
220
231
|
await this.checkRemainingAdminExistence(keys);
|
|
221
232
|
}
|
|
@@ -325,13 +336,13 @@ export class UsersService extends ItemsService {
|
|
|
325
336
|
if (isEmpty(user) || user.status === 'invited') {
|
|
326
337
|
const subjectLine = subject ?? "You've been invited";
|
|
327
338
|
await mailService.send({
|
|
328
|
-
to: email,
|
|
339
|
+
to: user?.email ?? email,
|
|
329
340
|
subject: subjectLine,
|
|
330
341
|
template: {
|
|
331
342
|
name: 'user-invitation',
|
|
332
343
|
data: {
|
|
333
|
-
url: this.inviteUrl(email, url),
|
|
334
|
-
email,
|
|
344
|
+
url: this.inviteUrl(user?.email ?? email, url),
|
|
345
|
+
email: user?.email ?? email,
|
|
335
346
|
},
|
|
336
347
|
},
|
|
337
348
|
});
|
|
@@ -369,20 +380,20 @@ export class UsersService extends ItemsService {
|
|
|
369
380
|
knex: this.knex,
|
|
370
381
|
accountability: this.accountability,
|
|
371
382
|
});
|
|
372
|
-
const payload = { email, scope: 'password-reset', hash: getSimpleHash('' + user.password) };
|
|
383
|
+
const payload = { email: user.email, scope: 'password-reset', hash: getSimpleHash('' + user.password) };
|
|
373
384
|
const token = jwt.sign(payload, env['SECRET'], { expiresIn: '1d', issuer: 'directus' });
|
|
374
385
|
const acceptURL = url
|
|
375
386
|
? new Url(url).setQuery('token', token).toString()
|
|
376
387
|
: new Url(env['PUBLIC_URL']).addPath('admin', 'reset-password').setQuery('token', token).toString();
|
|
377
388
|
const subjectLine = subject ? subject : 'Password Reset Request';
|
|
378
389
|
await mailService.send({
|
|
379
|
-
to: email,
|
|
390
|
+
to: user.email,
|
|
380
391
|
subject: subjectLine,
|
|
381
392
|
template: {
|
|
382
393
|
name: 'password-reset',
|
|
383
394
|
data: {
|
|
384
395
|
url: acceptURL,
|
|
385
|
-
email,
|
|
396
|
+
email: user.email,
|
|
386
397
|
},
|
|
387
398
|
},
|
|
388
399
|
});
|
|
@@ -172,26 +172,27 @@ export class VersionsService extends ItemsService {
|
|
|
172
172
|
knex: this.knex,
|
|
173
173
|
schema: this.schema,
|
|
174
174
|
});
|
|
175
|
+
const { item, collection } = version;
|
|
175
176
|
const activity = await activityService.createOne({
|
|
176
177
|
action: Action.VERSION_SAVE,
|
|
177
178
|
user: this.accountability?.user ?? null,
|
|
178
|
-
collection
|
|
179
|
+
collection,
|
|
179
180
|
ip: this.accountability?.ip ?? null,
|
|
180
181
|
user_agent: this.accountability?.userAgent ?? null,
|
|
181
182
|
origin: this.accountability?.origin ?? null,
|
|
182
|
-
item
|
|
183
|
+
item,
|
|
183
184
|
});
|
|
184
185
|
const revisionDelta = await payloadService.prepareDelta(data);
|
|
185
186
|
await revisionsService.createOne({
|
|
186
187
|
activity,
|
|
187
188
|
version: key,
|
|
188
|
-
collection
|
|
189
|
-
item
|
|
189
|
+
collection,
|
|
190
|
+
item,
|
|
190
191
|
data: revisionDelta,
|
|
191
192
|
delta: revisionDelta,
|
|
192
193
|
});
|
|
193
194
|
const { cache } = getCache();
|
|
194
|
-
if (shouldClearCache(cache, undefined,
|
|
195
|
+
if (shouldClearCache(cache, undefined, collection)) {
|
|
195
196
|
cache.clear();
|
|
196
197
|
}
|
|
197
198
|
return data;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Bus } from '@directus/memory';
|
|
2
2
|
import type { AbstractServiceOptions, Item, MutationOptions, PrimaryKey, Webhook } from '../types/index.js';
|
|
3
3
|
import { ItemsService } from './items.js';
|
|
4
4
|
export declare class WebhooksService extends ItemsService<Webhook> {
|
|
5
|
-
messenger:
|
|
5
|
+
messenger: Bus;
|
|
6
6
|
constructor(options: AbstractServiceOptions);
|
|
7
7
|
createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
|
|
8
8
|
createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useBus } from '../bus/index.js';
|
|
2
2
|
import { ItemsService } from './items.js';
|
|
3
3
|
export class WebhooksService extends ItemsService {
|
|
4
4
|
messenger;
|
|
5
5
|
constructor(options) {
|
|
6
6
|
super('directus_webhooks', options);
|
|
7
|
-
this.messenger =
|
|
7
|
+
this.messenger = useBus();
|
|
8
8
|
}
|
|
9
9
|
async createOne(data, opts) {
|
|
10
10
|
const result = await super.createOne(data, opts);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ActionHandler } from '@directus/types';
|
|
2
|
-
import type { WebSocketClient } from '../websocket/types.js';
|
|
3
2
|
import type { WebSocketMessage } from '../websocket/messages.js';
|
|
3
|
+
import type { WebSocketClient } from '../websocket/types.js';
|
|
4
4
|
export declare class WebSocketService {
|
|
5
5
|
private controller;
|
|
6
6
|
constructor();
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
2
|
import { ServiceUnavailableError } from '@directus/errors';
|
|
3
|
-
import { toBoolean } from '
|
|
3
|
+
import { toBoolean } from '@directus/utils';
|
|
4
4
|
import emitter from '../emitter.js';
|
|
5
|
-
import
|
|
5
|
+
import { getWebSocketController } from '../websocket/controllers/index.js';
|
|
6
|
+
const env = useEnv();
|
|
6
7
|
export class WebSocketService {
|
|
7
8
|
controller;
|
|
8
9
|
constructor() {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
2
|
import { getStorageDriver } from './get-storage-driver.js';
|
|
3
3
|
export const registerDrivers = async (storage) => {
|
|
4
|
+
const env = useEnv();
|
|
4
5
|
const usedDrivers = [];
|
|
5
6
|
for (const [key, value] of Object.entries(env)) {
|
|
6
7
|
if ((key.startsWith('STORAGE_') && key.endsWith('_DRIVER')) === false)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { toArray } from '@directus/utils';
|
|
2
|
-
import env from '../env.js';
|
|
3
3
|
import { getConfigFromEnv } from '../utils/get-config-from-env.js';
|
|
4
4
|
export const registerLocations = async (storage) => {
|
|
5
|
+
const env = useEnv();
|
|
5
6
|
const locations = toArray(env['STORAGE_LOCATIONS']);
|
|
6
7
|
locations.forEach((location) => {
|
|
7
8
|
location = location.trim();
|
package/dist/synchronization.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
1
2
|
import { Redis } from 'ioredis';
|
|
2
|
-
import env from './env.js';
|
|
3
3
|
import { getConfigFromEnv } from './utils/get-config-from-env.js';
|
|
4
4
|
let synchronizationManager;
|
|
5
5
|
function getSynchronizationManager() {
|
|
6
6
|
if (synchronizationManager)
|
|
7
7
|
return synchronizationManager;
|
|
8
|
+
const env = useEnv();
|
|
8
9
|
if (env['SYNCHRONIZATION_STORE'] === 'redis') {
|
|
9
10
|
synchronizationManager = new SynchronizationManagerRedis();
|
|
10
11
|
}
|
|
@@ -73,6 +74,7 @@ class SynchronizationManagerRedis {
|
|
|
73
74
|
namespace;
|
|
74
75
|
client;
|
|
75
76
|
constructor() {
|
|
77
|
+
const env = useEnv();
|
|
76
78
|
const config = getConfigFromEnv('REDIS');
|
|
77
79
|
this.client = new Redis(env['REDIS'] ?? config);
|
|
78
80
|
this.namespace = env['SYNCHRONIZATION_NAMESPACE'] ?? 'directus-sync';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { version } from 'directus/version';
|
|
3
|
+
import { getDatabase, getDatabaseClient } from '../../database/index.js';
|
|
4
|
+
import { getItemCount } from '../utils/get-item-count.js';
|
|
5
|
+
import { getUserCount } from '../utils/get-user-count.js';
|
|
6
|
+
import { getUserItemCount } from '../utils/get-user-item-count.js';
|
|
7
|
+
const basicCountCollections = [
|
|
8
|
+
'directus_dashboards',
|
|
9
|
+
'directus_extensions',
|
|
10
|
+
'directus_files',
|
|
11
|
+
'directus_flows',
|
|
12
|
+
'directus_roles',
|
|
13
|
+
'directus_shares',
|
|
14
|
+
];
|
|
15
|
+
/**
|
|
16
|
+
* Create a telemetry report about the anonymous usage of the current installation
|
|
17
|
+
*/
|
|
18
|
+
export const getReport = async () => {
|
|
19
|
+
const db = getDatabase();
|
|
20
|
+
const env = useEnv();
|
|
21
|
+
const [basicCounts, userCounts, userItemCount] = await Promise.all([
|
|
22
|
+
getItemCount(db, basicCountCollections),
|
|
23
|
+
getUserCount(db),
|
|
24
|
+
getUserItemCount(db),
|
|
25
|
+
]);
|
|
26
|
+
return {
|
|
27
|
+
url: env['PUBLIC_URL'],
|
|
28
|
+
version: version,
|
|
29
|
+
database: getDatabaseClient(),
|
|
30
|
+
dashboards: basicCounts.directus_dashboards,
|
|
31
|
+
extensions: basicCounts.directus_extensions,
|
|
32
|
+
files: basicCounts.directus_files,
|
|
33
|
+
flows: basicCounts.directus_flows,
|
|
34
|
+
roles: basicCounts.directus_roles,
|
|
35
|
+
shares: basicCounts.directus_shares,
|
|
36
|
+
admin_users: userCounts.admin,
|
|
37
|
+
app_users: userCounts.app,
|
|
38
|
+
api_users: userCounts.api,
|
|
39
|
+
collections: userItemCount.collections,
|
|
40
|
+
items: userItemCount.items,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exported to be able to test the anonymous callback function
|
|
3
|
+
*/
|
|
4
|
+
export declare const jobCallback: () => void;
|
|
5
|
+
/**
|
|
6
|
+
* Initialize the telemetry tracking. Will generate a report on start, and set a schedule to report
|
|
7
|
+
* every 6 hours
|
|
8
|
+
*
|
|
9
|
+
* @returns Whether or not telemetry has been initialized
|
|
10
|
+
*/
|
|
11
|
+
export declare const initTelemetry: () => Promise<boolean>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { toBoolean } from '@directus/utils';
|
|
3
|
+
import { getCache } from '../../cache.js';
|
|
4
|
+
import { scheduleSynchronizedJob } from '../../utils/schedule.js';
|
|
5
|
+
import { track } from './track.js';
|
|
6
|
+
/**
|
|
7
|
+
* Exported to be able to test the anonymous callback function
|
|
8
|
+
*/
|
|
9
|
+
export const jobCallback = () => {
|
|
10
|
+
track();
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the telemetry tracking. Will generate a report on start, and set a schedule to report
|
|
14
|
+
* every 6 hours
|
|
15
|
+
*
|
|
16
|
+
* @returns Whether or not telemetry has been initialized
|
|
17
|
+
*/
|
|
18
|
+
export const initTelemetry = async () => {
|
|
19
|
+
const env = useEnv();
|
|
20
|
+
if (toBoolean(env['TELEMETRY']) === false)
|
|
21
|
+
return false;
|
|
22
|
+
scheduleSynchronizedJob('telemetry', '0 */6 * * *', jobCallback);
|
|
23
|
+
const { lockCache } = getCache();
|
|
24
|
+
if (!(await lockCache.get('telemetry-lock'))) {
|
|
25
|
+
await lockCache.set('telemetry-lock', true, 30000);
|
|
26
|
+
track({ wait: false });
|
|
27
|
+
// Don't flush the lock. We want to debounce these calls across containers on startup
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEnv } from '@directus/env';
|
|
2
|
+
import { URL } from 'node:url';
|
|
3
|
+
/**
|
|
4
|
+
* Post an anonymous usage report to the centralized intake server
|
|
5
|
+
*/
|
|
6
|
+
export const sendReport = async (report) => {
|
|
7
|
+
const env = useEnv();
|
|
8
|
+
const url = new URL('/v1/metrics', env['TELEMETRY_URL']);
|
|
9
|
+
const headers = {
|
|
10
|
+
'Content-Type': 'application/json',
|
|
11
|
+
};
|
|
12
|
+
if (env['TELEMETRY_AUTHORIZATION']) {
|
|
13
|
+
headers['Authorization'] = env['TELEMETRY_AUTHORIZATION'];
|
|
14
|
+
}
|
|
15
|
+
const res = await fetch(url, {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: JSON.stringify(report),
|
|
18
|
+
headers,
|
|
19
|
+
});
|
|
20
|
+
if (!res.ok) {
|
|
21
|
+
throw new Error(`[${res.status}] ${await res.text()}`);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate and send a report. Will log on error, but not throw. No need to be awaited
|
|
3
|
+
*
|
|
4
|
+
* @param opts Options for the tracking
|
|
5
|
+
* @param opts.wait Whether or not to wait a random amount of time between 0 and 30 minutes
|
|
6
|
+
* @returns whether or not the tracking was successful
|
|
7
|
+
*/
|
|
8
|
+
export declare const track: (opts?: {
|
|
9
|
+
wait: boolean;
|
|
10
|
+
}) => Promise<boolean>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getNodeEnv } from '@directus/utils/node';
|
|
2
|
+
import { setTimeout } from 'timers/promises';
|
|
3
|
+
import { useLogger } from '../../logger.js';
|
|
4
|
+
import { getRandomWaitTime } from '../utils/get-random-wait-time.js';
|
|
5
|
+
import { getReport } from './get-report.js';
|
|
6
|
+
import { sendReport } from './send-report.js';
|
|
7
|
+
/**
|
|
8
|
+
* Generate and send a report. Will log on error, but not throw. No need to be awaited
|
|
9
|
+
*
|
|
10
|
+
* @param opts Options for the tracking
|
|
11
|
+
* @param opts.wait Whether or not to wait a random amount of time between 0 and 30 minutes
|
|
12
|
+
* @returns whether or not the tracking was successful
|
|
13
|
+
*/
|
|
14
|
+
export const track = async (opts = { wait: true }) => {
|
|
15
|
+
const logger = useLogger();
|
|
16
|
+
if (opts.wait) {
|
|
17
|
+
await setTimeout(getRandomWaitTime());
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
const report = await getReport();
|
|
21
|
+
await sendReport(report);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (getNodeEnv() === 'development') {
|
|
26
|
+
logger.error(err);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export interface TelemetryReport {
|
|
2
|
+
/**
|
|
3
|
+
* The project's web-facing public URL
|
|
4
|
+
*/
|
|
5
|
+
url: string;
|
|
6
|
+
/**
|
|
7
|
+
* Current Directus version in use
|
|
8
|
+
*/
|
|
9
|
+
version: string;
|
|
10
|
+
/**
|
|
11
|
+
* Database client in use
|
|
12
|
+
*/
|
|
13
|
+
database: string;
|
|
14
|
+
/**
|
|
15
|
+
* Number of users in the system that have admin access to the system
|
|
16
|
+
*/
|
|
17
|
+
admin_users: number;
|
|
18
|
+
/**
|
|
19
|
+
* Number of users that can access the app, but don't have admin access
|
|
20
|
+
*/
|
|
21
|
+
app_users: number;
|
|
22
|
+
/**
|
|
23
|
+
* Number of users that can only access the API
|
|
24
|
+
*/
|
|
25
|
+
api_users: number;
|
|
26
|
+
/**
|
|
27
|
+
* Number of unique roles in the system
|
|
28
|
+
*/
|
|
29
|
+
roles: number;
|
|
30
|
+
/**
|
|
31
|
+
* Number of unique flows in the system
|
|
32
|
+
*/
|
|
33
|
+
flows: number;
|
|
34
|
+
/**
|
|
35
|
+
* Number of unique dashboards in the system
|
|
36
|
+
*/
|
|
37
|
+
dashboards: number;
|
|
38
|
+
/**
|
|
39
|
+
* Number of installed extensions in the system. Does not differentiate between enabled/disabled
|
|
40
|
+
*/
|
|
41
|
+
extensions: number;
|
|
42
|
+
/**
|
|
43
|
+
* Number of Directus-managed collections
|
|
44
|
+
*/
|
|
45
|
+
collections: number;
|
|
46
|
+
/**
|
|
47
|
+
* Total number of items in the non-system tables
|
|
48
|
+
*/
|
|
49
|
+
items: number;
|
|
50
|
+
/**
|
|
51
|
+
* Number of files in the system
|
|
52
|
+
*/
|
|
53
|
+
files: number;
|
|
54
|
+
/**
|
|
55
|
+
* Number of shares in the system
|
|
56
|
+
*/
|
|
57
|
+
shares: number;
|
|
58
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Knex } from 'knex';
|
|
2
|
+
export interface CollectionCount {
|
|
3
|
+
collection: string;
|
|
4
|
+
count: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Get the item count of the given collection in the given database
|
|
8
|
+
* @param db Knex instance to count against
|
|
9
|
+
* @param collection Table to count rows in
|
|
10
|
+
* @returns Collection name and count
|
|
11
|
+
*/
|
|
12
|
+
export declare const countCollection: (db: Knex, collection: string) => Promise<CollectionCount>;
|
|
13
|
+
/**
|
|
14
|
+
* Merge the given collection count in the object accumulator
|
|
15
|
+
* Intended for use with .reduce()
|
|
16
|
+
* @param acc Accumulator
|
|
17
|
+
* @param value Current collection count object in array
|
|
18
|
+
* @returns Updated accumulator
|
|
19
|
+
*/
|
|
20
|
+
export declare const mergeResults: (acc: Record<string, number>, value: CollectionCount) => Record<string, number>;
|
|
21
|
+
/**
|
|
22
|
+
* Get an object of item counts for the given collections
|
|
23
|
+
* @param db Database instance to get counts in
|
|
24
|
+
* @param collections Array of table names to get count from
|
|
25
|
+
*/
|
|
26
|
+
export declare const getItemCount: <T extends readonly string[]>(db: Knex, collections: T) => Promise<Record<T[number], number>>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {} from 'knex';
|
|
2
|
+
import pLimit from 'p-limit';
|
|
3
|
+
/**
|
|
4
|
+
* Get the item count of the given collection in the given database
|
|
5
|
+
* @param db Knex instance to count against
|
|
6
|
+
* @param collection Table to count rows in
|
|
7
|
+
* @returns Collection name and count
|
|
8
|
+
*/
|
|
9
|
+
export const countCollection = async (db, collection) => {
|
|
10
|
+
const count = await db.count('*', { as: 'count' }).from(collection).first();
|
|
11
|
+
return { collection, count: Number(count?.['count'] ?? 0) };
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Merge the given collection count in the object accumulator
|
|
15
|
+
* Intended for use with .reduce()
|
|
16
|
+
* @param acc Accumulator
|
|
17
|
+
* @param value Current collection count object in array
|
|
18
|
+
* @returns Updated accumulator
|
|
19
|
+
*/
|
|
20
|
+
export const mergeResults = (acc, value) => {
|
|
21
|
+
acc[value.collection] = value.count;
|
|
22
|
+
return acc;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Get an object of item counts for the given collections
|
|
26
|
+
* @param db Database instance to get counts in
|
|
27
|
+
* @param collections Array of table names to get count from
|
|
28
|
+
*/
|
|
29
|
+
export const getItemCount = async (db, collections) => {
|
|
30
|
+
// Counts can be a little heavy if the table is very large, so we'll only ever execute 3 of these
|
|
31
|
+
// queries simultaneously to not overload the database
|
|
32
|
+
const limit = pLimit(3);
|
|
33
|
+
const calls = collections.map((collection) => limit(countCollection, db, collection));
|
|
34
|
+
const results = await Promise.all(calls);
|
|
35
|
+
return results.reduce(mergeResults, {});
|
|
36
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { toBoolean } from '@directus/utils';
|
|
2
|
+
import {} from 'knex';
|
|
3
|
+
export const getUserCount = async (db) => {
|
|
4
|
+
const counts = {
|
|
5
|
+
admin: 0,
|
|
6
|
+
app: 0,
|
|
7
|
+
api: 0,
|
|
8
|
+
};
|
|
9
|
+
const result = (await db
|
|
10
|
+
.count('directus_users.id', { as: 'count' })
|
|
11
|
+
.select('directus_roles.admin_access', 'directus_roles.app_access')
|
|
12
|
+
.from('directus_users')
|
|
13
|
+
.leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
|
|
14
|
+
.groupBy('directus_roles.admin_access', 'directus_roles.app_access'));
|
|
15
|
+
for (const record of result) {
|
|
16
|
+
const adminAccess = toBoolean(record.admin_access);
|
|
17
|
+
const appAccess = toBoolean(record.app_access);
|
|
18
|
+
const count = Number(record.count);
|
|
19
|
+
if (adminAccess) {
|
|
20
|
+
counts.admin = count;
|
|
21
|
+
}
|
|
22
|
+
else if (appAccess) {
|
|
23
|
+
counts.app = count;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
counts.api = count;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return counts;
|
|
30
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Knex } from 'knex';
|
|
2
|
+
export interface UserItemCount {
|
|
3
|
+
collections: number;
|
|
4
|
+
items: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Sum all passed values together. Meant to be used with .reduce()
|
|
8
|
+
*/
|
|
9
|
+
export declare const sum: (acc: number, val: number) => number;
|
|
10
|
+
/**
|
|
11
|
+
* Count all the items in the non-system tables
|
|
12
|
+
*/
|
|
13
|
+
export declare const getUserItemCount: (db: Knex) => Promise<UserItemCount>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {} from 'knex';
|
|
2
|
+
import { getSchema } from '../../utils/get-schema.js';
|
|
3
|
+
import { getItemCount } from './get-item-count.js';
|
|
4
|
+
/**
|
|
5
|
+
* Sum all passed values together. Meant to be used with .reduce()
|
|
6
|
+
*/
|
|
7
|
+
export const sum = (acc, val) => (acc += val);
|
|
8
|
+
/**
|
|
9
|
+
* Count all the items in the non-system tables
|
|
10
|
+
*/
|
|
11
|
+
export const getUserItemCount = async (db) => {
|
|
12
|
+
const schema = await getSchema({ database: db });
|
|
13
|
+
const userCollections = Object.keys(schema.collections).filter((collection) => collection.startsWith('directus_') === false);
|
|
14
|
+
const counts = await getItemCount(db, userCollections);
|
|
15
|
+
const collections = userCollections.length;
|
|
16
|
+
const items = Object.values(counts).reduce(sum, 0);
|
|
17
|
+
return { collections, items };
|
|
18
|
+
};
|
package/dist/types/assets.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export type TransformationParams = {
|
|
|
12
12
|
transforms?: Transformation[];
|
|
13
13
|
format?: TransformationFormat | 'auto';
|
|
14
14
|
quality?: number;
|
|
15
|
+
focal_point_x?: number;
|
|
16
|
+
focal_point_y?: number;
|
|
15
17
|
} & TransformationResize;
|
|
16
18
|
export type TransformationSet = {
|
|
17
19
|
transformationParams: TransformationParams;
|