@directus/api 35.1.0 → 36.0.0-rc.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/ai/chat/models/chat-request.js +48 -48
- package/dist/ai/chat/models/object-request.js +6 -6
- package/dist/ai/chat/models/providers.js +14 -14
- package/dist/ai/chat/utils/parse-json-schema-7.js +22 -22
- package/dist/ai/mcp/server.js +44 -6
- package/dist/ai/mcp/utils.js +31 -0
- package/dist/ai/tools/assets/index.js +3 -3
- package/dist/ai/tools/collections/index.js +18 -18
- package/dist/ai/tools/fields/index.js +18 -18
- package/dist/ai/tools/files/index.js +18 -18
- package/dist/ai/tools/flows/index.js +16 -16
- package/dist/ai/tools/folders/index.js +18 -18
- package/dist/ai/tools/items/index.js +17 -17
- package/dist/ai/tools/operations/index.js +16 -16
- package/dist/ai/tools/relations/index.js +22 -22
- package/dist/ai/tools/schema/index.js +3 -3
- package/dist/ai/tools/schema.js +159 -159
- package/dist/ai/tools/system/index.js +3 -3
- package/dist/ai/tools/trigger-flow/index.js +3 -3
- package/dist/app.js +33 -9
- package/dist/auth/drivers/ldap.js +3 -1
- package/dist/auth/drivers/local.js +2 -0
- package/dist/auth/drivers/oauth2.js +3 -1
- package/dist/auth/drivers/openid.js +3 -1
- package/dist/auth/drivers/saml.js +2 -0
- package/dist/auth/utils/check-local-disabled.js +16 -0
- package/dist/auth/utils/check-sso-enabled.js +14 -0
- package/dist/auth.js +8 -5
- package/dist/cli/commands/bootstrap/index.js +3 -0
- package/dist/cli/commands/cache/clear.js +6 -1
- package/dist/cli/commands/roles/create.js +4 -1
- package/dist/cli/commands/users/create.js +3 -0
- package/dist/constants.js +8 -1
- package/dist/controllers/access.js +1 -1
- package/dist/controllers/activity.js +2 -1
- package/dist/controllers/assets.js +45 -1
- package/dist/controllers/auth.js +13 -5
- package/dist/controllers/collections.js +1 -1
- package/dist/controllers/comments.js +1 -1
- package/dist/controllers/dashboards.js +1 -1
- package/dist/controllers/fields.js +1 -1
- package/dist/controllers/files.js +3 -1
- package/dist/controllers/flows.js +6 -5
- package/dist/controllers/folders.js +1 -1
- package/dist/controllers/graphql.js +2 -0
- package/dist/controllers/items.js +3 -1
- package/dist/controllers/license.js +119 -0
- package/dist/controllers/mcp/index.js +38 -0
- package/dist/controllers/mcp/oauth-clients.js +68 -0
- package/dist/controllers/mcp/oauth-consent-page.js +316 -0
- package/dist/controllers/mcp/oauth.js +381 -0
- package/dist/controllers/mcp/templates/oauth-consent.liquid +62 -0
- package/dist/controllers/mcp/templates/oauth-error.liquid +28 -0
- package/dist/controllers/notifications.js +1 -1
- package/dist/controllers/operations.js +1 -1
- package/dist/controllers/panels.js +1 -1
- package/dist/controllers/permissions.js +1 -1
- package/dist/controllers/policies.js +1 -1
- package/dist/controllers/presets.js +1 -1
- package/dist/controllers/revisions.js +3 -2
- package/dist/controllers/roles.js +1 -1
- package/dist/controllers/schema.js +2 -2
- package/dist/controllers/server.js +38 -9
- package/dist/controllers/shares.js +1 -1
- package/dist/controllers/translations.js +1 -1
- package/dist/controllers/users.js +1 -1
- package/dist/controllers/utils.js +2 -2
- package/dist/controllers/versions.js +12 -5
- package/dist/database/get-ast-from-query/lib/convert-wildcards.js +10 -1
- package/dist/database/get-ast-from-query/lib/parse-fields.js +2 -1
- package/dist/database/helpers/fn/dialects/mysql.js +7 -12
- package/dist/database/helpers/fn/dialects/oracle.js +3 -4
- package/dist/database/helpers/fn/dialects/postgres.js +4 -26
- package/dist/database/helpers/fn/json/mysql-json-path.js +22 -0
- package/dist/database/helpers/fn/json/parse-function.js +14 -6
- package/dist/database/helpers/fn/json/postgres-json-path.js +54 -0
- package/dist/database/migrations/20260110A-add-ai-provider-settings.js +4 -4
- package/dist/database/migrations/20260217A-null-item-versions.js +14 -0
- package/dist/database/migrations/20260312A-add-ai-translation-settings.js +18 -0
- package/dist/database/migrations/20260507A-add-licensing.js +22 -0
- package/dist/database/migrations/20260512A-add-autosave-revision-interval.js +14 -0
- package/dist/database/migrations/20260512B-add-mcp-oauth.js +87 -0
- package/dist/database/run-ast/lib/apply-query/filter/operator.js +116 -33
- package/dist/database/run-ast/lib/apply-query/index.js +4 -1
- package/dist/database/run-ast/lib/apply-query/sort.js +17 -7
- package/dist/database/run-ast/lib/get-db-query.js +21 -9
- package/dist/database/run-ast/lib/parse-current-level.js +2 -1
- package/dist/database/run-ast/run-ast.js +2 -1
- package/dist/database/run-ast/utils/get-column.js +2 -1
- package/dist/extensions/lib/installation/manager.js +3 -3
- package/dist/extensions/lib/sandbox/register/operation.js +1 -1
- package/dist/extensions/lib/sync/sync.js +2 -2
- package/dist/extensions/manager.js +5 -5
- package/dist/flows.js +12 -10
- package/dist/license/entitlements/lib/collections.js +37 -0
- package/dist/license/entitlements/lib/custom-llms-enabled.js +18 -0
- package/dist/license/entitlements/lib/custom-permission-rules-enabled.js +41 -0
- package/dist/license/entitlements/lib/flows.js +29 -0
- package/dist/license/entitlements/lib/seats.js +103 -0
- package/dist/license/entitlements/lib/sso-enabled.js +45 -0
- package/dist/license/entitlements/manager.js +256 -0
- package/dist/license/index.js +4 -0
- package/dist/license/manager.js +505 -0
- package/dist/license/utils/compute-license-status.js +27 -0
- package/dist/license/utils/get-core-grace-expires-at.js +38 -0
- package/dist/license/utils/get-license-key.js +23 -0
- package/dist/license/utils/get-license-token.js +23 -0
- package/dist/license/utils/handle-license-error.js +41 -0
- package/dist/license/utils/is-in-core-grace-period.js +11 -0
- package/dist/license/utils/is-sso-bypass-allowed.js +21 -0
- package/dist/license/utils/use-rpc.js +33 -0
- package/dist/middleware/cache.js +4 -1
- package/dist/middleware/error-handler.js +11 -0
- package/dist/middleware/extract-token.js +11 -2
- package/dist/middleware/is-admin.js +16 -0
- package/dist/middleware/is-locked.js +16 -0
- package/dist/middleware/mcp-oauth-guard.js +23 -0
- package/dist/middleware/request-counter.js +5 -2
- package/dist/packages/types/dist/index.js +117 -122
- package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +10 -1
- package/dist/permissions/utils/get-unaliased-field-key.js +2 -1
- package/dist/request/is-denied-ip.js +2 -0
- package/dist/schedules/license.js +31 -0
- package/dist/schedules/oauth-cleanup.js +26 -0
- package/dist/schedules/retention.js +1 -1
- package/dist/schedules/telemetry.js +4 -1
- package/dist/schedules/tus.js +1 -1
- package/dist/schedules/utils/duration-to-cron.js +36 -0
- package/dist/services/activity.js +15 -0
- package/dist/services/authentication.js +12 -5
- package/dist/services/collections.js +41 -10
- package/dist/services/fields.js +6 -6
- package/dist/services/flows.js +12 -0
- package/dist/services/graphql/resolvers/system-admin.js +2 -2
- package/dist/services/graphql/resolvers/system-global.js +1 -1
- package/dist/services/graphql/resolvers/system.js +34 -18
- package/dist/services/graphql/schema/get-types.js +23 -2
- package/dist/services/graphql/schema/parse-query.js +8 -0
- package/dist/services/graphql/schema/read.js +12 -0
- package/dist/services/graphql/types/json-filter.js +30 -0
- package/dist/services/index.js +6 -6
- package/dist/services/items.js +32 -14
- package/dist/services/mcp-oauth/cimd.js +307 -0
- package/dist/services/mcp-oauth/index.js +1185 -0
- package/dist/services/mcp-oauth/types/error.js +22 -0
- package/dist/services/mcp-oauth/utils/cimd-egress.js +182 -0
- package/dist/services/mcp-oauth/utils/domain.js +21 -0
- package/dist/services/mcp-oauth/utils/loopback.js +11 -0
- package/dist/services/mcp-oauth/utils/redirect.js +84 -0
- package/dist/services/mcp-oauth/utils/registration-debug.js +131 -0
- package/dist/services/payload.js +2 -1
- package/dist/services/permissions.js +31 -9
- package/dist/services/revisions.js +15 -0
- package/dist/services/schema.js +2 -2
- package/dist/services/server.js +21 -4
- package/dist/services/settings.js +37 -3
- package/dist/services/users.js +13 -6
- package/dist/services/utils.js +6 -1
- package/dist/services/versions.js +138 -69
- package/dist/utils/calculate-field-depth.js +1 -0
- package/dist/utils/deep-freeze.js +24 -0
- package/dist/utils/extract-function-name.js +13 -0
- package/dist/utils/generate-translations.js +5 -5
- package/dist/utils/get-accountability-for-token.js +13 -1
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-history-filter-query.js +22 -0
- package/dist/utils/get-schema.js +2 -2
- package/dist/utils/get-service.js +3 -3
- package/dist/utils/is-admin.js +9 -0
- package/dist/utils/parse-oauth-scope.js +12 -0
- package/dist/utils/sanitize-query.js +1 -1
- package/dist/utils/split-field-path.js +29 -0
- package/dist/utils/transaction.js +2 -2
- package/dist/utils/translations-validation.js +2 -2
- package/dist/utils/validate-diff.js +7 -3
- package/dist/utils/validate-query.js +35 -4
- package/dist/utils/validate-user-count-integrity.js +28 -5
- package/dist/utils/verify-session-jwt.js +5 -2
- package/dist/utils/versioning/handle-version.js +130 -48
- package/dist/utils/versioning/remove-circular.js +17 -0
- package/dist/websocket/authenticate.js +2 -1
- package/dist/websocket/collab/collab.js +1 -1
- package/dist/websocket/collab/room.js +1 -1
- package/dist/websocket/controllers/base.js +12 -0
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/handlers/subscribe.js +1 -1
- package/dist/websocket/messages.js +64 -64
- package/dist/websocket/utils/items.js +2 -2
- package/license +90 -80
- package/package.json +33 -33
- package/dist/controllers/mcp.js +0 -31
- package/dist/utils/job-queue.js +0 -24
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { CUSTOM_LLM_FIELDS } from "../constants.js";
|
|
1
2
|
import { ItemsService } from "./items.js";
|
|
3
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
2
4
|
import { sendReport } from "../telemetry/lib/send-report.js";
|
|
3
5
|
import "../telemetry/index.js";
|
|
6
|
+
import "../license/index.js";
|
|
7
|
+
import { InvalidPayloadError, ResourceRestrictedError } from "@directus/errors";
|
|
4
8
|
import { version } from "directus/version";
|
|
5
9
|
|
|
6
10
|
//#region src/services/settings.ts
|
|
@@ -8,6 +12,37 @@ var SettingsService = class extends ItemsService {
|
|
|
8
12
|
constructor(options) {
|
|
9
13
|
super("directus_settings", options);
|
|
10
14
|
}
|
|
15
|
+
async createOne(data, opts) {
|
|
16
|
+
if (this.accountability !== null) {
|
|
17
|
+
if ("license_key" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_key" value manually` });
|
|
18
|
+
if ("license_token" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_token" value manually` });
|
|
19
|
+
}
|
|
20
|
+
const entitlementManager = getEntitlementManager();
|
|
21
|
+
const changesLLM = CUSTOM_LLM_FIELDS.some((field) => field in data && data[field] !== null);
|
|
22
|
+
if (!entitlementManager.isEntitled("custom_llms_enabled") && changesLLM) throw new ResourceRestrictedError({ category: "custom_llms_enabled" });
|
|
23
|
+
const result = await super.createOne(data, opts);
|
|
24
|
+
if (changesLLM) await getEntitlementManager().clearCache("custom_llms_enabled");
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
async updateMany(keys, data, opts) {
|
|
28
|
+
if (this.accountability !== null) {
|
|
29
|
+
if ("license_key" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_key" value manually` });
|
|
30
|
+
if ("license_token" in data) throw new InvalidPayloadError({ reason: `You can't change the "license_token" value manually` });
|
|
31
|
+
}
|
|
32
|
+
const entitlementManager = getEntitlementManager();
|
|
33
|
+
const changesLLM = CUSTOM_LLM_FIELDS.some((field) => field in data && data[field] !== null);
|
|
34
|
+
if (!entitlementManager.isEntitled("custom_llms_enabled") && changesLLM) throw new ResourceRestrictedError({ category: "custom_llms_enabled" });
|
|
35
|
+
const result = await super.updateMany(keys, data, opts);
|
|
36
|
+
if (changesLLM) await entitlementManager.clearCache("custom_llms_enabled");
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
async readByQuery(query, opts) {
|
|
40
|
+
const data = await super.readByQuery(query, opts);
|
|
41
|
+
if (!getEntitlementManager().isEntitled("custom_llms_enabled") && this.accountability !== null) {
|
|
42
|
+
for (const record of data) for (const field of CUSTOM_LLM_FIELDS) if (record[field]) record[field] = null;
|
|
43
|
+
}
|
|
44
|
+
return data;
|
|
45
|
+
}
|
|
11
46
|
async setOwner(data) {
|
|
12
47
|
const { project_id } = await this.knex.select("project_id").from("directus_settings").first();
|
|
13
48
|
sendReport({
|
|
@@ -19,10 +54,9 @@ var SettingsService = class extends ItemsService {
|
|
|
19
54
|
});
|
|
20
55
|
return await this.upsertSingleton({
|
|
21
56
|
project_owner: data.project_owner,
|
|
22
|
-
project_usage: data.project_usage,
|
|
23
|
-
org_name: data.org_name,
|
|
24
57
|
product_updates: data.product_updates,
|
|
25
|
-
|
|
58
|
+
project_usage: data.project_usage,
|
|
59
|
+
org_name: data.org_name
|
|
26
60
|
});
|
|
27
61
|
}
|
|
28
62
|
};
|
package/dist/services/users.js
CHANGED
|
@@ -12,11 +12,14 @@ import { createDefaultAccountability } from "../permissions/utils/create-default
|
|
|
12
12
|
import isUrlAllowed from "../utils/is-url-allowed.js";
|
|
13
13
|
import { verifyJWT } from "../utils/jwt.js";
|
|
14
14
|
import { stall } from "../utils/stall.js";
|
|
15
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
15
16
|
import { SettingsService } from "./settings.js";
|
|
17
|
+
import "../license/index.js";
|
|
16
18
|
import { useEnv } from "@directus/env";
|
|
17
19
|
import { ForbiddenError, InvalidInviteError, InvalidPayloadError, RecordNotUniqueError } from "@directus/errors";
|
|
18
20
|
import { getSimpleHash, toArray, validatePayload } from "@directus/utils";
|
|
19
21
|
import { isEmpty } from "lodash-es";
|
|
22
|
+
import { USER_INACTIVE_LICENSE_STATUS } from "@directus/constants";
|
|
20
23
|
import { performance } from "perf_hooks";
|
|
21
24
|
import Joi from "joi";
|
|
22
25
|
import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from "@directus/validation";
|
|
@@ -187,7 +190,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
187
190
|
}
|
|
188
191
|
if ("role" in data) opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
|
|
189
192
|
if ("status" in data) if (data["status"] === "active") opts.userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
|
|
190
|
-
else opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.
|
|
193
|
+
else opts.userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.RemainingAdmins;
|
|
191
194
|
if (opts.userIntegrityCheckFlags) opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
|
|
192
195
|
const result = await super.updateMany(keys, data, opts);
|
|
193
196
|
if (data["status"] !== void 0 && data["status"] !== "active") await this.clearUserSessions(keys);
|
|
@@ -213,6 +216,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
213
216
|
await this.knex("directus_versions").update({ user_updated: null }).whereIn("user_updated", keys);
|
|
214
217
|
await super.deleteMany(keys, opts);
|
|
215
218
|
await this.clearUserSessions(keys);
|
|
219
|
+
await getEntitlementManager().clearCache("seats", "sso_enabled");
|
|
216
220
|
return keys;
|
|
217
221
|
}
|
|
218
222
|
async inviteUser(email, role, url, subject) {
|
|
@@ -258,12 +262,15 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
258
262
|
if (scope !== "invite") throw new ForbiddenError();
|
|
259
263
|
const user = await this.getUserByEmail(email);
|
|
260
264
|
if (user?.status !== "invited") throw new InvalidInviteError();
|
|
261
|
-
|
|
265
|
+
const service = new UsersService({
|
|
262
266
|
knex: this.knex,
|
|
263
267
|
schema: this.schema
|
|
264
|
-
})
|
|
268
|
+
});
|
|
269
|
+
const { allowed: isWithinLicenseLimits } = await getEntitlementManager().check("seats");
|
|
270
|
+
const status = isWithinLicenseLimits ? "active" : USER_INACTIVE_LICENSE_STATUS;
|
|
271
|
+
await service.updateOne(user.id, {
|
|
265
272
|
password,
|
|
266
|
-
status
|
|
273
|
+
status
|
|
267
274
|
});
|
|
268
275
|
}
|
|
269
276
|
async registerUser(input) {
|
|
@@ -310,7 +317,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
310
317
|
email: input.email,
|
|
311
318
|
scope: "pending-registration"
|
|
312
319
|
};
|
|
313
|
-
const token = jwt.sign(payload,
|
|
320
|
+
const token = jwt.sign(payload, getSecret(), {
|
|
314
321
|
expiresIn: env["EMAIL_VERIFICATION_TOKEN_TTL"],
|
|
315
322
|
issuer: "directus"
|
|
316
323
|
});
|
|
@@ -334,7 +341,7 @@ var UsersService = class UsersService extends ItemsService {
|
|
|
334
341
|
await stall(STALL_TIME, timeStart);
|
|
335
342
|
}
|
|
336
343
|
async verifyRegistration(token) {
|
|
337
|
-
const { email, scope } = verifyJWT(token,
|
|
344
|
+
const { email, scope } = verifyJWT(token, getSecret());
|
|
338
345
|
if (scope !== "pending-registration") throw new ForbiddenError();
|
|
339
346
|
const user = await this.getUserByEmail(email);
|
|
340
347
|
if (user?.status !== "unverified") throw new InvalidPayloadError({ reason: "Invalid verification code" });
|
package/dist/services/utils.js
CHANGED
|
@@ -4,6 +4,8 @@ import { fetchAllowedFields } from "../permissions/modules/fetch-allowed-fields/
|
|
|
4
4
|
import emitter_default from "../emitter.js";
|
|
5
5
|
import { validateAccess } from "../permissions/modules/validate-access/validate-access.js";
|
|
6
6
|
import { shouldClearCache } from "../utils/should-clear-cache.js";
|
|
7
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
8
|
+
import "../license/index.js";
|
|
7
9
|
import { ForbiddenError, InvalidPayloadError } from "@directus/errors";
|
|
8
10
|
import { systemCollectionRows } from "@directus/system-data";
|
|
9
11
|
|
|
@@ -74,7 +76,10 @@ var UtilsService = class {
|
|
|
74
76
|
async clearCache({ system }) {
|
|
75
77
|
if (this.accountability?.admin !== true) throw new ForbiddenError();
|
|
76
78
|
const { cache } = getCache();
|
|
77
|
-
if (system)
|
|
79
|
+
if (system) {
|
|
80
|
+
await clearSystemCache({ forced: true });
|
|
81
|
+
await getEntitlementManager().clearCache();
|
|
82
|
+
}
|
|
78
83
|
return cache?.clear();
|
|
79
84
|
}
|
|
80
85
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import "../packages/types/dist/index.js";
|
|
1
2
|
import { getCache } from "../cache.js";
|
|
2
3
|
import { getHelpers } from "../database/helpers/index.js";
|
|
3
4
|
import emitter_default from "../emitter.js";
|
|
@@ -8,10 +9,10 @@ import { splitRecursive } from "../utils/versioning/split-recursive.js";
|
|
|
8
9
|
import { ItemsService } from "./items.js";
|
|
9
10
|
import { ActivityService } from "./activity.js";
|
|
10
11
|
import { RevisionsService } from "./revisions.js";
|
|
11
|
-
import { ForbiddenError, InvalidPayloadError, UnprocessableContentError } from "@directus/errors";
|
|
12
|
+
import { ForbiddenError, InvalidPayloadError, UnprocessableContentError, VersionHashMismatchError } from "@directus/errors";
|
|
12
13
|
import { deepMapWithSchema } from "@directus/utils";
|
|
13
|
-
import { assign, get, isEqual, isPlainObject, pick } from "lodash-es";
|
|
14
|
-
import { Action } from "@directus/constants";
|
|
14
|
+
import { assign, get, isEqual, isNil, isPlainObject, pick } from "lodash-es";
|
|
15
|
+
import { Action, VERSION_KEY_DRAFT, isPublishedVersionKey } from "@directus/constants";
|
|
15
16
|
import hash from "object-hash";
|
|
16
17
|
import Joi from "joi";
|
|
17
18
|
|
|
@@ -21,16 +22,23 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
21
22
|
super("directus_versions", options);
|
|
22
23
|
}
|
|
23
24
|
async validateCreateData(data) {
|
|
24
|
-
const
|
|
25
|
+
const versionCreateSchema = Joi.object({
|
|
25
26
|
key: Joi.string().required(),
|
|
26
27
|
name: Joi.string().allow(null),
|
|
27
28
|
collection: Joi.string().required(),
|
|
28
|
-
item: Joi.string().
|
|
29
|
-
})
|
|
29
|
+
item: Joi.string().allow(null)
|
|
30
|
+
});
|
|
31
|
+
const itemLess = isNil(data["item"]);
|
|
32
|
+
const { error } = versionCreateSchema.validate(data);
|
|
30
33
|
if (error) throw new InvalidPayloadError({ reason: error.message });
|
|
31
|
-
if (data["key"]
|
|
34
|
+
if (isPublishedVersionKey(data["key"])) throw new InvalidPayloadError({ reason: `"${data["key"]}" is a reserved version key` });
|
|
35
|
+
if (itemLess && data["key"] !== VERSION_KEY_DRAFT) throw new InvalidPayloadError({ reason: `"key" must be "${VERSION_KEY_DRAFT}" for versions not linked to an item` });
|
|
32
36
|
if (this.accountability) try {
|
|
33
|
-
await validateAccess({
|
|
37
|
+
await validateAccess(itemLess ? {
|
|
38
|
+
accountability: this.accountability,
|
|
39
|
+
action: "read",
|
|
40
|
+
collection: data["collection"]
|
|
41
|
+
} : {
|
|
34
42
|
accountability: this.accountability,
|
|
35
43
|
action: "read",
|
|
36
44
|
collection: data["collection"],
|
|
@@ -47,6 +55,7 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
47
55
|
knex: this.knex,
|
|
48
56
|
schema: this.schema
|
|
49
57
|
}).readOne(data["collection"])).meta?.versioning) throw new UnprocessableContentError({ reason: `Content Versioning is not enabled for collection "${data["collection"]}"` });
|
|
58
|
+
if (itemLess) return;
|
|
50
59
|
if ((await new VersionsService({
|
|
51
60
|
knex: this.knex,
|
|
52
61
|
schema: this.schema
|
|
@@ -73,18 +82,27 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
73
82
|
mainHash
|
|
74
83
|
};
|
|
75
84
|
}
|
|
76
|
-
async
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
async getVersionSaves(key, collection, item, mapDelta = true) {
|
|
86
|
+
let itemFilter = {};
|
|
87
|
+
if (item) itemFilter = { item: { _eq: item } };
|
|
88
|
+
let versions = await this.readByQuery({
|
|
89
|
+
filter: {
|
|
90
|
+
key: { _eq: key },
|
|
91
|
+
collection: { _eq: collection },
|
|
92
|
+
...itemFilter
|
|
93
|
+
},
|
|
94
|
+
limit: -1
|
|
95
|
+
});
|
|
96
|
+
if (mapDelta) versions = versions.map((version) => {
|
|
97
|
+
if (version.delta) version.delta = this.mapDelta(version);
|
|
98
|
+
return version;
|
|
99
|
+
});
|
|
100
|
+
return versions;
|
|
84
101
|
}
|
|
85
102
|
async createOne(data, opts) {
|
|
86
103
|
await this.validateCreateData(data);
|
|
87
|
-
data["hash"] = hash(await this.getMainItem(data["collection"], data["item"]));
|
|
104
|
+
if (data["item"]) data["hash"] = hash(await this.getMainItem(data["collection"], data["item"]));
|
|
105
|
+
else data["hash"] = null;
|
|
88
106
|
return super.createOne(data, opts);
|
|
89
107
|
}
|
|
90
108
|
async readOne(key, query = {}, opts) {
|
|
@@ -105,66 +123,105 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
105
123
|
async updateMany(keys, data, opts) {
|
|
106
124
|
const { error } = Joi.object({
|
|
107
125
|
key: Joi.string(),
|
|
108
|
-
name: Joi.string().allow(null)
|
|
126
|
+
name: Joi.string().allow(null),
|
|
127
|
+
item: Joi.string().allow(null)
|
|
109
128
|
}).validate(data);
|
|
110
129
|
if (error) throw new InvalidPayloadError({ reason: error.message });
|
|
111
|
-
if ("key"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
if (isPublishedVersionKey(data["key"])) throw new InvalidPayloadError({ reason: `"${data["key"]}" is a reserved version key` });
|
|
131
|
+
const keyCombos = /* @__PURE__ */ new Set();
|
|
132
|
+
for (const pk of keys) {
|
|
133
|
+
const existingVersion = await this.readOne(pk, { fields: [
|
|
134
|
+
"collection",
|
|
135
|
+
"item",
|
|
136
|
+
"key"
|
|
137
|
+
] });
|
|
138
|
+
const collection = existingVersion.collection;
|
|
139
|
+
const item = "item" in data ? data["item"] : existingVersion.item;
|
|
140
|
+
const key = "key" in data ? data["key"] : existingVersion.key;
|
|
141
|
+
if (key !== VERSION_KEY_DRAFT && item === null) throw new InvalidPayloadError({ reason: `"key" must be "${VERSION_KEY_DRAFT}" for versions not linked to an item` });
|
|
142
|
+
if (item === null) continue;
|
|
143
|
+
const keyCombo = `${key}-${collection}-${item}`;
|
|
144
|
+
if (keyCombos.has(keyCombo)) throw new UnprocessableContentError({ reason: `Cannot update multiple versions on "${item}" in collection "${collection}" to the same key "${key}"` });
|
|
145
|
+
keyCombos.add(keyCombo);
|
|
146
|
+
if ((await super.readByQuery({
|
|
147
|
+
aggregate: { count: ["*"] },
|
|
148
|
+
filter: {
|
|
149
|
+
id: { _neq: pk },
|
|
150
|
+
key: { _eq: key },
|
|
151
|
+
collection: { _eq: collection },
|
|
152
|
+
item: { _eq: item }
|
|
153
|
+
}
|
|
154
|
+
}))[0]["count"] > 0) throw new UnprocessableContentError({ reason: `Version "${key}" already exists for item "${item}" in collection "${collection}"` });
|
|
129
155
|
}
|
|
130
156
|
return super.updateMany(keys, data, opts);
|
|
131
157
|
}
|
|
132
|
-
async save(key, delta) {
|
|
158
|
+
async save(key, delta, opts) {
|
|
133
159
|
const version = await super.readOne(key);
|
|
134
160
|
const payloadService = new PayloadService(this.collection, {
|
|
135
161
|
accountability: this.accountability,
|
|
136
162
|
knex: this.knex,
|
|
137
163
|
schema: this.schema
|
|
138
164
|
});
|
|
139
|
-
const activityService = new ActivityService({
|
|
140
|
-
knex: this.knex,
|
|
141
|
-
schema: this.schema
|
|
142
|
-
});
|
|
143
|
-
const revisionsService = new RevisionsService({
|
|
144
|
-
knex: this.knex,
|
|
145
|
-
schema: this.schema
|
|
146
|
-
});
|
|
147
165
|
const { item, collection, delta: existingDelta } = version;
|
|
148
|
-
const activity = await activityService.createOne({
|
|
149
|
-
action: Action.VERSION_SAVE,
|
|
150
|
-
user: this.accountability?.user ?? null,
|
|
151
|
-
collection,
|
|
152
|
-
ip: this.accountability?.ip ?? null,
|
|
153
|
-
user_agent: this.accountability?.userAgent ?? null,
|
|
154
|
-
origin: this.accountability?.origin ?? null,
|
|
155
|
-
item
|
|
156
|
-
});
|
|
157
|
-
const helpers = getHelpers(this.knex);
|
|
158
166
|
let revisionDelta = await payloadService.prepareDelta(delta);
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
+
if (item) {
|
|
168
|
+
const trackingAccountability = this.schema.collections[collection]?.accountability ?? null;
|
|
169
|
+
if (trackingAccountability !== null) {
|
|
170
|
+
const revisionsService = new RevisionsService({
|
|
171
|
+
knex: this.knex,
|
|
172
|
+
schema: this.schema
|
|
173
|
+
});
|
|
174
|
+
let patchedExistingRevision = false;
|
|
175
|
+
if (opts?.patchRevision && trackingAccountability === "all") {
|
|
176
|
+
const [latestRevision] = await revisionsService.readByQuery({
|
|
177
|
+
filter: { version: { _eq: key } },
|
|
178
|
+
sort: ["-activity.timestamp"],
|
|
179
|
+
limit: 1,
|
|
180
|
+
fields: [
|
|
181
|
+
"id",
|
|
182
|
+
"data",
|
|
183
|
+
"delta",
|
|
184
|
+
"activity.user"
|
|
185
|
+
]
|
|
186
|
+
});
|
|
187
|
+
const currentUser = this.accountability?.user ?? null;
|
|
188
|
+
const latestRevisionUser = (latestRevision?.["activity"])?.user ?? null;
|
|
189
|
+
if (latestRevision && latestRevisionUser === currentUser) {
|
|
190
|
+
const mergedRevisionData = assign({}, latestRevision["data"], revisionDelta);
|
|
191
|
+
const mergedRevisionDelta = assign({}, latestRevision["delta"], revisionDelta);
|
|
192
|
+
await revisionsService.updateOne(latestRevision["id"], {
|
|
193
|
+
data: mergedRevisionData,
|
|
194
|
+
delta: mergedRevisionDelta
|
|
195
|
+
});
|
|
196
|
+
patchedExistingRevision = true;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!patchedExistingRevision) {
|
|
200
|
+
const activity = await new ActivityService({
|
|
201
|
+
knex: this.knex,
|
|
202
|
+
schema: this.schema
|
|
203
|
+
}).createOne({
|
|
204
|
+
action: Action.VERSION_SAVE,
|
|
205
|
+
user: this.accountability?.user ?? null,
|
|
206
|
+
collection,
|
|
207
|
+
ip: this.accountability?.ip ?? null,
|
|
208
|
+
user_agent: this.accountability?.userAgent ?? null,
|
|
209
|
+
origin: this.accountability?.origin ?? null,
|
|
210
|
+
item
|
|
211
|
+
});
|
|
212
|
+
if (trackingAccountability === "all") await revisionsService.createOne({
|
|
213
|
+
activity,
|
|
214
|
+
version: key,
|
|
215
|
+
collection,
|
|
216
|
+
item,
|
|
217
|
+
data: revisionDelta,
|
|
218
|
+
delta: revisionDelta
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
167
223
|
revisionDelta = revisionDelta ? revisionDelta : null;
|
|
224
|
+
const helpers = getHelpers(this.knex);
|
|
168
225
|
const date = new Date(helpers.date.writeTimestamp((/* @__PURE__ */ new Date()).toISOString()));
|
|
169
226
|
deepMapObjects(revisionDelta, (object, path) => {
|
|
170
227
|
const existing = get(existingDelta, path);
|
|
@@ -185,22 +242,29 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
185
242
|
if (shouldClearCache(cache, void 0, collection)) cache.clear();
|
|
186
243
|
return finalVersionDelta;
|
|
187
244
|
}
|
|
188
|
-
async promote(version,
|
|
245
|
+
async promote(version, opts) {
|
|
189
246
|
const { collection, item, delta } = await super.readOne(version);
|
|
190
|
-
if (
|
|
247
|
+
if (item && typeof opts?.mainHash !== "string") throw new InvalidPayloadError({ reason: `"mainHash" field is required` });
|
|
248
|
+
if (this.accountability) await validateAccess(item ? {
|
|
191
249
|
accountability: this.accountability,
|
|
192
250
|
action: "update",
|
|
193
251
|
collection,
|
|
194
252
|
primaryKeys: [item]
|
|
253
|
+
} : {
|
|
254
|
+
accountability: this.accountability,
|
|
255
|
+
action: "create",
|
|
256
|
+
collection
|
|
195
257
|
}, {
|
|
196
258
|
schema: this.schema,
|
|
197
259
|
knex: this.knex
|
|
198
260
|
});
|
|
199
261
|
if (!delta) throw new UnprocessableContentError({ reason: `No changes to promote` });
|
|
200
|
-
|
|
201
|
-
|
|
262
|
+
if (item) {
|
|
263
|
+
const { outdated, mainHash } = await this.verifyHash(collection, item, opts?.mainHash);
|
|
264
|
+
if (outdated) throw new VersionHashMismatchError({ mainHash });
|
|
265
|
+
}
|
|
202
266
|
const { rawDelta, defaultOverwrites } = splitRecursive(delta);
|
|
203
|
-
const payloadToUpdate = fields ? pick(rawDelta, fields) : rawDelta;
|
|
267
|
+
const payloadToUpdate = opts?.fields ? pick(rawDelta, opts.fields) : rawDelta;
|
|
204
268
|
const itemsService = new ItemsService(collection, {
|
|
205
269
|
accountability: this.accountability,
|
|
206
270
|
knex: this.knex,
|
|
@@ -215,7 +279,12 @@ var VersionsService = class VersionsService extends ItemsService {
|
|
|
215
279
|
schema: this.schema,
|
|
216
280
|
accountability: this.accountability
|
|
217
281
|
});
|
|
218
|
-
|
|
282
|
+
let updatedItemKey;
|
|
283
|
+
if (item) updatedItemKey = await itemsService.updateOne(item, payloadAfterHooks, { overwriteDefaults: defaultOverwrites });
|
|
284
|
+
else {
|
|
285
|
+
updatedItemKey = await itemsService.createOne(payloadAfterHooks, { overwriteDefaults: defaultOverwrites });
|
|
286
|
+
await this.updateOne(version, { item: String(updatedItemKey) });
|
|
287
|
+
}
|
|
219
288
|
emitter_default.emitAction(["items.promote", `${collection}.items.promote`], {
|
|
220
289
|
payload: payloadAfterHooks,
|
|
221
290
|
collection,
|
|
@@ -39,6 +39,7 @@ function calculateFieldDepth(obj, dotNotationKeys = []) {
|
|
|
39
39
|
const keys = Object.keys(obj);
|
|
40
40
|
for (const key of keys) {
|
|
41
41
|
const nestedValue = obj[key];
|
|
42
|
+
if (key === "_json") continue;
|
|
42
43
|
if (dotNotationKeys.includes(key) && nestedValue) {
|
|
43
44
|
let sortDepth = 0;
|
|
44
45
|
for (const sortKey of nestedValue) if (sortKey) sortDepth = Math.max(sortKey.split(".").length, sortDepth);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isPlainObject } from "lodash-es";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/deep-freeze.ts
|
|
4
|
+
/**
|
|
5
|
+
* Recursively freezes arrays and plain objects so the entire structure is immutable.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const frozen = deepFreeze({ a: { b: 1 } });
|
|
9
|
+
* frozen.a.b = 2; // throws in strict mode
|
|
10
|
+
*/
|
|
11
|
+
function deepFreeze(value) {
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
for (const item of value) deepFreeze(item);
|
|
14
|
+
return Object.freeze(value);
|
|
15
|
+
}
|
|
16
|
+
if (isPlainObject(value)) {
|
|
17
|
+
for (const item of Object.values(value)) deepFreeze(item);
|
|
18
|
+
return Object.freeze(value);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
//#endregion
|
|
24
|
+
export { deepFreeze };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/utils/extract-function-name.ts
|
|
2
|
+
/**
|
|
3
|
+
* Extracts the function name from a function call string, e.g. `year(date_created)` → `'year'`.
|
|
4
|
+
* Returns null if the string is not a recognized function call.
|
|
5
|
+
*/
|
|
6
|
+
function extractFunctionName(str) {
|
|
7
|
+
const trimmed = str.trim();
|
|
8
|
+
if (!trimmed.includes("(") || !trimmed.endsWith(")")) return null;
|
|
9
|
+
return trimmed.split("(")[0] ?? null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { extractFunctionName };
|
|
@@ -10,7 +10,7 @@ import { cloneFields, validateFieldsEligibility } from "./translations-shared.js
|
|
|
10
10
|
import { dbSafeIdentifierSchema } from "./translations-validation.js";
|
|
11
11
|
import { InvalidPayloadError } from "@directus/errors";
|
|
12
12
|
import { fromZodError } from "zod-validation-error";
|
|
13
|
-
import { z } from "zod";
|
|
13
|
+
import { z as z$1 } from "zod";
|
|
14
14
|
|
|
15
15
|
//#region src/utils/generate-translations.ts
|
|
16
16
|
const logger = useLogger();
|
|
@@ -31,15 +31,15 @@ function buildTranslationsAliasField(languagesFields) {
|
|
|
31
31
|
}
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
const GenerateTranslationsInput = z.object({
|
|
34
|
+
const GenerateTranslationsInput = z$1.object({
|
|
35
35
|
collection: dbSafeIdentifierSchema,
|
|
36
|
-
fields: z.array(dbSafeIdentifierSchema).min(1),
|
|
36
|
+
fields: z$1.array(dbSafeIdentifierSchema).min(1),
|
|
37
37
|
translationsCollection: dbSafeIdentifierSchema.optional(),
|
|
38
38
|
languagesCollection: dbSafeIdentifierSchema.optional(),
|
|
39
39
|
parentFkField: dbSafeIdentifierSchema.optional(),
|
|
40
40
|
languageFkField: dbSafeIdentifierSchema.optional(),
|
|
41
|
-
createLanguagesCollection: z.boolean().optional().default(true),
|
|
42
|
-
seedLanguages: z.boolean().optional().default(true)
|
|
41
|
+
createLanguagesCollection: z$1.boolean().optional().default(true),
|
|
42
|
+
seedLanguages: z$1.boolean().optional().default(true)
|
|
43
43
|
});
|
|
44
44
|
async function generateTranslations(input, options) {
|
|
45
45
|
const parseResult = GenerateTranslationsInput.safeParse(input);
|
|
@@ -5,6 +5,7 @@ import { getSecret } from "./get-secret.js";
|
|
|
5
5
|
import { createDefaultAccountability } from "../permissions/utils/create-default-accountability.js";
|
|
6
6
|
import { verifyAccessJWT } from "./jwt.js";
|
|
7
7
|
import isDirectusJWT from "./is-directus-jwt.js";
|
|
8
|
+
import { parseOAuthScope } from "./parse-oauth-scope.js";
|
|
8
9
|
import { verifySessionJWT } from "./verify-session-jwt.js";
|
|
9
10
|
import { InvalidCredentialsError } from "@directus/errors";
|
|
10
11
|
|
|
@@ -15,8 +16,19 @@ async function getAccountabilityForToken(token, accountability) {
|
|
|
15
16
|
if (token) if (isDirectusJWT(token)) {
|
|
16
17
|
const payload = verifyAccessJWT(token, getSecret());
|
|
17
18
|
if ("session" in payload) {
|
|
18
|
-
await verifySessionJWT(payload);
|
|
19
|
+
const { oauth_client } = await verifySessionJWT(payload);
|
|
19
20
|
accountability.session = payload.session;
|
|
21
|
+
if (oauth_client !== null) {
|
|
22
|
+
let aud;
|
|
23
|
+
if (Array.isArray(payload.aud)) aud = payload.aud;
|
|
24
|
+
else if (payload.aud) aud = [String(payload.aud)];
|
|
25
|
+
else aud = [];
|
|
26
|
+
accountability.oauth = {
|
|
27
|
+
client: oauth_client,
|
|
28
|
+
scopes: parseOAuthScope(payload.scope),
|
|
29
|
+
aud
|
|
30
|
+
};
|
|
31
|
+
}
|
|
20
32
|
}
|
|
21
33
|
if (payload.share) accountability.share = payload.share;
|
|
22
34
|
if (payload.id) accountability.user = payload.id;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import database_default from "../database/index.js";
|
|
2
|
+
import { getFlowManager } from "../flows.js";
|
|
2
3
|
import { fetchPoliciesIpAccess } from "../permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js";
|
|
3
4
|
import { getGraphqlQueryAndVariables } from "./get-graphql-query-and-variables.js";
|
|
4
|
-
import { getFlowManager } from "../flows.js";
|
|
5
5
|
import { toArray } from "@directus/utils";
|
|
6
6
|
import { isEmpty, pick } from "lodash-es";
|
|
7
7
|
import { ipInNetworks } from "@directus/utils/node";
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getEntitlementManager } from "../license/entitlements/manager.js";
|
|
2
|
+
import "../license/index.js";
|
|
3
|
+
import { mergeFilters } from "@directus/utils";
|
|
4
|
+
|
|
5
|
+
//#region src/utils/get-history-filter-query.ts
|
|
6
|
+
function getHistoryFilterQuery(query, entitlement, buildFilter) {
|
|
7
|
+
const limit = getEntitlementManager().getEntitlementLimit(entitlement);
|
|
8
|
+
if (limit === null || !Number.isFinite(limit) || limit < 0) return query;
|
|
9
|
+
if (limit === 0) return {
|
|
10
|
+
...query,
|
|
11
|
+
limit: 0
|
|
12
|
+
};
|
|
13
|
+
const filter = mergeFilters(buildFilter(/* @__PURE__ */ new Date(Date.now() - limit * 1e3)), query.filter ?? null, "and");
|
|
14
|
+
if (!filter) return query;
|
|
15
|
+
return {
|
|
16
|
+
...query,
|
|
17
|
+
filter
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
export { getHistoryFilterQuery };
|
package/dist/utils/get-schema.js
CHANGED
|
@@ -12,7 +12,7 @@ import getDefaultValue from "./get-default-value.js";
|
|
|
12
12
|
import { getSystemFieldRowsWithAuthProviders } from "./get-field-system-rows.js";
|
|
13
13
|
import { useEnv } from "@directus/env";
|
|
14
14
|
import { parseJSON, toArray, toBoolean } from "@directus/utils";
|
|
15
|
-
import { mapValues } from "lodash-es";
|
|
15
|
+
import { mapValues, pick } from "lodash-es";
|
|
16
16
|
import { createInspector } from "@directus/schema";
|
|
17
17
|
import { systemCollectionRows } from "@directus/system-data";
|
|
18
18
|
|
|
@@ -74,7 +74,7 @@ async function getDatabaseSchema(database, schemaInspector) {
|
|
|
74
74
|
};
|
|
75
75
|
const systemFieldRows$1 = getSystemFieldRowsWithAuthProviders();
|
|
76
76
|
const schemaOverview = await schemaInspector.overview();
|
|
77
|
-
const collections = [...await database.select("collection", "singleton", "note", "sort_field", "accountability")
|
|
77
|
+
const collections = [...(await database.select("*").from("directus_collections")).filter((c) => !("status" in c) || c["status"] === "active").map((c) => pick(c, "collection", "singleton", "note", "sort_field", "accountability")), ...systemCollectionRows];
|
|
78
78
|
for (const [collection, info] of Object.entries(schemaOverview)) {
|
|
79
79
|
if (toArray(env["DB_EXCLUDE_TABLES"]).includes(collection)) {
|
|
80
80
|
logger.trace(`Collection "${collection}" is configured to be excluded and will be ignored`);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ItemsService } from "../services/items.js";
|
|
2
2
|
import { FilesService } from "../services/files.js";
|
|
3
3
|
import { FoldersService } from "../services/folders.js";
|
|
4
|
-
import { ActivityService } from "../services/activity.js";
|
|
5
4
|
import { AccessService } from "../services/access.js";
|
|
5
|
+
import { ActivityService } from "../services/activity.js";
|
|
6
|
+
import { FlowsService } from "../services/flows.js";
|
|
7
|
+
import { RevisionsService } from "../services/revisions.js";
|
|
6
8
|
import { SettingsService } from "../services/settings.js";
|
|
7
9
|
import { UsersService } from "../services/users.js";
|
|
8
10
|
import { NotificationsService } from "../services/notifications.js";
|
|
9
|
-
import { RevisionsService } from "../services/revisions.js";
|
|
10
11
|
import { CommentsService } from "../services/comments.js";
|
|
11
12
|
import { DashboardsService } from "../services/dashboards.js";
|
|
12
13
|
import { DeploymentProjectsService } from "../services/deployment-projects.js";
|
|
@@ -22,7 +23,6 @@ import { SharesService } from "../services/shares.js";
|
|
|
22
23
|
import { TranslationsService } from "../services/translations.js";
|
|
23
24
|
import { VersionsService } from "../services/versions.js";
|
|
24
25
|
import "../services/index.js";
|
|
25
|
-
import { FlowsService } from "../services/flows.js";
|
|
26
26
|
import { ForbiddenError } from "@directus/errors";
|
|
27
27
|
|
|
28
28
|
//#region src/utils/get-service.ts
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
//#region src/utils/parse-oauth-scope.ts
|
|
2
|
+
/**
|
|
3
|
+
* Parse an OAuth scope string into a deduplicated array of scope tokens.
|
|
4
|
+
* Per RFC 6749 Section 3.3, scope is a space-separated list of case-sensitive strings.
|
|
5
|
+
*/
|
|
6
|
+
function parseOAuthScope(scope) {
|
|
7
|
+
if (typeof scope !== "string" || scope.trim() === "") return [];
|
|
8
|
+
return [...new Set(scope.trim().split(/\s+/))];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
12
|
+
export { parseOAuthScope };
|