@directus/api 14.1.1 → 15.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 +3 -3
- package/dist/auth/drivers/oauth2.js +2 -3
- package/dist/auth/drivers/openid.js +2 -3
- package/dist/cli/commands/schema/apply.js +44 -33
- package/dist/cli/index.js +2 -2
- package/dist/database/index.d.ts +2 -1
- package/dist/database/index.js +2 -1
- package/dist/database/system-data/fields/settings.yaml +4 -0
- package/dist/database/system-data/fields/users.yaml +4 -0
- package/dist/database/system-data/relations/relations.yaml +4 -0
- package/dist/emitter.d.ts +1 -0
- package/dist/emitter.js +2 -1
- package/dist/env.d.ts +1 -0
- package/dist/env.js +6 -0
- package/dist/extensions/lib/get-shared-deps-mapping.js +1 -1
- package/dist/extensions/lib/sandbox/register/route.d.ts +1 -0
- package/dist/extensions/manager.d.ts +5 -0
- package/dist/extensions/manager.js +42 -16
- package/dist/logger.d.ts +2 -1
- package/dist/logger.js +1 -0
- package/dist/middleware/rate-limiter-ip.js +14 -11
- package/dist/redis/create-redis.d.ts +7 -0
- package/dist/redis/create-redis.js +12 -0
- package/dist/redis/index.d.ts +2 -0
- package/dist/redis/index.js +2 -0
- package/dist/redis/use-redis.d.ts +16 -0
- package/dist/redis/use-redis.js +22 -0
- package/dist/server.d.ts +2 -0
- package/dist/services/extensions.js +1 -1
- package/dist/services/graphql/index.js +50 -15
- package/dist/services/payload.js +3 -3
- package/dist/services/server.js +2 -3
- package/dist/services/specifications.js +3 -3
- package/dist/services/users.js +6 -6
- 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 +31 -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/utils/apply-query.js +13 -2
- package/dist/utils/get-cache-key.js +1 -1
- package/dist/utils/get-ip-from-req.d.ts +1 -1
- package/dist/utils/get-ip-from-req.js +1 -1
- package/dist/utils/get-permissions.js +0 -3
- package/dist/utils/get-snapshot-diff.js +2 -1
- package/dist/utils/get-snapshot.js +1 -1
- package/dist/utils/get-versioned-hash.js +1 -1
- package/dist/utils/md.d.ts +1 -1
- package/dist/utils/md.js +3 -2
- package/dist/utils/merge-permissions.js +19 -11
- package/dist/utils/validate-query.js +1 -0
- package/dist/utils/validate-snapshot.js +3 -3
- package/dist/websocket/controllers/base.d.ts +2 -0
- package/dist/websocket/controllers/graphql.d.ts +2 -0
- package/dist/websocket/controllers/graphql.js +1 -1
- package/dist/websocket/controllers/index.d.ts +2 -0
- package/dist/websocket/controllers/rest.d.ts +2 -0
- package/dist/websocket/types.d.ts +3 -1
- package/package.json +108 -109
- 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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {} from 'knex';
|
|
2
|
+
import { toBoolean } from '../../utils/to-boolean.js';
|
|
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
|
+
};
|
|
@@ -405,9 +405,12 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
405
405
|
if (column.includes('(') && column.includes(')')) {
|
|
406
406
|
const functionName = column.split('(')[0];
|
|
407
407
|
const type = getOutputTypeForFunction(functionName);
|
|
408
|
-
if (['
|
|
408
|
+
if (['integer', 'float', 'decimal'].includes(type)) {
|
|
409
409
|
compareValue = Number(compareValue);
|
|
410
410
|
}
|
|
411
|
+
else if (type === 'bigInteger') {
|
|
412
|
+
compareValue = BigInt(compareValue);
|
|
413
|
+
}
|
|
411
414
|
}
|
|
412
415
|
// Cast filter value (compareValue) based on type of field being filtered against
|
|
413
416
|
const [collection, field] = key.split('.');
|
|
@@ -422,7 +425,7 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
422
425
|
compareValue = helpers.date.parse(compareValue);
|
|
423
426
|
}
|
|
424
427
|
}
|
|
425
|
-
if (['
|
|
428
|
+
if (['integer', 'float', 'decimal'].includes(type)) {
|
|
426
429
|
if (Array.isArray(compareValue)) {
|
|
427
430
|
compareValue = compareValue.map((val) => Number(val));
|
|
428
431
|
}
|
|
@@ -430,6 +433,14 @@ export function applyFilter(knex, schema, rootQuery, rootFilter, collection, ali
|
|
|
430
433
|
compareValue = Number(compareValue);
|
|
431
434
|
}
|
|
432
435
|
}
|
|
436
|
+
if (type === 'bigInteger') {
|
|
437
|
+
if (Array.isArray(compareValue)) {
|
|
438
|
+
compareValue = compareValue.map((val) => BigInt(val));
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
compareValue = BigInt(compareValue);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
433
444
|
}
|
|
434
445
|
if (operator === '_eq') {
|
|
435
446
|
dbQuery[logical].where(selectionRaw, '=', compareValue);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import hash from 'object-hash';
|
|
2
2
|
import url from 'url';
|
|
3
3
|
import { getGraphqlQueryAndVariables } from './get-graphql-query-and-variables.js';
|
|
4
|
-
import { version } from '
|
|
4
|
+
import { version } from 'directus/version';
|
|
5
5
|
export function getCacheKey(req) {
|
|
6
6
|
const path = url.parse(req.originalUrl).pathname;
|
|
7
7
|
const isGraphQl = path?.startsWith('/graphql');
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Request } from 'express';
|
|
2
|
-
export declare function getIPFromReq(req: Request): string;
|
|
2
|
+
export declare function getIPFromReq(req: Request): string | null;
|
|
@@ -13,5 +13,5 @@ export function getIPFromReq(req) {
|
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
15
|
// IP addresses starting with ::ffff: are IPv4 addresses in IPv6 format. We can strip the prefix to get back to IPv4
|
|
16
|
-
return ip
|
|
16
|
+
return ip?.startsWith('::ffff:') ? ip.substring(7) : ip ?? null;
|
|
17
17
|
}
|
|
@@ -86,9 +86,6 @@ function parsePermissions(permissions) {
|
|
|
86
86
|
if (permission.permissions && typeof permission.permissions === 'string') {
|
|
87
87
|
permission.permissions = parseJSON(permission.permissions);
|
|
88
88
|
}
|
|
89
|
-
else if (permission.permissions === null) {
|
|
90
|
-
permission.permissions = {};
|
|
91
|
-
}
|
|
92
89
|
if (permission.validation && typeof permission.validation === 'string') {
|
|
93
90
|
permission.validation = parseJSON(permission.validation);
|
|
94
91
|
}
|
|
@@ -55,7 +55,8 @@ export function getSnapshotDiff(current, after) {
|
|
|
55
55
|
}),
|
|
56
56
|
...after.relations
|
|
57
57
|
.filter((afterRelation) => {
|
|
58
|
-
const currentRelation = current.relations.find((currentRelation) => currentRelation.collection === afterRelation.collection &&
|
|
58
|
+
const currentRelation = current.relations.find((currentRelation) => currentRelation.collection === afterRelation.collection &&
|
|
59
|
+
afterRelation.field === currentRelation.field);
|
|
59
60
|
return !!currentRelation === false;
|
|
60
61
|
})
|
|
61
62
|
.map((afterRelation) => ({
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { version } from 'directus/version';
|
|
1
2
|
import { fromPairs, isArray, isPlainObject, mapValues, omit, sortBy, toPairs } from 'lodash-es';
|
|
2
3
|
import getDatabase, { getDatabaseClient } from '../database/index.js';
|
|
3
4
|
import { CollectionsService } from '../services/collections.js';
|
|
4
5
|
import { FieldsService } from '../services/fields.js';
|
|
5
6
|
import { RelationsService } from '../services/relations.js';
|
|
6
7
|
import { getSchema } from './get-schema.js';
|
|
7
|
-
import { version } from './package.js';
|
|
8
8
|
import { sanitizeCollection, sanitizeField, sanitizeRelation } from './sanitize-schema.js';
|
|
9
9
|
export async function getSnapshot(options) {
|
|
10
10
|
const database = options?.database ?? getDatabase();
|
package/dist/utils/md.d.ts
CHANGED
package/dist/utils/md.js
CHANGED
|
@@ -3,6 +3,7 @@ import sanitizeHTML from 'sanitize-html';
|
|
|
3
3
|
/**
|
|
4
4
|
* Render and sanitize a markdown string
|
|
5
5
|
*/
|
|
6
|
-
export function md(
|
|
7
|
-
|
|
6
|
+
export function md(value) {
|
|
7
|
+
const markdown = marked.parse(value); /* Would only be a promise if used with async extensions */
|
|
8
|
+
return sanitizeHTML(markdown);
|
|
8
9
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { flatten, intersection,
|
|
1
|
+
import { flatten, intersection, isEqual, merge, omit } from 'lodash-es';
|
|
2
2
|
export function mergePermissions(strategy, ...permissions) {
|
|
3
3
|
const allPermissions = flatten(permissions);
|
|
4
4
|
const mergedPermissions = allPermissions
|
|
@@ -27,11 +27,15 @@ export function mergePermission(strategy, currentPerm, newPerm) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
else if (currentPerm.permissions) {
|
|
30
|
-
permissions
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
// Empty {} supersedes other permissions in _OR merge
|
|
31
|
+
if (strategy === 'or' && (isEqual(currentPerm.permissions, {}) || isEqual(newPerm.permissions, {}))) {
|
|
32
|
+
permissions = {};
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
permissions = {
|
|
36
|
+
[logicalKey]: [currentPerm.permissions, newPerm.permissions],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
35
39
|
}
|
|
36
40
|
else {
|
|
37
41
|
permissions = {
|
|
@@ -49,11 +53,15 @@ export function mergePermission(strategy, currentPerm, newPerm) {
|
|
|
49
53
|
};
|
|
50
54
|
}
|
|
51
55
|
else if (currentPerm.validation) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
// Empty {} supersedes other validations in _OR merge
|
|
57
|
+
if (strategy === 'or' && (isEqual(currentPerm.validation, {}) || isEqual(newPerm.validation, {}))) {
|
|
58
|
+
validation = {};
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
validation = {
|
|
62
|
+
[logicalKey]: [currentPerm.validation, newPerm.validation],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
else {
|
|
59
67
|
validation = {
|
|
@@ -4,7 +4,7 @@ import { ALIAS_TYPES } from '../constants.js';
|
|
|
4
4
|
import { getDatabaseClient } from '../database/index.js';
|
|
5
5
|
import { InvalidPayloadError } from '@directus/errors';
|
|
6
6
|
import { DatabaseClients } from '../types/index.js';
|
|
7
|
-
import { version
|
|
7
|
+
import { version } from 'directus/version';
|
|
8
8
|
const snapshotJoiSchema = Joi.object({
|
|
9
9
|
version: Joi.number().valid(1).required(),
|
|
10
10
|
directus: Joi.string().required(),
|
|
@@ -51,9 +51,9 @@ export function validateSnapshot(snapshot, force = false) {
|
|
|
51
51
|
// Bypass checks when "force" option is enabled
|
|
52
52
|
if (force)
|
|
53
53
|
return;
|
|
54
|
-
if (snapshot.directus !==
|
|
54
|
+
if (snapshot.directus !== version) {
|
|
55
55
|
throw new InvalidPayloadError({
|
|
56
|
-
reason: `Provided snapshot's directus version ${snapshot.directus} does not match the current instance's version ${
|
|
56
|
+
reason: `Provided snapshot's directus version ${snapshot.directus} does not match the current instance's version ${version}. You can bypass this check by passing the "force" query parameter`,
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
if (!snapshot.vendor) {
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
/// <reference types="node" resolution-mode="require"/>
|
|
4
4
|
/// <reference types="node" resolution-mode="require"/>
|
|
5
|
+
/// <reference types="node/http.js" />
|
|
6
|
+
/// <reference types="pino-http" />
|
|
5
7
|
import type { IncomingMessage, Server as httpServer } from 'http';
|
|
6
8
|
import type { ParsedUrlQuery } from 'querystring';
|
|
7
9
|
import type { RateLimiterAbstract } from 'rate-limiter-flexible';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node/http.js" />
|
|
3
|
+
/// <reference types="pino-http" />
|
|
2
4
|
import type { Server } from 'graphql-ws';
|
|
3
5
|
import type { Server as httpServer } from 'http';
|
|
4
6
|
import type { GraphQLSocket, UpgradeContext, WebSocketClient } from '../types.js';
|
|
@@ -37,7 +37,7 @@ export class GraphQLSubscriptionController extends SocketController {
|
|
|
37
37
|
send: (data) => new Promise((resolve, reject) => {
|
|
38
38
|
client.send(data, (err) => (err ? reject(err) : resolve()));
|
|
39
39
|
}),
|
|
40
|
-
close: (code, reason) => client.close(code, reason),
|
|
40
|
+
close: (code, reason) => client.close(code, reason), // for standard closures
|
|
41
41
|
onMessage: (cb) => {
|
|
42
42
|
client.on('parsed-message', async (message) => {
|
|
43
43
|
try {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node/http.js" />
|
|
3
|
+
/// <reference types="pino-http" />
|
|
2
4
|
import type { Server as httpServer } from 'http';
|
|
3
5
|
import { GraphQLSubscriptionController } from './graphql.js';
|
|
4
6
|
import { WebSocketController } from './rest.js';
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/// <reference types="node/http.js" />
|
|
3
|
+
/// <reference types="pino-http" />
|
|
2
4
|
import type { Server as httpServer } from 'http';
|
|
3
5
|
import { WebSocketMessage } from '../messages.js';
|
|
4
6
|
import SocketController from './base.js';
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" resolution-mode="require"/>
|
|
2
2
|
/// <reference types="node" resolution-mode="require"/>
|
|
3
3
|
/// <reference types="node" resolution-mode="require"/>
|
|
4
|
+
/// <reference types="node/http.js" />
|
|
5
|
+
/// <reference types="pino-http" />
|
|
4
6
|
import type { Accountability, Query } from '@directus/types';
|
|
5
7
|
import type { IncomingMessage } from 'http';
|
|
6
8
|
import type internal from 'stream';
|
|
@@ -12,7 +14,7 @@ export type AuthenticationState = {
|
|
|
12
14
|
};
|
|
13
15
|
export type WebSocketClient = WebSocket & AuthenticationState & {
|
|
14
16
|
uid: string | number;
|
|
15
|
-
auth_timer: NodeJS.
|
|
17
|
+
auth_timer: NodeJS.Timeout | null;
|
|
16
18
|
};
|
|
17
19
|
export type UpgradeRequest = IncomingMessage & AuthenticationState;
|
|
18
20
|
export type SubscriptionEvent = 'create' | 'update' | 'delete';
|