@directus/api 19.3.1 → 20.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.
Files changed (285) hide show
  1. package/dist/app.js +4 -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 +2 -4
  6. package/dist/cache.js +3 -0
  7. package/dist/cli/commands/bootstrap/index.js +8 -2
  8. package/dist/cli/commands/init/index.js +9 -10
  9. package/dist/cli/utils/defaults.d.ts +4 -11
  10. package/dist/cli/utils/defaults.js +7 -1
  11. package/dist/constants.d.ts +1 -1
  12. package/dist/controllers/access.d.ts +2 -0
  13. package/dist/controllers/access.js +148 -0
  14. package/dist/controllers/auth.js +5 -16
  15. package/dist/controllers/permissions.js +14 -2
  16. package/dist/controllers/policies.d.ts +2 -0
  17. package/dist/controllers/policies.js +169 -0
  18. package/dist/controllers/roles.js +22 -1
  19. package/dist/controllers/users.js +0 -55
  20. package/dist/database/errors/dialects/mysql.js +23 -23
  21. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
  22. package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
  23. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
  24. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
  25. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
  26. package/dist/database/get-ast-from-query/lib/parse-fields.js +190 -0
  27. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
  28. package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
  29. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
  30. package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
  31. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
  32. package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
  33. package/dist/database/helpers/fn/types.d.ts +2 -1
  34. package/dist/database/helpers/fn/types.js +1 -1
  35. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  36. package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
  37. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  38. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  39. package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
  40. package/dist/database/helpers/geometry/types.d.ts +1 -1
  41. package/dist/database/helpers/geometry/types.js +4 -2
  42. package/dist/database/index.js +2 -1
  43. package/dist/database/migrations/20240619A-permissions-policies.d.ts +3 -0
  44. package/dist/database/migrations/20240619A-permissions-policies.js +163 -0
  45. package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
  46. package/dist/database/run-ast/lib/get-db-query.js +194 -0
  47. package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
  48. package/dist/database/run-ast/lib/parse-current-level.js +41 -0
  49. package/dist/database/run-ast/run-ast.d.ts +7 -0
  50. package/dist/database/run-ast/run-ast.js +107 -0
  51. package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
  52. package/dist/database/run-ast/types.js +1 -0
  53. package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
  54. package/dist/database/run-ast/utils/apply-case-when.js +26 -0
  55. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
  56. package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
  57. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
  58. package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
  59. package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
  60. package/dist/database/run-ast/utils/get-field-alias.js +4 -0
  61. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
  62. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
  63. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
  64. package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
  65. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
  66. package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
  67. package/dist/extensions/lib/sandbox/generate-api-extensions-sandbox-entrypoint.d.ts +1 -1
  68. package/dist/flows.js +3 -4
  69. package/dist/middleware/authenticate.js +2 -7
  70. package/dist/middleware/cache.js +1 -1
  71. package/dist/middleware/cors.js +4 -4
  72. package/dist/middleware/respond.js +1 -1
  73. package/dist/permissions/cache.d.ts +2 -0
  74. package/dist/permissions/cache.js +23 -0
  75. package/dist/permissions/lib/fetch-permissions.d.ts +10 -0
  76. package/dist/permissions/lib/fetch-permissions.js +55 -0
  77. package/dist/permissions/lib/fetch-policies.d.ts +7 -0
  78. package/dist/permissions/lib/fetch-policies.js +28 -0
  79. package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
  80. package/dist/permissions/lib/fetch-roles-tree.js +28 -0
  81. package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  82. package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
  83. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
  84. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
  85. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
  86. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
  87. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
  88. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
  89. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
  90. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
  91. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
  92. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
  93. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
  94. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
  95. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
  96. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
  97. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
  98. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
  99. package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
  100. package/dist/permissions/modules/fetch-global-access/types.js +1 -0
  101. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
  102. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
  103. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
  104. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
  105. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
  106. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
  107. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
  108. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
  109. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
  110. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
  111. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
  112. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
  113. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
  114. package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
  115. package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
  116. package/dist/permissions/modules/process-ast/process-ast.js +39 -0
  117. package/dist/permissions/modules/process-ast/types.d.ts +24 -0
  118. package/dist/permissions/modules/process-ast/types.js +1 -0
  119. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
  120. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
  121. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
  122. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
  123. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
  124. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +50 -0
  125. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
  126. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
  127. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
  128. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +24 -0
  129. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
  130. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
  131. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
  132. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
  133. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
  134. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
  135. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
  136. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
  137. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
  138. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
  139. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
  140. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
  141. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
  142. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
  143. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
  144. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
  145. package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
  146. package/dist/permissions/modules/process-payload/process-payload.js +77 -0
  147. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
  148. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
  149. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
  150. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
  151. package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
  152. package/dist/permissions/modules/validate-access/validate-access.js +28 -0
  153. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
  154. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
  155. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
  156. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
  157. package/dist/permissions/types.d.ts +6 -0
  158. package/dist/permissions/types.js +1 -0
  159. package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
  160. package/dist/permissions/utils/create-default-accountability.js +11 -0
  161. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
  162. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
  163. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
  164. package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
  165. package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
  166. package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
  167. package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
  168. package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
  169. package/dist/permissions/utils/process-permissions.d.ts +7 -0
  170. package/dist/permissions/utils/process-permissions.js +9 -0
  171. package/dist/permissions/utils/with-cache.d.ts +10 -0
  172. package/dist/permissions/utils/with-cache.js +25 -0
  173. package/dist/services/access.d.ts +10 -0
  174. package/dist/services/access.js +43 -0
  175. package/dist/services/activity.js +22 -10
  176. package/dist/services/assets.d.ts +2 -3
  177. package/dist/services/assets.js +10 -5
  178. package/dist/services/authentication.js +18 -18
  179. package/dist/services/collections.js +18 -17
  180. package/dist/services/fields.d.ts +0 -1
  181. package/dist/services/fields.js +53 -24
  182. package/dist/services/files.d.ts +0 -4
  183. package/dist/services/files.js +10 -10
  184. package/dist/services/flows.d.ts +0 -2
  185. package/dist/services/flows.js +2 -14
  186. package/dist/services/graphql/index.d.ts +3 -3
  187. package/dist/services/graphql/index.js +126 -22
  188. package/dist/services/graphql/subscription.js +2 -4
  189. package/dist/services/import-export.js +23 -9
  190. package/dist/services/index.d.ts +3 -2
  191. package/dist/services/index.js +3 -2
  192. package/dist/services/items.d.ts +40 -14
  193. package/dist/services/items.js +182 -79
  194. package/dist/services/meta.js +60 -23
  195. package/dist/services/notifications.d.ts +0 -1
  196. package/dist/services/notifications.js +0 -7
  197. package/dist/services/operations.d.ts +0 -2
  198. package/dist/services/operations.js +2 -14
  199. package/dist/services/payload.d.ts +9 -10
  200. package/dist/services/payload.js +35 -19
  201. package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
  202. package/dist/services/{permissions/index.js → permissions.js} +30 -54
  203. package/dist/services/policies.d.ts +12 -0
  204. package/dist/services/policies.js +87 -0
  205. package/dist/services/relations.d.ts +0 -6
  206. package/dist/services/relations.js +26 -29
  207. package/dist/services/roles.d.ts +4 -14
  208. package/dist/services/roles.js +56 -430
  209. package/dist/services/shares.d.ts +0 -2
  210. package/dist/services/shares.js +12 -8
  211. package/dist/services/specifications.d.ts +2 -2
  212. package/dist/services/specifications.js +39 -27
  213. package/dist/services/users.d.ts +2 -20
  214. package/dist/services/users.js +87 -192
  215. package/dist/services/utils.js +11 -7
  216. package/dist/services/versions.d.ts +0 -2
  217. package/dist/services/versions.js +34 -10
  218. package/dist/telemetry/lib/get-report.js +6 -3
  219. package/dist/telemetry/types/report.d.ts +4 -0
  220. package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
  221. package/dist/telemetry/utils/check-user-limits.js +19 -0
  222. package/dist/telemetry/utils/get-filesize-sum.d.ts +5 -0
  223. package/dist/telemetry/utils/get-filesize-sum.js +7 -0
  224. package/dist/types/ast.d.ts +43 -1
  225. package/dist/types/items.d.ts +11 -0
  226. package/dist/utils/apply-query.d.ts +4 -3
  227. package/dist/utils/apply-query.js +37 -8
  228. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +17 -0
  229. package/dist/utils/fetch-user-count/fetch-access-lookup.js +22 -0
  230. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
  231. package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
  232. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
  233. package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
  234. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
  235. package/dist/utils/fetch-user-count/fetch-user-count.js +57 -0
  236. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
  237. package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
  238. package/dist/utils/get-accountability-for-role.js +16 -25
  239. package/dist/utils/get-accountability-for-token.js +17 -16
  240. package/dist/utils/get-cache-key.d.ts +1 -1
  241. package/dist/utils/get-cache-key.js +12 -1
  242. package/dist/utils/get-column.d.ts +2 -1
  243. package/dist/utils/get-column.js +1 -0
  244. package/dist/utils/get-graphql-type.js +1 -0
  245. package/dist/utils/get-service.d.ts +1 -1
  246. package/dist/utils/get-service.js +14 -10
  247. package/dist/utils/reduce-schema.d.ts +4 -6
  248. package/dist/utils/reduce-schema.js +14 -34
  249. package/dist/utils/validate-user-count-integrity.d.ts +13 -0
  250. package/dist/utils/validate-user-count-integrity.js +29 -0
  251. package/dist/websocket/authenticate.d.ts +0 -2
  252. package/dist/websocket/authenticate.js +0 -12
  253. package/dist/websocket/controllers/graphql.js +1 -4
  254. package/dist/websocket/controllers/hooks.js +4 -0
  255. package/dist/websocket/controllers/rest.js +0 -2
  256. package/dist/websocket/handlers/subscribe.js +0 -2
  257. package/dist/websocket/utils/items.d.ts +1 -1
  258. package/dist/websocket/utils/items.js +4 -1
  259. package/package.json +31 -30
  260. package/dist/database/run-ast.js +0 -450
  261. package/dist/middleware/check-ip.d.ts +0 -2
  262. package/dist/middleware/check-ip.js +0 -37
  263. package/dist/middleware/get-permissions.d.ts +0 -3
  264. package/dist/middleware/get-permissions.js +0 -10
  265. package/dist/services/authorization.d.ts +0 -17
  266. package/dist/services/authorization.js +0 -456
  267. package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
  268. package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
  269. package/dist/telemetry/utils/check-increased-user-limits.js +0 -22
  270. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
  271. package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
  272. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
  273. package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
  274. package/dist/telemetry/utils/get-user-count.d.ts +0 -8
  275. package/dist/telemetry/utils/get-user-count.js +0 -33
  276. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
  277. package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
  278. package/dist/utils/get-ast-from-query.d.ts +0 -13
  279. package/dist/utils/get-ast-from-query.js +0 -297
  280. package/dist/utils/get-permissions.d.ts +0 -2
  281. package/dist/utils/get-permissions.js +0 -150
  282. package/dist/utils/merge-permissions-for-share.d.ts +0 -4
  283. package/dist/utils/merge-permissions-for-share.js +0 -109
  284. package/dist/utils/merge-permissions.d.ts +0 -3
  285. package/dist/utils/merge-permissions.js +0 -95
@@ -1,19 +1,22 @@
1
1
  import { Action } from '@directus/constants';
2
2
  import { useEnv } from '@directus/env';
3
- import { ForbiddenError, InvalidPayloadError } from '@directus/errors';
3
+ import { ErrorCode, ForbiddenError, InvalidPayloadError, isDirectusError } from '@directus/errors';
4
4
  import { isSystemCollection } from '@directus/system-data';
5
5
  import { assign, clone, cloneDeep, omit, pick, without } from 'lodash-es';
6
6
  import { getCache } from '../cache.js';
7
7
  import { translateDatabaseError } from '../database/errors/translate.js';
8
+ import { getAstFromQuery } from '../database/get-ast-from-query/get-ast-from-query.js';
8
9
  import { getHelpers } from '../database/helpers/index.js';
9
10
  import getDatabase from '../database/index.js';
10
- import runAST from '../database/run-ast.js';
11
+ import { runAst } from '../database/run-ast/run-ast.js';
11
12
  import emitter from '../emitter.js';
12
- import getASTFromQuery from '../utils/get-ast-from-query.js';
13
+ import { processAst } from '../permissions/modules/process-ast/process-ast.js';
14
+ import { processPayload } from '../permissions/modules/process-payload/process-payload.js';
15
+ import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
13
16
  import { shouldClearCache } from '../utils/should-clear-cache.js';
14
17
  import { transaction } from '../utils/transaction.js';
15
18
  import { validateKeys } from '../utils/validate-keys.js';
16
- import { AuthorizationService } from './authorization.js';
19
+ import { UserIntegrityCheckFlag, validateUserCountIntegrity } from '../utils/validate-user-count-integrity.js';
17
20
  import { PayloadService } from './payload.js';
18
21
  const env = useEnv();
19
22
  export class ItemsService {
@@ -32,6 +35,20 @@ export class ItemsService {
32
35
  this.cache = getCache().cache;
33
36
  return this;
34
37
  }
38
+ /**
39
+ * Create a fork of the current service, allowing instantiation with different options.
40
+ */
41
+ fork(options) {
42
+ const Service = this.constructor;
43
+ // ItemsService expects `collection` and `options` as parameters,
44
+ // while the other services only expect `options`
45
+ const isItemsService = Service.length === 2;
46
+ const newOptions = { knex: this.knex, accountability: this.accountability, schema: this.schema, ...options };
47
+ if (isItemsService) {
48
+ return new ItemsService(this.collection, newOptions);
49
+ }
50
+ return new Service(newOptions);
51
+ }
35
52
  createMutationTracker(initialCount = 0) {
36
53
  const maxCount = Number(env['MAX_BATCH_MUTATION']);
37
54
  let mutationCount = initialCount;
@@ -84,17 +101,13 @@ export class ItemsService {
84
101
  // that any errors thrown in any nested relational changes will bubble up and cancel the whole
85
102
  // update tree
86
103
  const primaryKey = await transaction(this.knex, async (trx) => {
87
- // We're creating new services instances so they can use the transaction as their Knex interface
88
- const payloadService = new PayloadService(this.collection, {
89
- accountability: this.accountability,
90
- knex: trx,
91
- schema: this.schema,
92
- });
93
- const authorizationService = new AuthorizationService({
104
+ const serviceOptions = {
94
105
  accountability: this.accountability,
95
106
  knex: trx,
96
107
  schema: this.schema,
97
- });
108
+ };
109
+ // We're creating new services instances so they can use the transaction as their Knex interface
110
+ const payloadService = new PayloadService(this.collection, serviceOptions);
98
111
  // Run all hooks that are attached to this event so the end user has the chance to augment the
99
112
  // item that is about to be saved
100
113
  const payloadAfterHooks = opts.emitEvents !== false
@@ -109,13 +122,21 @@ export class ItemsService {
109
122
  })
110
123
  : payload;
111
124
  const payloadWithPresets = this.accountability
112
- ? authorizationService.validatePayload('create', this.collection, payloadAfterHooks)
125
+ ? await processPayload({
126
+ accountability: this.accountability,
127
+ action: 'create',
128
+ collection: this.collection,
129
+ payload: payloadAfterHooks,
130
+ }, {
131
+ knex: trx,
132
+ schema: this.schema,
133
+ })
113
134
  : payloadAfterHooks;
114
135
  if (opts.preMutationError) {
115
136
  throw opts.preMutationError;
116
137
  }
117
- const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
118
- const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
138
+ const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
139
+ const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
119
140
  const payloadWithoutAliases = pick(payloadWithA2O, without(fields, ...aliases));
120
141
  const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
121
142
  // The primary key can already exist in the payload.
@@ -151,7 +172,14 @@ export class ItemsService {
151
172
  }
152
173
  }
153
174
  catch (err) {
154
- throw await translateDatabaseError(err);
175
+ const dbError = await translateDatabaseError(err);
176
+ if (isDirectusError(dbError, ErrorCode.RecordNotUnique) && dbError.extensions.primaryKey) {
177
+ // This is a MySQL specific thing we need to handle here, since MySQL does not return the field name
178
+ // if the unique constraint is the primary key
179
+ dbError.extensions.field = pkField?.field ?? null;
180
+ delete dbError.extensions.primaryKey;
181
+ }
182
+ throw dbError;
155
183
  }
156
184
  // Most database support returning, those who don't tend to return the PK anyways
157
185
  // (MySQL/SQLite). In case the primary key isn't know yet, we'll do a best-attempt at
@@ -166,10 +194,22 @@ export class ItemsService {
166
194
  }
167
195
  // At this point, the primary key is guaranteed to be set.
168
196
  primaryKey = primaryKey;
169
- const { revisions: revisionsO2M, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithPresets, primaryKey, opts);
197
+ const { revisions: revisionsO2M, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithPresets, primaryKey, opts);
170
198
  nestedActionEvents.push(...nestedActionEventsM2O);
171
199
  nestedActionEvents.push(...nestedActionEventsA2O);
172
200
  nestedActionEvents.push(...nestedActionEventsO2M);
201
+ const userIntegrityCheckFlags = (opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None) |
202
+ userIntegrityCheckFlagsM2O |
203
+ userIntegrityCheckFlagsA2O |
204
+ userIntegrityCheckFlagsO2M;
205
+ if (userIntegrityCheckFlags) {
206
+ if (opts.onRequireUserIntegrityCheck) {
207
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
208
+ }
209
+ else {
210
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex: trx });
211
+ }
212
+ }
173
213
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
174
214
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
175
215
  const activityService = new ActivityService({
@@ -252,16 +292,15 @@ export class ItemsService {
252
292
  }
253
293
  /**
254
294
  * Create multiple new items at once. Inserts all provided records sequentially wrapped in a transaction.
295
+ *
296
+ * Uses `this.createOne` under the hood.
255
297
  */
256
298
  async createMany(data, opts = {}) {
257
299
  if (!opts.mutationTracker)
258
300
  opts.mutationTracker = this.createMutationTracker();
259
301
  const { primaryKeys, nestedActionEvents } = await transaction(this.knex, async (knex) => {
260
- const service = new ItemsService(this.collection, {
261
- accountability: this.accountability,
262
- schema: this.schema,
263
- knex: knex,
264
- });
302
+ const service = this.fork({ knex });
303
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
265
304
  const primaryKeys = [];
266
305
  const nestedActionEvents = [];
267
306
  const pkField = this.schema.collections[this.collection].primary;
@@ -275,12 +314,21 @@ export class ItemsService {
275
314
  const primaryKey = await service.createOne(payload, {
276
315
  ...(opts || {}),
277
316
  autoPurgeCache: false,
317
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
278
318
  bypassEmitAction: (params) => nestedActionEvents.push(params),
279
319
  mutationTracker: opts.mutationTracker,
280
320
  bypassAutoIncrementSequenceReset,
281
321
  });
282
322
  primaryKeys.push(primaryKey);
283
323
  }
324
+ if (userIntegrityCheckFlags) {
325
+ if (opts.onRequireUserIntegrityCheck) {
326
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
327
+ }
328
+ else {
329
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
330
+ }
331
+ }
284
332
  return { primaryKeys, nestedActionEvents };
285
333
  });
286
334
  if (opts.emitEvents !== false) {
@@ -299,7 +347,7 @@ export class ItemsService {
299
347
  return primaryKeys;
300
348
  }
301
349
  /**
302
- * Get items by query
350
+ * Get items by query.
303
351
  */
304
352
  async readByQuery(query, opts) {
305
353
  const updatedQuery = opts?.emitEvents !== false
@@ -313,27 +361,21 @@ export class ItemsService {
313
361
  accountability: this.accountability,
314
362
  })
315
363
  : query;
316
- let ast = await getASTFromQuery(this.collection, updatedQuery, this.schema, {
364
+ let ast = await getAstFromQuery({
365
+ collection: this.collection,
366
+ query: updatedQuery,
317
367
  accountability: this.accountability,
318
- // By setting the permissions action, you can read items using the permissions for another
319
- // operation's permissions. This is used to dynamically check if you have update/delete
320
- // access to (a) certain item(s)
321
- action: opts?.permissionsAction || 'read',
368
+ }, {
369
+ schema: this.schema,
322
370
  knex: this.knex,
323
371
  });
324
- if (this.accountability && this.accountability.admin !== true) {
325
- const authorizationService = new AuthorizationService({
326
- accountability: this.accountability,
327
- knex: this.knex,
328
- schema: this.schema,
329
- });
330
- ast = await authorizationService.processAST(ast, opts?.permissionsAction);
331
- }
332
- const records = await runAST(ast, this.schema, {
372
+ ast = await processAst({ ast, action: 'read', accountability: this.accountability }, { knex: this.knex, schema: this.schema });
373
+ const records = await runAst(ast, this.schema, {
333
374
  knex: this.knex,
334
375
  // GraphQL requires relational keys to be returned regardless
335
376
  stripNonRequested: opts?.stripNonRequested !== undefined ? opts.stripNonRequested : true,
336
377
  });
378
+ // TODO when would this happen?
337
379
  if (records === null) {
338
380
  throw new ForbiddenError();
339
381
  }
@@ -361,7 +403,9 @@ export class ItemsService {
361
403
  return filteredRecords;
362
404
  }
363
405
  /**
364
- * Get single item by primary key
406
+ * Get single item by primary key.
407
+ *
408
+ * Uses `this.readByQuery` under the hood.
365
409
  */
366
410
  async readOne(key, query = {}, opts) {
367
411
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -375,7 +419,9 @@ export class ItemsService {
375
419
  return results[0];
376
420
  }
377
421
  /**
378
- * Get multiple items by primary keys
422
+ * Get multiple items by primary keys.
423
+ *
424
+ * Uses `this.readByQuery` under the hood.
379
425
  */
380
426
  async readMany(keys, query = {}, opts) {
381
427
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -390,7 +436,9 @@ export class ItemsService {
390
436
  return results;
391
437
  }
392
438
  /**
393
- * Update multiple items by query
439
+ * Update multiple items by query.
440
+ *
441
+ * Uses `this.updateMany` under the hood.
394
442
  */
395
443
  async updateByQuery(query, data, opts) {
396
444
  const keys = await this.getKeysByQuery(query);
@@ -399,7 +447,9 @@ export class ItemsService {
399
447
  return keys.length ? await this.updateMany(keys, data, opts) : [];
400
448
  }
401
449
  /**
402
- * Update a single item by primary key
450
+ * Update a single item by primary key.
451
+ *
452
+ * Uses `this.updateMany` under the hood.
403
453
  */
404
454
  async updateOne(key, data, opts) {
405
455
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -408,7 +458,9 @@ export class ItemsService {
408
458
  return key;
409
459
  }
410
460
  /**
411
- * Update multiple items in a single transaction
461
+ * Update multiple items in a single transaction.
462
+ *
463
+ * Uses `this.updateOne` under the hood.
412
464
  */
413
465
  async updateBatch(data, opts = {}) {
414
466
  if (!Array.isArray(data)) {
@@ -419,17 +471,27 @@ export class ItemsService {
419
471
  const primaryKeyField = this.schema.collections[this.collection].primary;
420
472
  const keys = [];
421
473
  try {
422
- await transaction(this.knex, async (trx) => {
423
- const service = new ItemsService(this.collection, {
424
- accountability: this.accountability,
425
- knex: trx,
426
- schema: this.schema,
427
- });
474
+ await transaction(this.knex, async (knex) => {
475
+ const service = this.fork({ knex });
476
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
428
477
  for (const item of data) {
429
- if (!item[primaryKeyField])
478
+ const primaryKey = item[primaryKeyField];
479
+ if (!primaryKey)
430
480
  throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
431
- const combinedOpts = Object.assign({ autoPurgeCache: false }, opts);
432
- keys.push(await service.updateOne(item[primaryKeyField], omit(item, primaryKeyField), combinedOpts));
481
+ const combinedOpts = {
482
+ autoPurgeCache: false,
483
+ ...opts,
484
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
485
+ };
486
+ keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
487
+ }
488
+ if (userIntegrityCheckFlags) {
489
+ if (opts.onRequireUserIntegrityCheck) {
490
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
491
+ }
492
+ else {
493
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
494
+ }
433
495
  }
434
496
  });
435
497
  }
@@ -441,7 +503,7 @@ export class ItemsService {
441
503
  return keys;
442
504
  }
443
505
  /**
444
- * Update many items by primary key, setting all items to the same change
506
+ * Update many items by primary key, setting all items to the same change.
445
507
  */
446
508
  async updateMany(keys, data, opts = {}) {
447
509
  if (!opts.mutationTracker)
@@ -459,11 +521,6 @@ export class ItemsService {
459
521
  .map((field) => field.field);
460
522
  const payload = cloneDeep(data);
461
523
  const nestedActionEvents = [];
462
- const authorizationService = new AuthorizationService({
463
- accountability: this.accountability,
464
- knex: this.knex,
465
- schema: this.schema,
466
- });
467
524
  // Run all hooks that are attached to this event so the end user has the chance to augment the
468
525
  // item that is about to be saved
469
526
  const payloadAfterHooks = opts.emitEvents !== false
@@ -481,10 +538,26 @@ export class ItemsService {
481
538
  // Sort keys to ensure that the order is maintained
482
539
  keys.sort();
483
540
  if (this.accountability) {
484
- await authorizationService.checkAccess('update', this.collection, keys);
541
+ await validateAccess({
542
+ accountability: this.accountability,
543
+ action: 'update',
544
+ collection: this.collection,
545
+ primaryKeys: keys,
546
+ }, {
547
+ schema: this.schema,
548
+ knex: this.knex,
549
+ });
485
550
  }
486
551
  const payloadWithPresets = this.accountability
487
- ? authorizationService.validatePayload('update', this.collection, payloadAfterHooks)
552
+ ? await processPayload({
553
+ accountability: this.accountability,
554
+ action: 'update',
555
+ collection: this.collection,
556
+ payload: payloadAfterHooks,
557
+ }, {
558
+ knex: this.knex,
559
+ schema: this.schema,
560
+ })
488
561
  : payloadAfterHooks;
489
562
  if (opts.preMutationError) {
490
563
  throw opts.preMutationError;
@@ -495,8 +568,8 @@ export class ItemsService {
495
568
  knex: trx,
496
569
  schema: this.schema,
497
570
  });
498
- const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
499
- const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
571
+ const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, userIntegrityCheckFlags: userIntegrityCheckFlagsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
572
+ const { payload: payloadWithA2O, revisions: revisionsA2O, nestedActionEvents: nestedActionEventsA2O, userIntegrityCheckFlags: userIntegrityCheckFlagsA2O, } = await payloadService.processA2O(payloadWithM2O, opts);
500
573
  const payloadWithoutAliasAndPK = pick(payloadWithA2O, without(fields, primaryKeyField, ...aliases));
501
574
  const payloadWithTypeCasting = await payloadService.processValues('update', payloadWithoutAliasAndPK);
502
575
  if (Object.keys(payloadWithTypeCasting).length > 0) {
@@ -508,12 +581,25 @@ export class ItemsService {
508
581
  }
509
582
  }
510
583
  const childrenRevisions = [...revisionsM2O, ...revisionsA2O];
584
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ??
585
+ UserIntegrityCheckFlag.None | userIntegrityCheckFlagsM2O | userIntegrityCheckFlagsA2O;
511
586
  nestedActionEvents.push(...nestedActionEventsM2O);
512
587
  nestedActionEvents.push(...nestedActionEventsA2O);
513
588
  for (const key of keys) {
514
- const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
589
+ const { revisions, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithA2O, key, opts);
515
590
  childrenRevisions.push(...revisions);
516
591
  nestedActionEvents.push(...nestedActionEventsO2M);
592
+ userIntegrityCheckFlags |= userIntegrityCheckFlagsO2M;
593
+ }
594
+ if (userIntegrityCheckFlags) {
595
+ if (opts?.onRequireUserIntegrityCheck) {
596
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
597
+ }
598
+ else {
599
+ // Having no onRequireUserIntegrityCheck callback indicates that
600
+ // this is the top level invocation of the nested updates, so perform the user integrity check
601
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex: trx });
602
+ }
517
603
  }
518
604
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
519
605
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
@@ -603,7 +689,9 @@ export class ItemsService {
603
689
  return keys;
604
690
  }
605
691
  /**
606
- * Upsert a single item
692
+ * Upsert a single item.
693
+ *
694
+ * Uses `this.createOne` / `this.updateOne` under the hood.
607
695
  */
608
696
  async upsertOne(payload, opts) {
609
697
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -625,17 +713,15 @@ export class ItemsService {
625
713
  }
626
714
  }
627
715
  /**
628
- * Upsert many items
716
+ * Upsert many items.
717
+ *
718
+ * Uses `this.upsertOne` under the hood.
629
719
  */
630
720
  async upsertMany(payloads, opts = {}) {
631
721
  if (!opts.mutationTracker)
632
722
  opts.mutationTracker = this.createMutationTracker();
633
- const primaryKeys = await transaction(this.knex, async (trx) => {
634
- const service = new ItemsService(this.collection, {
635
- accountability: this.accountability,
636
- schema: this.schema,
637
- knex: trx,
638
- });
723
+ const primaryKeys = await transaction(this.knex, async (knex) => {
724
+ const service = this.fork({ knex });
639
725
  const primaryKeys = [];
640
726
  for (const payload of payloads) {
641
727
  const primaryKey = await service.upsertOne(payload, { ...(opts || {}), autoPurgeCache: false });
@@ -649,7 +735,9 @@ export class ItemsService {
649
735
  return primaryKeys;
650
736
  }
651
737
  /**
652
- * Delete multiple items by query
738
+ * Delete multiple items by query.
739
+ *
740
+ * Uses `this.deleteMany` under the hood.
653
741
  */
654
742
  async deleteByQuery(query, opts) {
655
743
  const keys = await this.getKeysByQuery(query);
@@ -658,7 +746,9 @@ export class ItemsService {
658
746
  return keys.length ? await this.deleteMany(keys, opts) : [];
659
747
  }
660
748
  /**
661
- * Delete a single item by primary key
749
+ * Delete a single item by primary key.
750
+ *
751
+ * Uses `this.deleteMany` under the hood.
662
752
  */
663
753
  async deleteOne(key, opts) {
664
754
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -667,7 +757,7 @@ export class ItemsService {
667
757
  return key;
668
758
  }
669
759
  /**
670
- * Delete multiple items by primary key
760
+ * Delete multiple items by primary key.
671
761
  */
672
762
  async deleteMany(keys, opts = {}) {
673
763
  if (!opts.mutationTracker)
@@ -678,13 +768,16 @@ export class ItemsService {
678
768
  const { ActivityService } = await import('./activity.js');
679
769
  const primaryKeyField = this.schema.collections[this.collection].primary;
680
770
  validateKeys(this.schema, this.collection, primaryKeyField, keys);
681
- if (this.accountability && this.accountability.admin !== true) {
682
- const authorizationService = new AuthorizationService({
771
+ if (this.accountability) {
772
+ await validateAccess({
683
773
  accountability: this.accountability,
684
- schema: this.schema,
774
+ action: 'delete',
775
+ collection: this.collection,
776
+ primaryKeys: keys,
777
+ }, {
685
778
  knex: this.knex,
779
+ schema: this.schema,
686
780
  });
687
- await authorizationService.checkAccess('delete', this.collection, keys);
688
781
  }
689
782
  if (opts.preMutationError) {
690
783
  throw opts.preMutationError;
@@ -700,6 +793,14 @@ export class ItemsService {
700
793
  }
701
794
  await transaction(this.knex, async (trx) => {
702
795
  await trx(this.collection).whereIn(primaryKeyField, keys).delete();
796
+ if (opts.userIntegrityCheckFlags) {
797
+ if (opts.onRequireUserIntegrityCheck) {
798
+ opts.onRequireUserIntegrityCheck(opts.userIntegrityCheckFlags);
799
+ }
800
+ else {
801
+ await validateUserCountIntegrity({ flags: opts.userIntegrityCheckFlags, knex: trx });
802
+ }
803
+ }
703
804
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
704
805
  const activityService = new ActivityService({
705
806
  knex: trx,
@@ -745,7 +846,7 @@ export class ItemsService {
745
846
  return keys;
746
847
  }
747
848
  /**
748
- * Read/treat collection as singleton
849
+ * Read/treat collection as singleton.
749
850
  */
750
851
  async readSingleton(query, opts) {
751
852
  query = clone(query);
@@ -773,7 +874,9 @@ export class ItemsService {
773
874
  return record;
774
875
  }
775
876
  /**
776
- * Upsert/treat collection as singleton
877
+ * Upsert/treat collection as singleton.
878
+ *
879
+ * Uses `this.createOne` / `this.updateOne` under the hood.
777
880
  */
778
881
  async upsertSingleton(data, opts) {
779
882
  const primaryKeyField = this.schema.collections[this.collection].primary;
@@ -1,5 +1,8 @@
1
1
  import getDatabase from '../database/index.js';
2
- import { ForbiddenError } from '@directus/errors';
2
+ import { fetchPermissions } from '../permissions/lib/fetch-permissions.js';
3
+ import { fetchPolicies } from '../permissions/lib/fetch-policies.js';
4
+ import { dedupeAccess } from '../permissions/modules/process-ast/utils/dedupe-access.js';
5
+ import { validateAccess } from '../permissions/modules/validate-access/validate-access.js';
3
6
  import { applyFilter, applySearch } from '../utils/apply-query.js';
4
7
  export class MetaService {
5
8
  knex;
@@ -28,39 +31,73 @@ export class MetaService {
28
31
  }, {});
29
32
  }
30
33
  async totalCount(collection) {
31
- const dbQuery = this.knex(collection).count('*', { as: 'count' }).first();
32
- if (this.accountability?.admin !== true) {
33
- const permissionsRecord = this.accountability?.permissions?.find((permission) => {
34
- return permission.action === 'read' && permission.collection === collection;
35
- });
36
- if (!permissionsRecord)
37
- throw new ForbiddenError();
38
- const permissions = permissionsRecord.permissions ?? {};
39
- applyFilter(this.knex, this.schema, dbQuery, permissions, collection, {});
34
+ const dbQuery = this.knex(collection);
35
+ let hasJoins = false;
36
+ if (this.accountability && this.accountability.admin === false) {
37
+ const context = { knex: this.knex, schema: this.schema };
38
+ await validateAccess({
39
+ accountability: this.accountability,
40
+ action: 'read',
41
+ collection,
42
+ }, context);
43
+ const policies = await fetchPolicies(this.accountability, context);
44
+ const permissions = await fetchPermissions({
45
+ action: 'read',
46
+ policies,
47
+ accountability: this.accountability,
48
+ ...(collection ? { collections: [collection] } : {}),
49
+ }, context);
50
+ const rules = dedupeAccess(permissions);
51
+ const cases = rules.map(({ rule }) => rule);
52
+ const filter = {
53
+ _or: cases,
54
+ };
55
+ const result = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}, cases);
56
+ hasJoins = result.hasJoins;
57
+ }
58
+ if (hasJoins) {
59
+ const primaryKeyName = this.schema.collections[collection].primary;
60
+ dbQuery.countDistinct({ count: [`${collection}.${primaryKeyName}`] });
61
+ }
62
+ else {
63
+ dbQuery.count('*', { as: 'count' });
40
64
  }
41
- const result = await dbQuery;
65
+ const result = await dbQuery.first();
42
66
  return Number(result?.count ?? 0);
43
67
  }
44
68
  async filterCount(collection, query) {
45
69
  const dbQuery = this.knex(collection);
46
70
  let filter = query.filter || {};
47
71
  let hasJoins = false;
48
- if (this.accountability?.admin !== true) {
49
- const permissionsRecord = this.accountability?.permissions?.find((permission) => {
50
- return permission.action === 'read' && permission.collection === collection;
51
- });
52
- if (!permissionsRecord)
53
- throw new ForbiddenError();
54
- const permissions = permissionsRecord.permissions ?? {};
72
+ let cases = [];
73
+ if (this.accountability && this.accountability.admin === false) {
74
+ const context = { knex: this.knex, schema: this.schema };
75
+ await validateAccess({
76
+ accountability: this.accountability,
77
+ action: 'read',
78
+ collection,
79
+ }, context);
80
+ const policies = await fetchPolicies(this.accountability, context);
81
+ const permissions = await fetchPermissions({
82
+ action: 'read',
83
+ policies,
84
+ accountability: this.accountability,
85
+ ...(collection ? { collections: [collection] } : {}),
86
+ }, context);
87
+ const rules = dedupeAccess(permissions);
88
+ cases = rules.map(({ rule }) => rule);
89
+ const permissionsFilter = {
90
+ _or: cases,
91
+ };
55
92
  if (Object.keys(filter).length > 0) {
56
- filter = { _and: [permissions, filter] };
93
+ filter = { _and: [permissionsFilter, filter] };
57
94
  }
58
95
  else {
59
- filter = permissions;
96
+ filter = permissionsFilter;
60
97
  }
61
98
  }
62
99
  if (Object.keys(filter).length > 0) {
63
- ({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}));
100
+ ({ hasJoins } = applyFilter(this.knex, this.schema, dbQuery, filter, collection, {}, cases));
64
101
  }
65
102
  if (query.search) {
66
103
  applySearch(this.knex, this.schema, dbQuery, query.search, collection);
@@ -72,7 +109,7 @@ export class MetaService {
72
109
  else {
73
110
  dbQuery.count('*', { as: 'count' });
74
111
  }
75
- const records = await dbQuery;
76
- return Number(records[0]['count']);
112
+ const result = await dbQuery.first();
113
+ return Number(result?.count ?? 0);
77
114
  }
78
115
  }
@@ -8,6 +8,5 @@ export declare class NotificationsService extends ItemsService {
8
8
  mailService: MailService;
9
9
  constructor(options: AbstractServiceOptions);
10
10
  createOne(data: Partial<Notification>, opts?: MutationOptions): Promise<PrimaryKey>;
11
- createMany(data: Partial<Notification>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
12
11
  sendEmail(data: Partial<Notification>): Promise<void>;
13
12
  }
@@ -20,13 +20,6 @@ export class NotificationsService extends ItemsService {
20
20
  await this.sendEmail(data);
21
21
  return response;
22
22
  }
23
- async createMany(data, opts) {
24
- const response = await super.createMany(data, opts);
25
- for (const notification of data) {
26
- await this.sendEmail(notification);
27
- }
28
- return response;
29
- }
30
23
  async sendEmail(data) {
31
24
  if (data.recipient) {
32
25
  const user = await this.usersService.readOne(data.recipient, {
@@ -4,8 +4,6 @@ import { ItemsService } from './items.js';
4
4
  export declare class OperationsService extends ItemsService<OperationRaw> {
5
5
  constructor(options: AbstractServiceOptions);
6
6
  createOne(data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey>;
7
- createMany(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
8
- updateBatch(data: Partial<Item>[], opts?: MutationOptions): Promise<PrimaryKey[]>;
9
7
  updateMany(keys: PrimaryKey[], data: Partial<Item>, opts?: MutationOptions): Promise<PrimaryKey[]>;
10
8
  deleteMany(keys: PrimaryKey[], opts?: MutationOptions): Promise<PrimaryKey[]>;
11
9
  }