@directus/api 19.1.1 → 19.3.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.
Files changed (48) hide show
  1. package/dist/controllers/users.js +1 -0
  2. package/dist/controllers/utils.js +7 -5
  3. package/dist/database/helpers/index.d.ts +1 -1
  4. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +1 -0
  5. package/dist/database/helpers/schema/dialects/cockroachdb.js +13 -0
  6. package/dist/database/helpers/schema/dialects/mssql.d.ts +1 -0
  7. package/dist/database/helpers/schema/dialects/mssql.js +9 -0
  8. package/dist/database/helpers/schema/dialects/mysql.d.ts +1 -0
  9. package/dist/database/helpers/schema/dialects/mysql.js +17 -0
  10. package/dist/database/helpers/schema/dialects/oracle.d.ts +1 -0
  11. package/dist/database/helpers/schema/dialects/oracle.js +9 -0
  12. package/dist/database/helpers/schema/dialects/postgres.d.ts +4 -0
  13. package/dist/database/helpers/schema/dialects/postgres.js +14 -0
  14. package/dist/database/helpers/schema/dialects/sqlite.d.ts +1 -0
  15. package/dist/database/helpers/schema/dialects/sqlite.js +9 -0
  16. package/dist/database/helpers/schema/index.d.ts +3 -3
  17. package/dist/database/helpers/schema/index.js +3 -3
  18. package/dist/database/helpers/schema/types.d.ts +4 -0
  19. package/dist/database/helpers/schema/types.js +6 -0
  20. package/dist/middleware/graphql.js +5 -1
  21. package/dist/services/extensions.js +11 -8
  22. package/dist/services/graphql/index.js +15 -11
  23. package/dist/services/graphql/utils/sanitize-gql-schema.d.ts +8 -0
  24. package/dist/services/graphql/utils/sanitize-gql-schema.js +80 -0
  25. package/dist/services/roles.d.ts +1 -0
  26. package/dist/services/roles.js +200 -11
  27. package/dist/services/users.d.ts +1 -1
  28. package/dist/services/users.js +86 -17
  29. package/dist/telemetry/lib/get-report.js +22 -10
  30. package/dist/telemetry/types/report.d.ts +12 -0
  31. package/dist/telemetry/utils/check-increased-user-limits.d.ts +7 -0
  32. package/dist/telemetry/utils/check-increased-user-limits.js +22 -0
  33. package/dist/telemetry/utils/get-extension-count.d.ts +9 -0
  34. package/dist/telemetry/utils/get-extension-count.js +19 -0
  35. package/dist/telemetry/utils/get-field-count.d.ts +6 -0
  36. package/dist/telemetry/utils/get-field-count.js +12 -0
  37. package/dist/telemetry/utils/get-item-count.d.ts +10 -6
  38. package/dist/telemetry/utils/get-item-count.js +13 -9
  39. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +6 -0
  40. package/dist/telemetry/utils/get-role-counts-by-roles.js +27 -0
  41. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +11 -0
  42. package/dist/telemetry/utils/get-role-counts-by-users.js +34 -0
  43. package/dist/telemetry/utils/get-user-count.d.ts +3 -2
  44. package/dist/telemetry/utils/get-user-count.js +7 -4
  45. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +7 -0
  46. package/dist/telemetry/utils/get-user-counts-by-roles.js +35 -0
  47. package/dist/telemetry/utils/get-user-item-count.js +4 -2
  48. package/package.json +28 -28
@@ -3,13 +3,17 @@ export interface CollectionCount {
3
3
  collection: string;
4
4
  count: number;
5
5
  }
6
+ export interface CollectionCountTask {
7
+ collection: string;
8
+ where?: readonly [string, string, string | boolean | number];
9
+ }
6
10
  /**
7
- * Get the item count of the given collection in the given database
11
+ * Get the item count of the given task in the given database
8
12
  * @param db Knex instance to count against
9
- * @param collection Table to count rows in
13
+ * @param task Task to count rows for
10
14
  * @returns Collection name and count
11
15
  */
12
- export declare const countCollection: (db: Knex, collection: string) => Promise<CollectionCount>;
16
+ export declare const countCollection: (db: Knex, task: CollectionCountTask) => Promise<CollectionCount>;
13
17
  /**
14
18
  * Merge the given collection count in the object accumulator
15
19
  * Intended for use with .reduce()
@@ -19,8 +23,8 @@ export declare const countCollection: (db: Knex, collection: string) => Promise<
19
23
  */
20
24
  export declare const mergeResults: (acc: Record<string, number>, value: CollectionCount) => Record<string, number>;
21
25
  /**
22
- * Get an object of item counts for the given collections
26
+ * Get an object of item counts for the given tasks
23
27
  * @param db Database instance to get counts in
24
- * @param collections Array of table names to get count from
28
+ * @param tasks Array of tasks to get count for
25
29
  */
26
- export declare const getItemCount: <T extends readonly string[]>(db: Knex, collections: T) => Promise<Record<T[number], number>>;
30
+ export declare const getItemCount: <T extends readonly CollectionCountTask[]>(db: Knex, tasks: T) => Promise<Record<T[number]["collection"], number>>;
@@ -1,14 +1,18 @@
1
1
  import {} from 'knex';
2
2
  import pLimit from 'p-limit';
3
3
  /**
4
- * Get the item count of the given collection in the given database
4
+ * Get the item count of the given task in the given database
5
5
  * @param db Knex instance to count against
6
- * @param collection Table to count rows in
6
+ * @param task Task to count rows for
7
7
  * @returns Collection name and count
8
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) };
9
+ export const countCollection = async (db, task) => {
10
+ const query = db.count('*', { as: 'count' }).from(task.collection);
11
+ if (task.where) {
12
+ query.where(...task.where);
13
+ }
14
+ const count = await query.first();
15
+ return { collection: task.collection, count: Number(count?.['count'] ?? 0) };
12
16
  };
13
17
  /**
14
18
  * Merge the given collection count in the object accumulator
@@ -22,15 +26,15 @@ export const mergeResults = (acc, value) => {
22
26
  return acc;
23
27
  };
24
28
  /**
25
- * Get an object of item counts for the given collections
29
+ * Get an object of item counts for the given tasks
26
30
  * @param db Database instance to get counts in
27
- * @param collections Array of table names to get count from
31
+ * @param tasks Array of tasks to get count for
28
32
  */
29
- export const getItemCount = async (db, collections) => {
33
+ export const getItemCount = async (db, tasks) => {
30
34
  // Counts can be a little heavy if the table is very large, so we'll only ever execute 3 of these
31
35
  // queries simultaneously to not overload the database
32
36
  const limit = pLimit(3);
33
- const calls = collections.map((collection) => limit(countCollection, db, collection));
37
+ const calls = tasks.map((task) => limit(countCollection, db, task));
34
38
  const results = await Promise.all(calls);
35
39
  return results.reduce(mergeResults, {});
36
40
  };
@@ -0,0 +1,6 @@
1
+ import type { Knex } from 'knex';
2
+ import { type AccessTypeCount } from './get-user-count.js';
3
+ /**
4
+ * Get the role type counts by role IDs
5
+ */
6
+ export declare function getRoleCountsByRoles(db: Knex, roles: string[]): Promise<AccessTypeCount>;
@@ -0,0 +1,27 @@
1
+ import { toBoolean } from '@directus/utils';
2
+ import {} from './get-user-count.js';
3
+ /**
4
+ * Get the role type counts by role IDs
5
+ */
6
+ export async function getRoleCountsByRoles(db, roles) {
7
+ const counts = {
8
+ admin: 0,
9
+ app: 0,
10
+ api: 0,
11
+ };
12
+ const result = (await db.select('id', 'admin_access', 'app_access').from('directus_roles').whereIn('id', roles));
13
+ for (const role of result) {
14
+ const adminAccess = toBoolean(role.admin_access);
15
+ const appAccess = toBoolean(role.app_access);
16
+ if (adminAccess) {
17
+ counts.admin++;
18
+ }
19
+ else if (appAccess) {
20
+ counts.app++;
21
+ }
22
+ else {
23
+ counts.api++;
24
+ }
25
+ }
26
+ return counts;
27
+ }
@@ -0,0 +1,11 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ import type { AccessTypeCount } from './get-user-count.js';
4
+ type CountOptions = {
5
+ inactiveUsers?: boolean;
6
+ };
7
+ /**
8
+ * Get the role type counts by user IDs
9
+ */
10
+ export declare function getRoleCountsByUsers(db: Knex, userIds: PrimaryKey[], options?: CountOptions): Promise<AccessTypeCount>;
11
+ export {};
@@ -0,0 +1,34 @@
1
+ import { toBoolean } from '@directus/utils';
2
+ /**
3
+ * Get the role type counts by user IDs
4
+ */
5
+ export async function getRoleCountsByUsers(db, userIds, options = {}) {
6
+ const counts = {
7
+ admin: 0,
8
+ app: 0,
9
+ api: 0,
10
+ };
11
+ const result = await db
12
+ .count('directus_users.id', { as: 'count' })
13
+ .select('directus_roles.admin_access', 'directus_roles.app_access')
14
+ .from('directus_users')
15
+ .whereIn('directus_users.id', userIds)
16
+ .andWhere('directus_users.status', options.inactiveUsers ? '!=' : '=', 'active')
17
+ .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
18
+ .groupBy('directus_roles.admin_access', 'directus_roles.app_access');
19
+ for (const record of result) {
20
+ const adminAccess = toBoolean(record.admin_access);
21
+ const appAccess = toBoolean(record.app_access);
22
+ const count = Number(record.count);
23
+ if (adminAccess) {
24
+ counts.admin += count;
25
+ }
26
+ else if (appAccess) {
27
+ counts.app += count;
28
+ }
29
+ else {
30
+ counts.api += count;
31
+ }
32
+ }
33
+ return counts;
34
+ }
@@ -1,7 +1,8 @@
1
+ import type { PrimaryKey } from '@directus/types';
1
2
  import { type Knex } from 'knex';
2
- export interface UserCount {
3
+ export interface AccessTypeCount {
3
4
  admin: number;
4
5
  app: number;
5
6
  api: number;
6
7
  }
7
- export declare const getUserCount: (db: Knex) => Promise<UserCount>;
8
+ export declare const getUserCount: (db: Knex, ignoreIds?: PrimaryKey[]) => Promise<AccessTypeCount>;
@@ -1,6 +1,6 @@
1
1
  import { toBoolean } from '@directus/utils';
2
2
  import {} from 'knex';
3
- export const getUserCount = async (db) => {
3
+ export const getUserCount = async (db, ignoreIds = []) => {
4
4
  const counts = {
5
5
  admin: 0,
6
6
  app: 0,
@@ -10,20 +10,23 @@ export const getUserCount = async (db) => {
10
10
  .count('directus_users.id', { as: 'count' })
11
11
  .select('directus_roles.admin_access', 'directus_roles.app_access')
12
12
  .from('directus_users')
13
+ .whereNotIn('directus_users.id', ignoreIds)
14
+ .andWhere('directus_users.status', 'active')
13
15
  .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
16
+ .where('directus_users.status', '=', 'active')
14
17
  .groupBy('directus_roles.admin_access', 'directus_roles.app_access'));
15
18
  for (const record of result) {
16
19
  const adminAccess = toBoolean(record.admin_access);
17
20
  const appAccess = toBoolean(record.app_access);
18
21
  const count = Number(record.count);
19
22
  if (adminAccess) {
20
- counts.admin = count;
23
+ counts.admin += count;
21
24
  }
22
25
  else if (appAccess) {
23
- counts.app = count;
26
+ counts.app += count;
24
27
  }
25
28
  else {
26
- counts.api = count;
29
+ counts.api += count;
27
30
  }
28
31
  }
29
32
  return counts;
@@ -0,0 +1,7 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import type { Knex } from 'knex';
3
+ import { type AccessTypeCount } from './get-user-count.js';
4
+ /**
5
+ * Get the user type counts by role IDs
6
+ */
7
+ export declare function getUserCountsByRoles(db: Knex, roleIds: PrimaryKey[]): Promise<AccessTypeCount>;
@@ -0,0 +1,35 @@
1
+ import { toBoolean } from '@directus/utils';
2
+ import {} from './get-user-count.js';
3
+ /**
4
+ * Get the user type counts by role IDs
5
+ */
6
+ export async function getUserCountsByRoles(db, roleIds) {
7
+ const counts = {
8
+ admin: 0,
9
+ app: 0,
10
+ api: 0,
11
+ };
12
+ const result = (await db
13
+ .count('directus_users.id', { as: 'count' })
14
+ .select('directus_roles.admin_access', 'directus_roles.app_access')
15
+ .from('directus_users')
16
+ .whereIn('directus_roles.id', roleIds)
17
+ .andWhere('directus_users.status', '=', 'active')
18
+ .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
19
+ .groupBy('directus_roles.admin_access', 'directus_roles.app_access'));
20
+ for (const record of result) {
21
+ const adminAccess = toBoolean(record.admin_access);
22
+ const appAccess = toBoolean(record.app_access);
23
+ const count = Number(record.count);
24
+ if (adminAccess) {
25
+ counts.admin += count;
26
+ }
27
+ else if (appAccess) {
28
+ counts.app += count;
29
+ }
30
+ else {
31
+ counts.api += count;
32
+ }
33
+ }
34
+ return counts;
35
+ }
@@ -1,7 +1,7 @@
1
+ import { isSystemCollection } from '@directus/system-data';
1
2
  import {} from 'knex';
2
3
  import { getSchema } from '../../utils/get-schema.js';
3
4
  import { getItemCount } from './get-item-count.js';
4
- import { isSystemCollection } from '@directus/system-data';
5
5
  /**
6
6
  * Sum all passed values together. Meant to be used with .reduce()
7
7
  */
@@ -11,7 +11,9 @@ export const sum = (acc, val) => (acc += val);
11
11
  */
12
12
  export const getUserItemCount = async (db) => {
13
13
  const schema = await getSchema({ database: db });
14
- const userCollections = Object.keys(schema.collections).filter((collection) => isSystemCollection(collection) === false);
14
+ const userCollections = Object.keys(schema.collections)
15
+ .filter((collection) => isSystemCollection(collection) === false)
16
+ .map((collection) => ({ collection }));
15
17
  const counts = await getItemCount(db, userCollections);
16
18
  const collections = userCollections.length;
17
19
  const items = Object.values(counts).reduce(sum, 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@directus/api",
3
- "version": "19.1.1",
3
+ "version": "19.3.0",
4
4
  "description": "Directus is a real-time API and App dashboard for managing SQL database content",
5
5
  "keywords": [
6
6
  "directus",
@@ -67,7 +67,7 @@
67
67
  "@types/cookie": "0.6.0",
68
68
  "argon2": "0.40.1",
69
69
  "async": "3.2.5",
70
- "axios": "1.6.8",
70
+ "axios": "1.7.2",
71
71
  "busboy": "1.6.0",
72
72
  "bytes": "3.1.2",
73
73
  "camelcase": "8.0.0",
@@ -92,11 +92,11 @@
92
92
  "fs-extra": "11.2.0",
93
93
  "glob-to-regexp": "0.4.1",
94
94
  "graphql": "16.8.1",
95
- "graphql-compose": "9.0.10",
95
+ "graphql-compose": "9.0.11",
96
96
  "graphql-ws": "5.16.0",
97
97
  "helmet": "7.1.0",
98
98
  "icc": "3.0.0",
99
- "inquirer": "9.2.20",
99
+ "inquirer": "9.2.22",
100
100
  "ioredis": "5.4.1",
101
101
  "ip-matching": "2.1.2",
102
102
  "isolated-vm": "4.7.2",
@@ -108,7 +108,7 @@
108
108
  "keyv": "4.5.4",
109
109
  "knex": "3.1.0",
110
110
  "ldapjs": "2.3.3",
111
- "liquidjs": "10.12.0",
111
+ "liquidjs": "10.13.1",
112
112
  "lodash-es": "4.17.21",
113
113
  "marked": "12.0.2",
114
114
  "micromustache": "8.0.3",
@@ -121,14 +121,14 @@
121
121
  "node-schedule": "2.1.1",
122
122
  "nodemailer": "6.9.13",
123
123
  "object-hash": "3.0.0",
124
- "openapi3-ts": "4.3.1",
124
+ "openapi3-ts": "4.3.2",
125
125
  "openid-client": "5.6.5",
126
126
  "ora": "8.0.1",
127
127
  "otplib": "12.0.1",
128
128
  "p-limit": "5.0.0",
129
129
  "p-queue": "8.0.1",
130
130
  "papaparse": "5.4.1",
131
- "pino": "9.0.0",
131
+ "pino": "9.1.0",
132
132
  "pino-http": "9.0.0",
133
133
  "pino-http-print": "3.1.0",
134
134
  "pino-pretty": "11.0.0",
@@ -146,29 +146,29 @@
146
146
  "ws": "8.17.0",
147
147
  "zod": "3.23.8",
148
148
  "zod-validation-error": "3.2.0",
149
+ "@directus/app": "12.1.3",
150
+ "@directus/env": "1.1.6",
149
151
  "@directus/constants": "11.0.4",
150
- "@directus/app": "12.1.1",
151
- "@directus/extensions": "1.0.5",
152
- "@directus/env": "1.1.4",
153
- "@directus/extensions-registry": "1.0.5",
154
- "@directus/errors": "0.3.0",
152
+ "@directus/errors": "0.3.2",
153
+ "@directus/extensions": "1.0.7",
154
+ "@directus/extensions-registry": "1.0.7",
155
155
  "@directus/format-title": "10.1.2",
156
- "@directus/extensions-sdk": "11.0.5",
157
- "@directus/memory": "1.0.7",
158
- "@directus/pressure": "1.0.19",
156
+ "@directus/extensions-sdk": "11.0.7",
157
+ "@directus/pressure": "1.0.20",
158
+ "@directus/memory": "1.0.9",
159
159
  "@directus/schema": "11.0.2",
160
- "@directus/storage": "10.0.12",
161
- "@directus/storage-driver-azure": "10.0.20",
162
- "@directus/storage-driver-cloudinary": "10.0.20",
163
- "@directus/storage-driver-gcs": "10.0.21",
164
- "@directus/storage-driver-local": "10.0.19",
165
- "@directus/specs": "10.2.9",
166
- "@directus/storage-driver-s3": "10.0.21",
167
- "@directus/storage-driver-supabase": "1.0.12",
168
- "@directus/system-data": "1.0.3",
169
- "@directus/utils": "11.0.8",
170
- "@directus/validation": "0.0.15",
171
- "directus": "10.11.1"
160
+ "@directus/specs": "10.2.10",
161
+ "@directus/storage": "10.0.13",
162
+ "@directus/storage-driver-azure": "10.0.22",
163
+ "@directus/storage-driver-cloudinary": "10.0.22",
164
+ "@directus/storage-driver-gcs": "10.0.23",
165
+ "@directus/storage-driver-local": "10.0.20",
166
+ "@directus/storage-driver-supabase": "1.0.14",
167
+ "@directus/storage-driver-s3": "10.0.23",
168
+ "@directus/system-data": "1.0.4",
169
+ "@directus/utils": "11.0.9",
170
+ "directus": "10.12.0",
171
+ "@directus/validation": "0.0.17"
172
172
  },
173
173
  "devDependencies": {
174
174
  "@ngneat/falso": "7.2.0",
@@ -211,7 +211,7 @@
211
211
  "vitest": "1.5.3",
212
212
  "@directus/random": "0.2.8",
213
213
  "@directus/tsconfig": "1.0.1",
214
- "@directus/types": "11.1.1"
214
+ "@directus/types": "11.1.2"
215
215
  },
216
216
  "optionalDependencies": {
217
217
  "@keyv/redis": "2.8.4",