@directus/api 20.0.0-rc.0 → 20.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.
Files changed (291) hide show
  1. package/dist/app.js +9 -4
  2. package/dist/auth/drivers/ldap.js +4 -4
  3. package/dist/auth/drivers/local.js +4 -4
  4. package/dist/auth/drivers/oauth2.js +4 -4
  5. package/dist/auth/drivers/openid.js +4 -2
  6. package/dist/cache.js +0 -3
  7. package/dist/cli/commands/bootstrap/index.js +2 -8
  8. package/dist/cli/commands/init/index.js +10 -9
  9. package/dist/cli/utils/defaults.d.ts +11 -4
  10. package/dist/cli/utils/defaults.js +1 -7
  11. package/dist/constants.d.ts +9 -1
  12. package/dist/constants.js +10 -0
  13. package/dist/controllers/auth.js +16 -5
  14. package/dist/controllers/permissions.js +2 -14
  15. package/dist/controllers/roles.js +1 -22
  16. package/dist/controllers/{access.d.ts → tus.d.ts} +1 -0
  17. package/dist/controllers/tus.js +72 -0
  18. package/dist/controllers/users.js +55 -0
  19. package/dist/database/helpers/fn/types.d.ts +1 -2
  20. package/dist/database/helpers/fn/types.js +1 -1
  21. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  22. package/dist/database/helpers/geometry/dialects/mssql.js +2 -4
  23. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  24. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  25. package/dist/database/helpers/geometry/dialects/oracle.js +3 -5
  26. package/dist/database/helpers/geometry/types.d.ts +1 -1
  27. package/dist/database/helpers/geometry/types.js +2 -4
  28. package/dist/database/index.js +1 -2
  29. package/dist/database/migrations/20240701A-add-tus-data.js +12 -0
  30. package/dist/database/{run-ast/types.d.ts → run-ast.d.ts} +9 -3
  31. package/dist/database/run-ast.js +450 -0
  32. package/dist/flows.js +4 -3
  33. package/dist/middleware/authenticate.js +7 -2
  34. package/dist/middleware/cache.js +1 -1
  35. package/dist/middleware/check-ip.d.ts +2 -0
  36. package/dist/middleware/check-ip.js +37 -0
  37. package/dist/middleware/get-permissions.d.ts +3 -0
  38. package/dist/middleware/get-permissions.js +10 -0
  39. package/dist/middleware/respond.js +1 -1
  40. package/dist/services/activity.js +10 -22
  41. package/dist/services/assets.d.ts +3 -2
  42. package/dist/services/assets.js +5 -10
  43. package/dist/services/authentication.js +26 -32
  44. package/dist/services/authorization.d.ts +17 -0
  45. package/dist/services/authorization.js +456 -0
  46. package/dist/services/collections.js +17 -18
  47. package/dist/services/fields.d.ts +1 -0
  48. package/dist/services/fields.js +24 -53
  49. package/dist/services/files/lib/extract-metadata.d.ts +3 -0
  50. package/dist/services/files/lib/extract-metadata.js +32 -0
  51. package/dist/services/files/utils/get-metadata.d.ts +5 -0
  52. package/dist/services/files/utils/get-metadata.js +107 -0
  53. package/dist/services/files.d.ts +4 -6
  54. package/dist/services/files.js +24 -140
  55. package/dist/services/graphql/index.d.ts +3 -3
  56. package/dist/services/graphql/index.js +22 -126
  57. package/dist/services/graphql/subscription.js +4 -2
  58. package/dist/services/import-export.js +4 -18
  59. package/dist/services/index.d.ts +2 -3
  60. package/dist/services/index.js +2 -3
  61. package/dist/services/items.js +44 -115
  62. package/dist/services/meta.js +23 -60
  63. package/dist/services/payload.d.ts +10 -9
  64. package/dist/services/payload.js +3 -18
  65. package/dist/services/{permissions.d.ts → permissions/index.d.ts} +7 -5
  66. package/dist/services/{permissions.js → permissions/index.js} +54 -30
  67. package/dist/{permissions → services/permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  68. package/dist/services/permissions/lib/with-app-minimal-permissions.js +13 -0
  69. package/dist/services/relations.d.ts +6 -0
  70. package/dist/services/relations.js +29 -26
  71. package/dist/services/roles.d.ts +12 -4
  72. package/dist/services/roles.js +424 -57
  73. package/dist/services/server.js +6 -0
  74. package/dist/services/shares.d.ts +2 -0
  75. package/dist/services/shares.js +8 -12
  76. package/dist/services/specifications.d.ts +2 -2
  77. package/dist/services/specifications.js +27 -39
  78. package/dist/services/tus/data-store.d.ts +36 -0
  79. package/dist/services/tus/data-store.js +214 -0
  80. package/dist/services/tus/index.d.ts +2 -0
  81. package/dist/services/tus/index.js +2 -0
  82. package/dist/services/tus/lockers.d.ts +36 -0
  83. package/dist/services/tus/lockers.js +83 -0
  84. package/dist/services/tus/server.d.ts +8 -0
  85. package/dist/services/tus/server.js +80 -0
  86. package/dist/services/tus/utils/wait-timeout.d.ts +1 -0
  87. package/dist/services/tus/utils/wait-timeout.js +13 -0
  88. package/dist/services/users.d.ts +5 -1
  89. package/dist/services/users.js +161 -78
  90. package/dist/services/utils.js +7 -11
  91. package/dist/services/versions.d.ts +2 -0
  92. package/dist/services/versions.js +10 -34
  93. package/dist/storage/register-locations.js +5 -1
  94. package/dist/telemetry/lib/get-report.js +2 -2
  95. package/dist/telemetry/utils/check-increased-user-limits.d.ts +7 -0
  96. package/dist/telemetry/utils/check-increased-user-limits.js +25 -0
  97. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +6 -0
  98. package/dist/telemetry/utils/get-role-counts-by-roles.js +27 -0
  99. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +11 -0
  100. package/dist/telemetry/utils/get-role-counts-by-users.js +34 -0
  101. package/dist/telemetry/utils/get-user-count.d.ts +8 -0
  102. package/dist/telemetry/utils/get-user-count.js +33 -0
  103. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +7 -0
  104. package/dist/telemetry/utils/get-user-counts-by-roles.js +35 -0
  105. package/dist/types/ast.d.ts +1 -43
  106. package/dist/types/items.d.ts +0 -11
  107. package/dist/utils/apply-query.d.ts +3 -4
  108. package/dist/utils/apply-query.js +8 -37
  109. package/dist/utils/get-accountability-for-role.js +25 -16
  110. package/dist/utils/get-accountability-for-token.js +16 -17
  111. package/dist/utils/get-ast-from-query.d.ts +13 -0
  112. package/dist/utils/get-ast-from-query.js +297 -0
  113. package/dist/utils/get-cache-key.d.ts +1 -1
  114. package/dist/utils/get-cache-key.js +1 -12
  115. package/dist/utils/get-column.d.ts +1 -2
  116. package/dist/utils/get-column.js +0 -1
  117. package/dist/utils/get-permissions.d.ts +2 -0
  118. package/dist/utils/get-permissions.js +150 -0
  119. package/dist/utils/get-service.js +1 -5
  120. package/dist/utils/merge-permissions-for-share.d.ts +4 -0
  121. package/dist/utils/merge-permissions-for-share.js +109 -0
  122. package/dist/utils/merge-permissions.d.ts +3 -0
  123. package/dist/utils/merge-permissions.js +95 -0
  124. package/dist/utils/reduce-schema.d.ts +6 -4
  125. package/dist/utils/reduce-schema.js +34 -14
  126. package/dist/utils/verify-session-jwt.js +2 -1
  127. package/dist/websocket/authenticate.d.ts +2 -0
  128. package/dist/websocket/authenticate.js +12 -0
  129. package/dist/websocket/controllers/graphql.js +4 -1
  130. package/dist/websocket/controllers/hooks.js +0 -4
  131. package/dist/websocket/controllers/rest.js +2 -0
  132. package/dist/websocket/handlers/subscribe.js +2 -0
  133. package/dist/websocket/utils/items.d.ts +1 -1
  134. package/package.json +35 -33
  135. package/dist/controllers/access.js +0 -148
  136. package/dist/controllers/policies.d.ts +0 -2
  137. package/dist/controllers/policies.js +0 -169
  138. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +0 -16
  139. package/dist/database/get-ast-from-query/get-ast-from-query.js +0 -82
  140. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +0 -13
  141. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +0 -69
  142. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +0 -15
  143. package/dist/database/get-ast-from-query/lib/parse-fields.js +0 -190
  144. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +0 -14
  145. package/dist/database/get-ast-from-query/utils/get-deep-query.js +0 -17
  146. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +0 -2
  147. package/dist/database/get-ast-from-query/utils/get-related-collection.js +0 -13
  148. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +0 -2
  149. package/dist/database/get-ast-from-query/utils/get-relation.js +0 -7
  150. package/dist/database/migrations/20240619A-permissions-policies.js +0 -163
  151. package/dist/database/run-ast/lib/get-db-query.d.ts +0 -4
  152. package/dist/database/run-ast/lib/get-db-query.js +0 -194
  153. package/dist/database/run-ast/lib/parse-current-level.d.ts +0 -7
  154. package/dist/database/run-ast/lib/parse-current-level.js +0 -41
  155. package/dist/database/run-ast/run-ast.d.ts +0 -7
  156. package/dist/database/run-ast/run-ast.js +0 -107
  157. package/dist/database/run-ast/types.js +0 -1
  158. package/dist/database/run-ast/utils/apply-case-when.d.ts +0 -16
  159. package/dist/database/run-ast/utils/apply-case-when.js +0 -26
  160. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +0 -3
  161. package/dist/database/run-ast/utils/apply-parent-filters.js +0 -55
  162. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +0 -10
  163. package/dist/database/run-ast/utils/get-column-pre-processor.js +0 -57
  164. package/dist/database/run-ast/utils/get-field-alias.d.ts +0 -2
  165. package/dist/database/run-ast/utils/get-field-alias.js +0 -4
  166. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +0 -5
  167. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +0 -23
  168. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +0 -3
  169. package/dist/database/run-ast/utils/merge-with-parent-items.js +0 -87
  170. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +0 -3
  171. package/dist/database/run-ast/utils/remove-temporary-fields.js +0 -73
  172. package/dist/permissions/cache.d.ts +0 -2
  173. package/dist/permissions/cache.js +0 -23
  174. package/dist/permissions/lib/fetch-permissions.d.ts +0 -10
  175. package/dist/permissions/lib/fetch-permissions.js +0 -55
  176. package/dist/permissions/lib/fetch-policies.d.ts +0 -7
  177. package/dist/permissions/lib/fetch-policies.js +0 -28
  178. package/dist/permissions/lib/fetch-roles-tree.d.ts +0 -3
  179. package/dist/permissions/lib/fetch-roles-tree.js +0 -28
  180. package/dist/permissions/lib/with-app-minimal-permissions.js +0 -10
  181. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +0 -7
  182. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +0 -56
  183. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +0 -3
  184. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +0 -16
  185. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +0 -8
  186. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +0 -24
  187. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +0 -9
  188. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +0 -31
  189. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +0 -16
  190. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +0 -27
  191. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +0 -10
  192. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +0 -23
  193. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +0 -5
  194. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +0 -7
  195. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +0 -5
  196. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +0 -10
  197. package/dist/permissions/modules/fetch-global-access/types.d.ts +0 -4
  198. package/dist/permissions/modules/fetch-global-access/types.js +0 -1
  199. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +0 -4
  200. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +0 -27
  201. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +0 -12
  202. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +0 -32
  203. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +0 -4
  204. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +0 -29
  205. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +0 -4
  206. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +0 -49
  207. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +0 -3
  208. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +0 -56
  209. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +0 -4
  210. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +0 -8
  211. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +0 -9
  212. package/dist/permissions/modules/process-ast/lib/inject-cases.js +0 -93
  213. package/dist/permissions/modules/process-ast/process-ast.d.ts +0 -9
  214. package/dist/permissions/modules/process-ast/process-ast.js +0 -39
  215. package/dist/permissions/modules/process-ast/types.d.ts +0 -24
  216. package/dist/permissions/modules/process-ast/types.js +0 -1
  217. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +0 -2
  218. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +0 -7
  219. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +0 -12
  220. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +0 -30
  221. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +0 -15
  222. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +0 -50
  223. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +0 -3
  224. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +0 -9
  225. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +0 -3
  226. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +0 -24
  227. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +0 -1
  228. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +0 -3
  229. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +0 -5
  230. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +0 -7
  231. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +0 -2
  232. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +0 -3
  233. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +0 -2
  234. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +0 -3
  235. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +0 -3
  236. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +0 -16
  237. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +0 -2
  238. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +0 -12
  239. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +0 -2
  240. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +0 -28
  241. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +0 -5
  242. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +0 -12
  243. package/dist/permissions/modules/process-payload/process-payload.d.ts +0 -13
  244. package/dist/permissions/modules/process-payload/process-payload.js +0 -77
  245. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +0 -12
  246. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +0 -11
  247. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +0 -9
  248. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +0 -33
  249. package/dist/permissions/modules/validate-access/validate-access.d.ts +0 -14
  250. package/dist/permissions/modules/validate-access/validate-access.js +0 -28
  251. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +0 -1
  252. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +0 -8
  253. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +0 -5
  254. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +0 -10
  255. package/dist/permissions/types.d.ts +0 -6
  256. package/dist/permissions/types.js +0 -1
  257. package/dist/permissions/utils/create-default-accountability.d.ts +0 -2
  258. package/dist/permissions/utils/create-default-accountability.js +0 -11
  259. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +0 -8
  260. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +0 -27
  261. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +0 -9
  262. package/dist/permissions/utils/fetch-dynamic-variable-context.js +0 -43
  263. package/dist/permissions/utils/filter-policies-by-ip.d.ts +0 -2
  264. package/dist/permissions/utils/filter-policies-by-ip.js +0 -15
  265. package/dist/permissions/utils/get-unaliased-field-key.d.ts +0 -5
  266. package/dist/permissions/utils/get-unaliased-field-key.js +0 -17
  267. package/dist/permissions/utils/process-permissions.d.ts +0 -7
  268. package/dist/permissions/utils/process-permissions.js +0 -9
  269. package/dist/permissions/utils/with-cache.d.ts +0 -10
  270. package/dist/permissions/utils/with-cache.js +0 -25
  271. package/dist/services/access.d.ts +0 -10
  272. package/dist/services/access.js +0 -43
  273. package/dist/services/policies.d.ts +0 -12
  274. package/dist/services/policies.js +0 -87
  275. package/dist/telemetry/utils/check-user-limits.d.ts +0 -5
  276. package/dist/telemetry/utils/check-user-limits.js +0 -19
  277. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +0 -17
  278. package/dist/utils/fetch-user-count/fetch-access-lookup.js +0 -22
  279. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +0 -16
  280. package/dist/utils/fetch-user-count/fetch-access-roles.js +0 -37
  281. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +0 -6
  282. package/dist/utils/fetch-user-count/fetch-active-users.js +0 -3
  283. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +0 -12
  284. package/dist/utils/fetch-user-count/fetch-user-count.js +0 -57
  285. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +0 -20
  286. package/dist/utils/fetch-user-count/get-user-count-query.js +0 -17
  287. package/dist/utils/validate-user-count-integrity.d.ts +0 -13
  288. package/dist/utils/validate-user-count-integrity.js +0 -29
  289. /package/dist/database/migrations/{20240619A-permissions-policies.d.ts → 20240701A-add-tus-data.d.ts} +0 -0
  290. /package/dist/{utils → services/files/utils}/parse-image-metadata.d.ts +0 -0
  291. /package/dist/{utils → services/files/utils}/parse-image-metadata.js +0 -0
@@ -1,22 +1,23 @@
1
1
  import { useEnv } from '@directus/env';
2
- import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError } from '@directus/errors';
3
- import { getSimpleHash, toArray, validatePayload } from '@directus/utils';
2
+ import { ForbiddenError, InvalidPayloadError, RecordNotUniqueError, UnprocessableContentError } from '@directus/errors';
3
+ import { getSimpleHash, toArray, toBoolean, validatePayload } from '@directus/utils';
4
4
  import { FailedValidationError, joiValidationErrorItemToErrorExtensions } from '@directus/validation';
5
5
  import Joi from 'joi';
6
6
  import jwt from 'jsonwebtoken';
7
- import { isEmpty } from 'lodash-es';
7
+ import { isEmpty, mergeWith } from 'lodash-es';
8
8
  import { performance } from 'perf_hooks';
9
- import { clearSystemCache } from '../cache.js';
10
9
  import getDatabase from '../database/index.js';
11
10
  import { useLogger } from '../logger.js';
12
- import { validateRemainingAdminUsers } from '../permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js';
13
- import { createDefaultAccountability } from '../permissions/utils/create-default-accountability.js';
11
+ import { checkIncreasedUserLimits } from '../telemetry/utils/check-increased-user-limits.js';
12
+ import { getRoleCountsByRoles } from '../telemetry/utils/get-role-counts-by-roles.js';
13
+ import { getRoleCountsByUsers } from '../telemetry/utils/get-role-counts-by-users.js';
14
+ import {} from '../telemetry/utils/get-user-count.js';
15
+ import { shouldCheckUserLimits } from '../telemetry/utils/should-check-user-limits.js';
14
16
  import { getSecret } from '../utils/get-secret.js';
15
17
  import isUrlAllowed from '../utils/is-url-allowed.js';
16
18
  import { verifyJWT } from '../utils/jwt.js';
17
19
  import { stall } from '../utils/stall.js';
18
20
  import { Url } from '../utils/url.js';
19
- import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
20
21
  import { ItemsService } from './items.js';
21
22
  import { MailService } from './mail/index.js';
22
23
  import { SettingsService } from './settings.js';
@@ -87,11 +88,42 @@ export class UsersService extends ItemsService {
87
88
  }
88
89
  }
89
90
  }
91
+ async checkRemainingAdminExistence(excludeKeys) {
92
+ // Make sure there's at least one admin user left after this deletion is done
93
+ const otherAdminUsers = await this.knex
94
+ .count('*', { as: 'count' })
95
+ .from('directus_users')
96
+ .whereNotIn('directus_users.id', excludeKeys)
97
+ .andWhere({ 'directus_roles.admin_access': true })
98
+ .leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
99
+ .first();
100
+ const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
101
+ if (otherAdminUsersCount === 0) {
102
+ throw new UnprocessableContentError({ reason: `You can't remove the last admin user from the role` });
103
+ }
104
+ }
105
+ /**
106
+ * Make sure there's at least one active admin user when updating user status
107
+ */
108
+ async checkRemainingActiveAdmin(excludeKeys) {
109
+ const otherAdminUsers = await this.knex
110
+ .count('*', { as: 'count' })
111
+ .from('directus_users')
112
+ .whereNotIn('directus_users.id', excludeKeys)
113
+ .andWhere({ 'directus_roles.admin_access': true })
114
+ .andWhere({ 'directus_users.status': 'active' })
115
+ .leftJoin('directus_roles', 'directus_users.role', 'directus_roles.id')
116
+ .first();
117
+ const otherAdminUsersCount = +(otherAdminUsers?.count || 0);
118
+ if (otherAdminUsersCount === 0) {
119
+ throw new UnprocessableContentError({ reason: `You can't change the active status of the last admin user` });
120
+ }
121
+ }
90
122
  /**
91
123
  * Get basic information of user identified by email
92
124
  */
93
125
  async getUserByEmail(email) {
94
- return this.knex
126
+ return await this.knex
95
127
  .select('id', 'role', 'status', 'password', 'email')
96
128
  .from('directus_users')
97
129
  .whereRaw(`LOWER(??) = ?`, ['email', email.toLowerCase()])
@@ -126,34 +158,51 @@ export class UsersService extends ItemsService {
126
158
  /**
127
159
  * Create a new user
128
160
  */
129
- async createOne(data, opts = {}) {
161
+ async createOne(data, opts) {
130
162
  try {
131
- if ('email' in data) {
163
+ if (data['email']) {
132
164
  this.validateEmail(data['email']);
133
165
  await this.checkUniqueEmails([data['email']]);
134
166
  }
135
- if ('password' in data) {
167
+ if (data['password']) {
136
168
  await this.checkPasswordPolicy([data['password']]);
137
169
  }
170
+ if (shouldCheckUserLimits() && data['role']) {
171
+ const increasedCounts = {
172
+ admin: 0,
173
+ app: 0,
174
+ api: 0,
175
+ };
176
+ if (typeof data['role'] === 'object') {
177
+ if ('admin_access' in data['role'] && data['role']['admin_access'] === true) {
178
+ increasedCounts.admin++;
179
+ }
180
+ else if ('app_access' in data['role'] && data['role']['app_access'] === true) {
181
+ increasedCounts.app++;
182
+ }
183
+ else {
184
+ increasedCounts.api++;
185
+ }
186
+ }
187
+ else {
188
+ const existingRoleCounts = await getRoleCountsByRoles(this.knex, [data['role']]);
189
+ mergeWith(increasedCounts, existingRoleCounts, (x, y) => x + y);
190
+ }
191
+ await checkIncreasedUserLimits(this.knex, increasedCounts);
192
+ }
138
193
  }
139
194
  catch (err) {
140
- opts.preMutationError = err;
141
- }
142
- if (!('status' in data) || data['status'] === 'active') {
143
- // Creating a user only requires checking user limits if the user is active, no need to care about the role
144
- opts.userIntegrityCheckFlags =
145
- (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
146
- opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
195
+ (opts || (opts = {})).preMutationError = err;
147
196
  }
148
197
  return await super.createOne(data, opts);
149
198
  }
150
199
  /**
151
200
  * Create multiple new users
152
201
  */
153
- async createMany(data, opts = {}) {
154
- const emails = data.map((payload) => payload['email']).filter((email) => email);
155
- const passwords = data.map((payload) => payload['password']).filter((password) => password);
156
- const someActive = data.some((payload) => !('status' in payload) || payload['status'] === 'active');
202
+ async createMany(data, opts) {
203
+ const emails = data['map']((payload) => payload['email']).filter((email) => email);
204
+ const passwords = data['map']((payload) => payload['password']).filter((password) => password);
205
+ const roles = data['map']((payload) => payload['role']).filter((role) => role);
157
206
  try {
158
207
  if (emails.length) {
159
208
  this.validateEmail(emails);
@@ -162,30 +211,96 @@ export class UsersService extends ItemsService {
162
211
  if (passwords.length) {
163
212
  await this.checkPasswordPolicy(passwords);
164
213
  }
214
+ if (shouldCheckUserLimits() && roles.length) {
215
+ const increasedCounts = {
216
+ admin: 0,
217
+ app: 0,
218
+ api: 0,
219
+ };
220
+ const existingRoles = [];
221
+ for (const role of roles) {
222
+ if (typeof role === 'object') {
223
+ if ('admin_access' in role && role['admin_access'] === true) {
224
+ increasedCounts.admin++;
225
+ }
226
+ else if ('app_access' in role && role['app_access'] === true) {
227
+ increasedCounts.app++;
228
+ }
229
+ else {
230
+ increasedCounts.api++;
231
+ }
232
+ }
233
+ else {
234
+ existingRoles.push(role);
235
+ }
236
+ }
237
+ const existingRoleCounts = await getRoleCountsByRoles(this.knex, existingRoles);
238
+ mergeWith(increasedCounts, existingRoleCounts, (x, y) => x + y);
239
+ await checkIncreasedUserLimits(this.knex, increasedCounts);
240
+ }
165
241
  }
166
242
  catch (err) {
167
- opts.preMutationError = err;
168
- }
169
- if (someActive) {
170
- // Creating users only requires checking user limits if the users are active, no need to care about the role
171
- opts.userIntegrityCheckFlags =
172
- (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
173
- opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
243
+ (opts || (opts = {})).preMutationError = err;
174
244
  }
175
- // Use generic ItemsService to avoid calling `UserService.createOne` to avoid additional work of validating emails,
176
- // as this requires one query per email if done in `createOne`
177
- const itemsService = new ItemsService(this.collection, {
178
- schema: this.schema,
179
- accountability: this.accountability,
180
- knex: this.knex,
181
- });
182
- return await itemsService.createMany(data, opts);
245
+ return await super.createMany(data, opts);
183
246
  }
184
247
  /**
185
248
  * Update many users by primary key
186
249
  */
187
- async updateMany(keys, data, opts = {}) {
250
+ async updateMany(keys, data, opts) {
188
251
  try {
252
+ const needsUserLimitCheck = shouldCheckUserLimits();
253
+ if (data['role']) {
254
+ /*
255
+ * data['role'] has the following cases:
256
+ * - a string with existing role id
257
+ * - an object with existing role id for GraphQL mutations
258
+ * - an object with data for new role
259
+ */
260
+ const role = data['role']?.id ?? data['role'];
261
+ let newRole;
262
+ if (typeof role === 'string') {
263
+ newRole = await this.knex
264
+ .select('admin_access', 'app_access')
265
+ .from('directus_roles')
266
+ .where('id', role)
267
+ .first();
268
+ }
269
+ else {
270
+ newRole = role;
271
+ }
272
+ if (!newRole?.admin_access) {
273
+ await this.checkRemainingAdminExistence(keys);
274
+ }
275
+ if (needsUserLimitCheck && newRole) {
276
+ const existingCounts = await getRoleCountsByUsers(this.knex, keys);
277
+ const increasedCounts = {
278
+ admin: 0,
279
+ app: 0,
280
+ api: 0,
281
+ };
282
+ if (toBoolean(newRole.admin_access)) {
283
+ increasedCounts.admin = keys.length - existingCounts.admin;
284
+ }
285
+ else if (toBoolean(newRole.app_access)) {
286
+ increasedCounts.app = keys.length - existingCounts.app;
287
+ }
288
+ else {
289
+ increasedCounts.api = keys.length - existingCounts.api;
290
+ }
291
+ await checkIncreasedUserLimits(this.knex, increasedCounts);
292
+ }
293
+ }
294
+ if (needsUserLimitCheck && data['role'] === null) {
295
+ await checkIncreasedUserLimits(this.knex, { admin: 0, app: 0, api: 1 });
296
+ }
297
+ if (data['status'] !== undefined && data['status'] !== 'active') {
298
+ await this.checkRemainingActiveAdmin(keys);
299
+ }
300
+ if (needsUserLimitCheck && data['status'] === 'active') {
301
+ const increasedCounts = await getRoleCountsByUsers(this.knex, keys, { inactiveUsers: true });
302
+ await checkIncreasedUserLimits(this.knex, increasedCounts);
303
+ }
189
304
  if (data['email']) {
190
305
  if (keys.length > 1) {
191
306
  throw new RecordNotUniqueError({
@@ -216,45 +331,19 @@ export class UsersService extends ItemsService {
216
331
  }
217
332
  }
218
333
  catch (err) {
219
- opts.preMutationError = err;
220
- }
221
- if ('role' in data) {
222
- opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
334
+ (opts || (opts = {})).preMutationError = err;
223
335
  }
224
- if ('status' in data) {
225
- if (data['status'] === 'active') {
226
- // User are being activated, no need to check if there are enough admins
227
- opts.userIntegrityCheckFlags =
228
- (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) | UserIntegrityCheckFlag.UserLimits;
229
- }
230
- else {
231
- opts.userIntegrityCheckFlags = UserIntegrityCheckFlag.All;
232
- }
233
- }
234
- if (opts.userIntegrityCheckFlags) {
235
- opts.onRequireUserIntegrityCheck?.(opts.userIntegrityCheckFlags);
236
- }
237
- const result = await super.updateMany(keys, data, opts);
238
- // Only clear the caches if the role has been updated
239
- if ('role' in data) {
240
- await this.clearCaches(opts);
241
- }
242
- return result;
336
+ return await super.updateMany(keys, data, opts);
243
337
  }
244
338
  /**
245
339
  * Delete multiple users by primary key
246
340
  */
247
- async deleteMany(keys, opts = {}) {
248
- if (opts?.onRequireUserIntegrityCheck) {
249
- opts.onRequireUserIntegrityCheck(opts?.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None);
341
+ async deleteMany(keys, opts) {
342
+ try {
343
+ await this.checkRemainingAdminExistence(keys);
250
344
  }
251
- else {
252
- try {
253
- await validateRemainingAdminUsers({ excludeUsers: keys }, { knex: this.knex, schema: this.schema });
254
- }
255
- catch (err) {
256
- opts.preMutationError = err;
257
- }
345
+ catch (err) {
346
+ (opts || (opts = {})).preMutationError = err;
258
347
  }
259
348
  // Manual constraint, see https://github.com/directus/directus/pull/19912
260
349
  await this.knex('directus_notifications').update({ sender: null }).whereIn('sender', keys);
@@ -477,16 +566,10 @@ export class UsersService extends ItemsService {
477
566
  knex: this.knex,
478
567
  schema: this.schema,
479
568
  accountability: {
480
- ...(this.accountability ?? createDefaultAccountability()),
569
+ ...(this.accountability ?? { role: null }),
481
570
  admin: true, // We need to skip permissions checks for the update call below
482
571
  },
483
572
  });
484
573
  await service.updateOne(user.id, { password, status: 'active' }, opts);
485
574
  }
486
- async clearCaches(opts) {
487
- await clearSystemCache({ autoPurgeCache: opts?.autoPurgeCache });
488
- if (this.cache && opts?.autoPurgeCache !== false) {
489
- await this.cache.clear();
490
- }
491
- }
492
575
  }
@@ -3,8 +3,6 @@ import { systemCollectionRows } from '@directus/system-data';
3
3
  import { clearSystemCache, getCache } from '../cache.js';
4
4
  import getDatabase from '../database/index.js';
5
5
  import emitter from '../emitter.js';
6
- import { fetchAllowedFields } from '../permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js';
7
- import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
8
6
  import { shouldClearCache } from '../utils/should-clear-cache.js';
9
7
  export class UtilsService {
10
8
  knex;
@@ -22,16 +20,14 @@ export class UtilsService {
22
20
  if (!sortField) {
23
21
  throw new InvalidPayloadError({ reason: `Collection "${collection}" doesn't have a sort field` });
24
22
  }
25
- if (this.accountability && this.accountability.admin !== true) {
26
- await validateAccess({
27
- accountability: this.accountability,
28
- action: 'update',
29
- collection,
30
- }, {
31
- schema: this.schema,
32
- knex: this.knex,
23
+ if (this.accountability?.admin !== true) {
24
+ const permissions = this.accountability?.permissions?.find((permission) => {
25
+ return permission.collection === collection && permission.action === 'update';
33
26
  });
34
- const allowedFields = await fetchAllowedFields({ collection, action: 'update', accountability: this.accountability }, { schema: this.schema, knex: this.knex });
27
+ if (!permissions) {
28
+ throw new ForbiddenError();
29
+ }
30
+ const allowedFields = permissions.fields ?? [];
35
31
  if (allowedFields[0] !== '*' && allowedFields.includes(sortField) === false) {
36
32
  throw new ForbiddenError();
37
33
  }
@@ -1,7 +1,9 @@
1
1
  import type { Item, PrimaryKey, Query } from '@directus/types';
2
2
  import type { AbstractServiceOptions, MutationOptions } from '../types/index.js';
3
+ import { AuthorizationService } from './authorization.js';
3
4
  import { ItemsService } from './items.js';
4
5
  export declare class VersionsService extends ItemsService {
6
+ authorizationService: AuthorizationService;
5
7
  constructor(options: AbstractServiceOptions);
6
8
  private validateCreateData;
7
9
  getMainItem(collection: string, item: PrimaryKey, query?: Query): Promise<Item>;
@@ -6,15 +6,21 @@ import objectHash from 'object-hash';
6
6
  import { getCache } from '../cache.js';
7
7
  import getDatabase from '../database/index.js';
8
8
  import emitter from '../emitter.js';
9
- import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
10
9
  import { shouldClearCache } from '../utils/should-clear-cache.js';
11
10
  import { ActivityService } from './activity.js';
11
+ import { AuthorizationService } from './authorization.js';
12
12
  import { ItemsService } from './items.js';
13
13
  import { PayloadService } from './payload.js';
14
14
  import { RevisionsService } from './revisions.js';
15
15
  export class VersionsService extends ItemsService {
16
+ authorizationService;
16
17
  constructor(options) {
17
18
  super('directus_versions', options);
19
+ this.authorizationService = new AuthorizationService({
20
+ accountability: this.accountability,
21
+ knex: this.knex,
22
+ schema: this.schema,
23
+ });
18
24
  }
19
25
  async validateCreateData(data) {
20
26
  if (!data['key'])
@@ -49,31 +55,11 @@ export class VersionsService extends ItemsService {
49
55
  });
50
56
  }
51
57
  // will throw an error if the accountability does not have permission to read the item
52
- if (this.accountability) {
53
- await validateAccess({
54
- accountability: this.accountability,
55
- action: 'read',
56
- collection: data['collection'],
57
- primaryKeys: [data['item']],
58
- }, {
59
- schema: this.schema,
60
- knex: this.knex,
61
- });
62
- }
58
+ await this.authorizationService.checkAccess('read', data['collection'], data['item']);
63
59
  }
64
60
  async getMainItem(collection, item, query) {
65
61
  // will throw an error if the accountability does not have permission to read the item
66
- if (this.accountability) {
67
- await validateAccess({
68
- accountability: this.accountability,
69
- action: 'read',
70
- collection,
71
- primaryKeys: [item],
72
- }, {
73
- schema: this.schema,
74
- knex: this.knex,
75
- });
76
- }
62
+ await this.authorizationService.checkAccess('read', collection, item);
77
63
  const itemsService = new ItemsService(collection, {
78
64
  knex: this.knex,
79
65
  accountability: this.accountability,
@@ -214,17 +200,7 @@ export class VersionsService extends ItemsService {
214
200
  async promote(version, mainHash, fields) {
215
201
  const { id, collection, item } = (await this.readOne(version));
216
202
  // will throw an error if the accountability does not have permission to update the item
217
- if (this.accountability) {
218
- await validateAccess({
219
- accountability: this.accountability,
220
- action: 'update',
221
- collection,
222
- primaryKeys: [item],
223
- }, {
224
- schema: this.schema,
225
- knex: this.knex,
226
- });
227
- }
203
+ await this.authorizationService.checkAccess('update', collection, item);
228
204
  const { outdated } = await this.verifyHash(collection, item, mainHash);
229
205
  if (outdated) {
230
206
  throw new UnprocessableContentError({
@@ -1,13 +1,17 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { toArray } from '@directus/utils';
3
+ import { RESUMABLE_UPLOADS } from '../constants.js';
3
4
  import { getConfigFromEnv } from '../utils/get-config-from-env.js';
4
5
  export const registerLocations = async (storage) => {
5
6
  const env = useEnv();
6
7
  const locations = toArray(env['STORAGE_LOCATIONS']);
8
+ const tus = {
9
+ chunkSize: RESUMABLE_UPLOADS.CHUNK_SIZE,
10
+ };
7
11
  locations.forEach((location) => {
8
12
  location = location.trim();
9
13
  const driverConfig = getConfigFromEnv(`STORAGE_${location.toUpperCase()}_`);
10
14
  const { driver, ...options } = driverConfig;
11
- storage.registerLocation(location, { driver, options });
15
+ storage.registerLocation(location, { driver, options: { ...options, tus } });
12
16
  });
13
17
  };
@@ -2,11 +2,11 @@ import { useEnv } from '@directus/env';
2
2
  import { version } from 'directus/version';
3
3
  import { getHelpers } from '../../database/helpers/index.js';
4
4
  import { getDatabase, getDatabaseClient } from '../../database/index.js';
5
- import { fetchUserCount } from '../../utils/fetch-user-count/fetch-user-count.js';
6
5
  import { getExtensionCount } from '../utils/get-extension-count.js';
7
6
  import { getFieldCount } from '../utils/get-field-count.js';
8
7
  import { getFilesizeSum } from '../utils/get-filesize-sum.js';
9
8
  import { getItemCount } from '../utils/get-item-count.js';
9
+ import { getUserCount } from '../utils/get-user-count.js';
10
10
  import { getUserItemCount } from '../utils/get-user-item-count.js';
11
11
  const basicCountTasks = [
12
12
  { collection: 'directus_dashboards' },
@@ -27,7 +27,7 @@ export const getReport = async () => {
27
27
  const helpers = getHelpers(db);
28
28
  const [basicCounts, userCounts, userItemCount, fieldsCounts, extensionsCounts, databaseSize, filesizes] = await Promise.all([
29
29
  getItemCount(db, basicCountTasks),
30
- fetchUserCount({ knex: db }),
30
+ getUserCount(db),
31
31
  getUserItemCount(db),
32
32
  getFieldCount(db),
33
33
  getExtensionCount(db),
@@ -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
+ * Ensure that user limits are not reached
6
+ */
7
+ export declare function checkIncreasedUserLimits(db: Knex, increasedUserCounts: AccessTypeCount, ignoreIds?: PrimaryKey[]): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { useEnv } from '@directus/env';
2
+ import { LimitExceededError } from '@directus/errors';
3
+ import { getUserCount } from './get-user-count.js';
4
+ const env = useEnv();
5
+ /**
6
+ * Ensure that user limits are not reached
7
+ */
8
+ export async function checkIncreasedUserLimits(db, increasedUserCounts, ignoreIds = []) {
9
+ if (!increasedUserCounts.admin && !increasedUserCounts.app && !increasedUserCounts.api)
10
+ return;
11
+ const userCounts = await getUserCount(db, ignoreIds);
12
+ // Admins have full permissions, therefore should count under app access limit
13
+ const existingAppUsersCount = userCounts.admin + userCounts.app;
14
+ const newAppUsersCount = increasedUserCounts.admin + increasedUserCounts.app;
15
+ if (increasedUserCounts.admin > 0 &&
16
+ increasedUserCounts.admin + userCounts.admin > Number(env['USERS_ADMIN_ACCESS_LIMIT'])) {
17
+ throw new LimitExceededError({ category: 'Active Admin users' });
18
+ }
19
+ if (newAppUsersCount > 0 && newAppUsersCount + existingAppUsersCount > Number(env['USERS_APP_ACCESS_LIMIT'])) {
20
+ throw new LimitExceededError({ category: 'Active App users' });
21
+ }
22
+ if (increasedUserCounts.api > 0 && increasedUserCounts.api + userCounts.api > Number(env['USERS_API_ACCESS_LIMIT'])) {
23
+ throw new LimitExceededError({ category: 'Active API users' });
24
+ }
25
+ }
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ import type { PrimaryKey } from '@directus/types';
2
+ import { type Knex } from 'knex';
3
+ export interface AccessTypeCount {
4
+ admin: number;
5
+ app: number;
6
+ api: number;
7
+ }
8
+ export declare const getUserCount: (db: Knex, ignoreIds?: PrimaryKey[]) => Promise<AccessTypeCount>;
@@ -0,0 +1,33 @@
1
+ import { toBoolean } from '@directus/utils';
2
+ import {} from 'knex';
3
+ export const getUserCount = async (db, ignoreIds = []) => {
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
+ .whereNotIn('directus_users.id', ignoreIds)
14
+ .andWhere('directus_users.status', 'active')
15
+ .leftJoin('directus_roles', 'directus_users.role', '=', 'directus_roles.id')
16
+ .where('directus_users.status', '=', 'active')
17
+ .groupBy('directus_roles.admin_access', 'directus_roles.app_access'));
18
+ for (const record of result) {
19
+ const adminAccess = toBoolean(record.admin_access);
20
+ const appAccess = toBoolean(record.app_access);
21
+ const count = Number(record.count);
22
+ if (adminAccess) {
23
+ counts.admin += count;
24
+ }
25
+ else if (appAccess) {
26
+ counts.app += count;
27
+ }
28
+ else {
29
+ counts.api += count;
30
+ }
31
+ }
32
+ return counts;
33
+ };
@@ -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>;