@directus/api 21.0.0 → 22.1.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 (299) 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/commands/init/questions.d.ts +7 -6
  10. package/dist/cli/commands/init/questions.js +2 -2
  11. package/dist/cli/utils/create-env/index.d.ts +2 -2
  12. package/dist/cli/utils/create-env/index.js +3 -1
  13. package/dist/cli/utils/defaults.d.ts +4 -11
  14. package/dist/cli/utils/defaults.js +7 -1
  15. package/dist/cli/utils/drivers.js +1 -1
  16. package/dist/constants.d.ts +1 -1
  17. package/dist/controllers/access.d.ts +2 -0
  18. package/dist/controllers/access.js +148 -0
  19. package/dist/controllers/auth.js +5 -16
  20. package/dist/controllers/permissions.js +14 -2
  21. package/dist/controllers/policies.d.ts +2 -0
  22. package/dist/controllers/policies.js +169 -0
  23. package/dist/controllers/roles.js +22 -1
  24. package/dist/controllers/tus.js +14 -26
  25. package/dist/controllers/users.js +0 -55
  26. package/dist/database/get-ast-from-query/get-ast-from-query.d.ts +16 -0
  27. package/dist/database/get-ast-from-query/get-ast-from-query.js +82 -0
  28. package/dist/database/get-ast-from-query/lib/convert-wildcards.d.ts +13 -0
  29. package/dist/database/get-ast-from-query/lib/convert-wildcards.js +69 -0
  30. package/dist/database/get-ast-from-query/lib/parse-fields.d.ts +15 -0
  31. package/dist/database/get-ast-from-query/lib/parse-fields.js +200 -0
  32. package/dist/database/get-ast-from-query/utils/get-deep-query.d.ts +14 -0
  33. package/dist/database/get-ast-from-query/utils/get-deep-query.js +17 -0
  34. package/dist/database/get-ast-from-query/utils/get-related-collection.d.ts +2 -0
  35. package/dist/database/get-ast-from-query/utils/get-related-collection.js +13 -0
  36. package/dist/database/get-ast-from-query/utils/get-relation.d.ts +2 -0
  37. package/dist/database/get-ast-from-query/utils/get-relation.js +7 -0
  38. package/dist/database/helpers/fn/types.d.ts +2 -1
  39. package/dist/database/helpers/fn/types.js +1 -1
  40. package/dist/database/helpers/geometry/dialects/mssql.d.ts +1 -1
  41. package/dist/database/helpers/geometry/dialects/mssql.js +4 -2
  42. package/dist/database/helpers/geometry/dialects/mysql.js +1 -1
  43. package/dist/database/helpers/geometry/dialects/oracle.d.ts +1 -1
  44. package/dist/database/helpers/geometry/dialects/oracle.js +5 -3
  45. package/dist/database/helpers/geometry/types.d.ts +1 -1
  46. package/dist/database/helpers/geometry/types.js +4 -2
  47. package/dist/database/helpers/index.d.ts +3 -3
  48. package/dist/database/helpers/schema/dialects/cockroachdb.d.ts +2 -1
  49. package/dist/database/helpers/schema/dialects/cockroachdb.js +4 -0
  50. package/dist/database/helpers/schema/dialects/mssql.d.ts +2 -1
  51. package/dist/database/helpers/schema/dialects/mssql.js +4 -0
  52. package/dist/database/helpers/schema/dialects/oracle.d.ts +2 -1
  53. package/dist/database/helpers/schema/dialects/oracle.js +4 -0
  54. package/dist/database/helpers/schema/dialects/postgres.d.ts +2 -1
  55. package/dist/database/helpers/schema/dialects/postgres.js +4 -0
  56. package/dist/database/helpers/schema/types.d.ts +5 -0
  57. package/dist/database/helpers/schema/types.js +3 -0
  58. package/dist/database/helpers/schema/utils/preprocess-bindings.d.ts +8 -0
  59. package/dist/database/helpers/schema/utils/preprocess-bindings.js +30 -0
  60. package/dist/database/index.js +6 -1
  61. package/dist/database/migrations/20210519A-add-system-fk-triggers.js +3 -2
  62. package/dist/database/migrations/20230721A-require-shares-fields.js +3 -5
  63. package/dist/database/migrations/20240716A-update-files-date-fields.js +3 -7
  64. package/dist/{utils/merge-permissions.d.ts → database/migrations/20240806A-permissions-policies.d.ts} +4 -1
  65. package/dist/database/migrations/20240806A-permissions-policies.js +352 -0
  66. package/dist/database/run-ast/lib/get-db-query.d.ts +4 -0
  67. package/dist/database/run-ast/lib/get-db-query.js +218 -0
  68. package/dist/database/run-ast/lib/parse-current-level.d.ts +7 -0
  69. package/dist/database/run-ast/lib/parse-current-level.js +41 -0
  70. package/dist/database/run-ast/run-ast.d.ts +7 -0
  71. package/dist/database/run-ast/run-ast.js +107 -0
  72. package/dist/database/{run-ast.d.ts → run-ast/types.d.ts} +3 -9
  73. package/dist/database/run-ast/types.js +1 -0
  74. package/dist/database/run-ast/utils/apply-case-when.d.ts +16 -0
  75. package/dist/database/run-ast/utils/apply-case-when.js +27 -0
  76. package/dist/database/run-ast/utils/apply-parent-filters.d.ts +3 -0
  77. package/dist/database/run-ast/utils/apply-parent-filters.js +55 -0
  78. package/dist/database/run-ast/utils/get-column-pre-processor.d.ts +10 -0
  79. package/dist/database/run-ast/utils/get-column-pre-processor.js +57 -0
  80. package/dist/database/run-ast/utils/get-field-alias.d.ts +2 -0
  81. package/dist/database/run-ast/utils/get-field-alias.js +4 -0
  82. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.d.ts +5 -0
  83. package/dist/database/run-ast/utils/get-inner-query-column-pre-processor.js +23 -0
  84. package/dist/database/run-ast/utils/merge-with-parent-items.d.ts +3 -0
  85. package/dist/database/run-ast/utils/merge-with-parent-items.js +87 -0
  86. package/dist/database/run-ast/utils/remove-temporary-fields.d.ts +3 -0
  87. package/dist/database/run-ast/utils/remove-temporary-fields.js +73 -0
  88. package/dist/database/run-ast/utils/with-preprocess-bindings.d.ts +2 -0
  89. package/dist/database/run-ast/utils/with-preprocess-bindings.js +14 -0
  90. package/dist/flows.js +3 -4
  91. package/dist/middleware/authenticate.js +2 -7
  92. package/dist/middleware/cache.js +1 -1
  93. package/dist/middleware/respond.js +1 -1
  94. package/dist/permissions/cache.d.ts +2 -0
  95. package/dist/permissions/cache.js +23 -0
  96. package/dist/permissions/lib/fetch-permissions.d.ts +11 -0
  97. package/dist/permissions/lib/fetch-permissions.js +56 -0
  98. package/dist/permissions/lib/fetch-policies.d.ts +14 -0
  99. package/dist/permissions/lib/fetch-policies.js +43 -0
  100. package/dist/permissions/lib/fetch-roles-tree.d.ts +3 -0
  101. package/dist/permissions/lib/fetch-roles-tree.js +28 -0
  102. package/dist/{services/permissions → permissions}/lib/with-app-minimal-permissions.d.ts +1 -1
  103. package/dist/permissions/lib/with-app-minimal-permissions.js +10 -0
  104. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.d.ts +7 -0
  105. package/dist/permissions/modules/fetch-accountability-collection-access/fetch-accountability-collection-access.js +56 -0
  106. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.d.ts +3 -0
  107. package/dist/permissions/modules/fetch-accountability-policy-globals/fetch-accountability-policy-globals.js +16 -0
  108. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.d.ts +8 -0
  109. package/dist/permissions/modules/fetch-allowed-collections/fetch-allowed-collections.js +24 -0
  110. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.d.ts +9 -0
  111. package/dist/permissions/modules/fetch-allowed-field-map/fetch-allowed-field-map.js +31 -0
  112. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.d.ts +16 -0
  113. package/dist/permissions/modules/fetch-allowed-fields/fetch-allowed-fields.js +27 -0
  114. package/dist/permissions/modules/fetch-global-access/fetch-global-access.d.ts +10 -0
  115. package/dist/permissions/modules/fetch-global-access/fetch-global-access.js +23 -0
  116. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.d.ts +5 -0
  117. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-roles.js +7 -0
  118. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.d.ts +5 -0
  119. package/dist/permissions/modules/fetch-global-access/lib/fetch-global-access-for-user.js +10 -0
  120. package/dist/permissions/modules/fetch-global-access/types.d.ts +4 -0
  121. package/dist/permissions/modules/fetch-global-access/types.js +1 -0
  122. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.d.ts +4 -0
  123. package/dist/permissions/modules/fetch-global-access/utils/fetch-global-access-for-query.js +27 -0
  124. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.d.ts +12 -0
  125. package/dist/permissions/modules/fetch-inconsistent-field-map/fetch-inconsistent-field-map.js +32 -0
  126. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.d.ts +4 -0
  127. package/dist/permissions/modules/fetch-policies-ip-access/fetch-policies-ip-access.js +29 -0
  128. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.d.ts +4 -0
  129. package/dist/permissions/modules/process-ast/lib/extract-fields-from-children.js +49 -0
  130. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.d.ts +3 -0
  131. package/dist/permissions/modules/process-ast/lib/extract-fields-from-query.js +56 -0
  132. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.d.ts +4 -0
  133. package/dist/permissions/modules/process-ast/lib/field-map-from-ast.js +8 -0
  134. package/dist/permissions/modules/process-ast/lib/inject-cases.d.ts +9 -0
  135. package/dist/permissions/modules/process-ast/lib/inject-cases.js +93 -0
  136. package/dist/permissions/modules/process-ast/process-ast.d.ts +9 -0
  137. package/dist/permissions/modules/process-ast/process-ast.js +39 -0
  138. package/dist/permissions/modules/process-ast/types.d.ts +18 -0
  139. package/dist/permissions/modules/process-ast/types.js +1 -0
  140. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.d.ts +2 -0
  141. package/dist/permissions/modules/process-ast/utils/collections-in-field-map.js +7 -0
  142. package/dist/permissions/modules/process-ast/utils/dedupe-access.d.ts +12 -0
  143. package/dist/permissions/modules/process-ast/utils/dedupe-access.js +30 -0
  144. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.d.ts +15 -0
  145. package/dist/permissions/modules/process-ast/utils/extract-paths-from-query.js +60 -0
  146. package/dist/permissions/modules/process-ast/utils/find-related-collection.d.ts +3 -0
  147. package/dist/permissions/modules/process-ast/utils/find-related-collection.js +9 -0
  148. package/dist/permissions/modules/process-ast/utils/flatten-filter.d.ts +3 -0
  149. package/dist/permissions/modules/process-ast/utils/flatten-filter.js +34 -0
  150. package/dist/permissions/modules/process-ast/utils/format-a2o-key.d.ts +1 -0
  151. package/dist/permissions/modules/process-ast/utils/format-a2o-key.js +3 -0
  152. package/dist/permissions/modules/process-ast/utils/get-info-for-path.d.ts +5 -0
  153. package/dist/permissions/modules/process-ast/utils/get-info-for-path.js +7 -0
  154. package/dist/permissions/modules/process-ast/utils/has-item-permissions.d.ts +2 -0
  155. package/dist/permissions/modules/process-ast/utils/has-item-permissions.js +3 -0
  156. package/dist/permissions/modules/process-ast/utils/stringify-query-path.d.ts +2 -0
  157. package/dist/permissions/modules/process-ast/utils/stringify-query-path.js +3 -0
  158. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.d.ts +3 -0
  159. package/dist/permissions/modules/process-ast/utils/validate-path/create-error.js +16 -0
  160. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.d.ts +2 -0
  161. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-existence.js +12 -0
  162. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.d.ts +2 -0
  163. package/dist/permissions/modules/process-ast/utils/validate-path/validate-path-permissions.js +28 -0
  164. package/dist/permissions/modules/process-payload/lib/is-field-nullable.d.ts +5 -0
  165. package/dist/permissions/modules/process-payload/lib/is-field-nullable.js +12 -0
  166. package/dist/permissions/modules/process-payload/process-payload.d.ts +13 -0
  167. package/dist/permissions/modules/process-payload/process-payload.js +77 -0
  168. package/dist/permissions/modules/validate-access/lib/validate-collection-access.d.ts +12 -0
  169. package/dist/permissions/modules/validate-access/lib/validate-collection-access.js +11 -0
  170. package/dist/permissions/modules/validate-access/lib/validate-item-access.d.ts +9 -0
  171. package/dist/permissions/modules/validate-access/lib/validate-item-access.js +33 -0
  172. package/dist/permissions/modules/validate-access/validate-access.d.ts +14 -0
  173. package/dist/permissions/modules/validate-access/validate-access.js +28 -0
  174. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.d.ts +1 -0
  175. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-count.js +8 -0
  176. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.d.ts +5 -0
  177. package/dist/permissions/modules/validate-remaining-admin/validate-remaining-admin-users.js +10 -0
  178. package/dist/permissions/types.d.ts +6 -0
  179. package/dist/permissions/types.js +1 -0
  180. package/dist/permissions/utils/create-default-accountability.d.ts +2 -0
  181. package/dist/permissions/utils/create-default-accountability.js +11 -0
  182. package/dist/permissions/utils/extract-required-dynamic-variable-context.d.ts +8 -0
  183. package/dist/permissions/utils/extract-required-dynamic-variable-context.js +27 -0
  184. package/dist/permissions/utils/fetch-dynamic-variable-context.d.ts +9 -0
  185. package/dist/permissions/utils/fetch-dynamic-variable-context.js +43 -0
  186. package/dist/permissions/utils/filter-policies-by-ip.d.ts +2 -0
  187. package/dist/permissions/utils/filter-policies-by-ip.js +15 -0
  188. package/dist/permissions/utils/get-unaliased-field-key.d.ts +5 -0
  189. package/dist/permissions/utils/get-unaliased-field-key.js +17 -0
  190. package/dist/permissions/utils/process-permissions.d.ts +7 -0
  191. package/dist/permissions/utils/process-permissions.js +9 -0
  192. package/dist/permissions/utils/with-cache.d.ts +10 -0
  193. package/dist/permissions/utils/with-cache.js +25 -0
  194. package/dist/server.js +17 -4
  195. package/dist/services/access.d.ts +10 -0
  196. package/dist/services/access.js +43 -0
  197. package/dist/services/activity.js +22 -10
  198. package/dist/services/assets.d.ts +2 -3
  199. package/dist/services/assets.js +10 -5
  200. package/dist/services/authentication.js +18 -18
  201. package/dist/services/collections.js +18 -17
  202. package/dist/services/fields.d.ts +0 -1
  203. package/dist/services/fields.js +54 -25
  204. package/dist/services/files.js +10 -3
  205. package/dist/services/graphql/index.d.ts +3 -3
  206. package/dist/services/graphql/index.js +126 -22
  207. package/dist/services/graphql/subscription.js +2 -4
  208. package/dist/services/import-export.d.ts +3 -1
  209. package/dist/services/import-export.js +67 -9
  210. package/dist/services/index.d.ts +3 -2
  211. package/dist/services/index.js +3 -2
  212. package/dist/services/items.js +115 -44
  213. package/dist/services/meta.js +60 -23
  214. package/dist/services/notifications.js +14 -6
  215. package/dist/services/payload.d.ts +9 -10
  216. package/dist/services/payload.js +18 -3
  217. package/dist/services/{permissions/index.d.ts → permissions.d.ts} +5 -7
  218. package/dist/services/{permissions/index.js → permissions.js} +30 -54
  219. package/dist/services/policies.d.ts +12 -0
  220. package/dist/services/policies.js +87 -0
  221. package/dist/services/relations.d.ts +0 -6
  222. package/dist/services/relations.js +27 -30
  223. package/dist/services/roles.d.ts +4 -12
  224. package/dist/services/roles.js +57 -424
  225. package/dist/services/shares.d.ts +0 -2
  226. package/dist/services/shares.js +12 -8
  227. package/dist/services/specifications.d.ts +2 -2
  228. package/dist/services/specifications.js +39 -27
  229. package/dist/services/users.d.ts +1 -5
  230. package/dist/services/users.js +78 -161
  231. package/dist/services/utils.js +11 -7
  232. package/dist/services/versions.d.ts +0 -2
  233. package/dist/services/versions.js +34 -10
  234. package/dist/telemetry/lib/get-report.js +2 -2
  235. package/dist/telemetry/utils/check-user-limits.d.ts +5 -0
  236. package/dist/telemetry/utils/check-user-limits.js +19 -0
  237. package/dist/types/ast.d.ts +43 -1
  238. package/dist/types/database.d.ts +1 -1
  239. package/dist/types/items.d.ts +11 -0
  240. package/dist/utils/apply-query.d.ts +11 -7
  241. package/dist/utils/apply-query.js +69 -11
  242. package/dist/utils/fetch-user-count/fetch-access-lookup.d.ts +19 -0
  243. package/dist/utils/fetch-user-count/fetch-access-lookup.js +23 -0
  244. package/dist/utils/fetch-user-count/fetch-access-roles.d.ts +16 -0
  245. package/dist/utils/fetch-user-count/fetch-access-roles.js +37 -0
  246. package/dist/utils/fetch-user-count/fetch-active-users.d.ts +6 -0
  247. package/dist/utils/fetch-user-count/fetch-active-users.js +3 -0
  248. package/dist/utils/fetch-user-count/fetch-user-count.d.ts +12 -0
  249. package/dist/utils/fetch-user-count/fetch-user-count.js +64 -0
  250. package/dist/utils/fetch-user-count/get-user-count-query.d.ts +20 -0
  251. package/dist/utils/fetch-user-count/get-user-count-query.js +17 -0
  252. package/dist/utils/get-accountability-for-role.js +16 -25
  253. package/dist/utils/get-accountability-for-token.js +17 -16
  254. package/dist/utils/get-address.d.ts +5 -0
  255. package/dist/utils/get-address.js +13 -0
  256. package/dist/utils/get-cache-key.d.ts +1 -1
  257. package/dist/utils/get-cache-key.js +12 -1
  258. package/dist/utils/get-column.d.ts +2 -1
  259. package/dist/utils/get-column.js +1 -0
  260. package/dist/utils/get-service.js +5 -1
  261. package/dist/utils/reduce-schema.d.ts +4 -6
  262. package/dist/utils/reduce-schema.js +16 -32
  263. package/dist/utils/sanitize-schema.d.ts +1 -1
  264. package/dist/utils/transaction.js +28 -11
  265. package/dist/utils/validate-user-count-integrity.d.ts +13 -0
  266. package/dist/utils/validate-user-count-integrity.js +29 -0
  267. package/dist/websocket/authenticate.d.ts +0 -2
  268. package/dist/websocket/authenticate.js +0 -12
  269. package/dist/websocket/controllers/graphql.js +3 -7
  270. package/dist/websocket/controllers/hooks.js +4 -0
  271. package/dist/websocket/controllers/rest.js +2 -5
  272. package/dist/websocket/handlers/subscribe.js +0 -2
  273. package/dist/websocket/utils/items.d.ts +1 -1
  274. package/package.json +31 -30
  275. package/dist/database/run-ast.js +0 -458
  276. package/dist/middleware/check-ip.d.ts +0 -2
  277. package/dist/middleware/check-ip.js +0 -37
  278. package/dist/middleware/get-permissions.d.ts +0 -3
  279. package/dist/middleware/get-permissions.js +0 -10
  280. package/dist/services/authorization.d.ts +0 -17
  281. package/dist/services/authorization.js +0 -456
  282. package/dist/services/permissions/lib/with-app-minimal-permissions.js +0 -13
  283. package/dist/telemetry/utils/check-increased-user-limits.d.ts +0 -7
  284. package/dist/telemetry/utils/check-increased-user-limits.js +0 -25
  285. package/dist/telemetry/utils/get-role-counts-by-roles.d.ts +0 -6
  286. package/dist/telemetry/utils/get-role-counts-by-roles.js +0 -27
  287. package/dist/telemetry/utils/get-role-counts-by-users.d.ts +0 -11
  288. package/dist/telemetry/utils/get-role-counts-by-users.js +0 -34
  289. package/dist/telemetry/utils/get-user-count.d.ts +0 -8
  290. package/dist/telemetry/utils/get-user-count.js +0 -33
  291. package/dist/telemetry/utils/get-user-counts-by-roles.d.ts +0 -7
  292. package/dist/telemetry/utils/get-user-counts-by-roles.js +0 -35
  293. package/dist/utils/get-ast-from-query.d.ts +0 -13
  294. package/dist/utils/get-ast-from-query.js +0 -297
  295. package/dist/utils/get-permissions.d.ts +0 -2
  296. package/dist/utils/get-permissions.js +0 -150
  297. package/dist/utils/merge-permissions-for-share.d.ts +0 -4
  298. package/dist/utils/merge-permissions-for-share.js +0 -109
  299. package/dist/utils/merge-permissions.js +0 -95
@@ -5,15 +5,18 @@ 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 {
@@ -98,17 +101,13 @@ export class ItemsService {
98
101
  // that any errors thrown in any nested relational changes will bubble up and cancel the whole
99
102
  // update tree
100
103
  const primaryKey = await transaction(this.knex, async (trx) => {
101
- // We're creating new services instances so they can use the transaction as their Knex interface
102
- const payloadService = new PayloadService(this.collection, {
103
- accountability: this.accountability,
104
- knex: trx,
105
- schema: this.schema,
106
- });
107
- const authorizationService = new AuthorizationService({
104
+ const serviceOptions = {
108
105
  accountability: this.accountability,
109
106
  knex: trx,
110
107
  schema: this.schema,
111
- });
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);
112
111
  // Run all hooks that are attached to this event so the end user has the chance to augment the
113
112
  // item that is about to be saved
114
113
  const payloadAfterHooks = opts.emitEvents !== false
@@ -123,13 +122,21 @@ export class ItemsService {
123
122
  })
124
123
  : payload;
125
124
  const payloadWithPresets = this.accountability
126
- ? 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
+ })
127
134
  : payloadAfterHooks;
128
135
  if (opts.preMutationError) {
129
136
  throw opts.preMutationError;
130
137
  }
131
- const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
132
- 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);
133
140
  const payloadWithoutAliases = pick(payloadWithA2O, without(fields, ...aliases));
134
141
  const payloadWithTypeCasting = await payloadService.processValues('create', payloadWithoutAliases);
135
142
  // The primary key can already exist in the payload.
@@ -187,10 +194,22 @@ export class ItemsService {
187
194
  }
188
195
  // At this point, the primary key is guaranteed to be set.
189
196
  primaryKey = primaryKey;
190
- 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);
191
198
  nestedActionEvents.push(...nestedActionEventsM2O);
192
199
  nestedActionEvents.push(...nestedActionEventsA2O);
193
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
+ }
194
213
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
195
214
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
196
215
  const activityService = new ActivityService({
@@ -281,6 +300,7 @@ export class ItemsService {
281
300
  opts.mutationTracker = this.createMutationTracker();
282
301
  const { primaryKeys, nestedActionEvents } = await transaction(this.knex, async (knex) => {
283
302
  const service = this.fork({ knex });
303
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
284
304
  const primaryKeys = [];
285
305
  const nestedActionEvents = [];
286
306
  const pkField = this.schema.collections[this.collection].primary;
@@ -294,12 +314,21 @@ export class ItemsService {
294
314
  const primaryKey = await service.createOne(payload, {
295
315
  ...(opts || {}),
296
316
  autoPurgeCache: false,
317
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
297
318
  bypassEmitAction: (params) => nestedActionEvents.push(params),
298
319
  mutationTracker: opts.mutationTracker,
299
320
  bypassAutoIncrementSequenceReset,
300
321
  });
301
322
  primaryKeys.push(primaryKey);
302
323
  }
324
+ if (userIntegrityCheckFlags) {
325
+ if (opts.onRequireUserIntegrityCheck) {
326
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
327
+ }
328
+ else {
329
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
330
+ }
331
+ }
303
332
  return { primaryKeys, nestedActionEvents };
304
333
  });
305
334
  if (opts.emitEvents !== false) {
@@ -332,27 +361,21 @@ export class ItemsService {
332
361
  accountability: this.accountability,
333
362
  })
334
363
  : query;
335
- let ast = await getASTFromQuery(this.collection, updatedQuery, this.schema, {
364
+ let ast = await getAstFromQuery({
365
+ collection: this.collection,
366
+ query: updatedQuery,
336
367
  accountability: this.accountability,
337
- // By setting the permissions action, you can read items using the permissions for another
338
- // operation's permissions. This is used to dynamically check if you have update/delete
339
- // access to (a) certain item(s)
340
- action: opts?.permissionsAction || 'read',
368
+ }, {
369
+ schema: this.schema,
341
370
  knex: this.knex,
342
371
  });
343
- if (this.accountability && this.accountability.admin !== true) {
344
- const authorizationService = new AuthorizationService({
345
- accountability: this.accountability,
346
- knex: this.knex,
347
- schema: this.schema,
348
- });
349
- ast = await authorizationService.processAST(ast, opts?.permissionsAction);
350
- }
351
- 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, {
352
374
  knex: this.knex,
353
375
  // GraphQL requires relational keys to be returned regardless
354
376
  stripNonRequested: opts?.stripNonRequested !== undefined ? opts.stripNonRequested : true,
355
377
  });
378
+ // TODO when would this happen?
356
379
  if (records === null) {
357
380
  throw new ForbiddenError();
358
381
  }
@@ -450,13 +473,26 @@ export class ItemsService {
450
473
  try {
451
474
  await transaction(this.knex, async (knex) => {
452
475
  const service = this.fork({ knex });
476
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ?? UserIntegrityCheckFlag.None;
453
477
  for (const item of data) {
454
478
  const primaryKey = item[primaryKeyField];
455
479
  if (!primaryKey)
456
480
  throw new InvalidPayloadError({ reason: `Item in update misses primary key` });
457
- const combinedOpts = Object.assign({ autoPurgeCache: false }, opts);
481
+ const combinedOpts = {
482
+ autoPurgeCache: false,
483
+ ...opts,
484
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
485
+ };
458
486
  keys.push(await service.updateOne(primaryKey, omit(item, primaryKeyField), combinedOpts));
459
487
  }
488
+ if (userIntegrityCheckFlags) {
489
+ if (opts.onRequireUserIntegrityCheck) {
490
+ opts.onRequireUserIntegrityCheck(userIntegrityCheckFlags);
491
+ }
492
+ else {
493
+ await validateUserCountIntegrity({ flags: userIntegrityCheckFlags, knex });
494
+ }
495
+ }
460
496
  });
461
497
  }
462
498
  finally {
@@ -485,11 +521,6 @@ export class ItemsService {
485
521
  .map((field) => field.field);
486
522
  const payload = cloneDeep(data);
487
523
  const nestedActionEvents = [];
488
- const authorizationService = new AuthorizationService({
489
- accountability: this.accountability,
490
- knex: this.knex,
491
- schema: this.schema,
492
- });
493
524
  // Run all hooks that are attached to this event so the end user has the chance to augment the
494
525
  // item that is about to be saved
495
526
  const payloadAfterHooks = opts.emitEvents !== false
@@ -507,10 +538,26 @@ export class ItemsService {
507
538
  // Sort keys to ensure that the order is maintained
508
539
  keys.sort();
509
540
  if (this.accountability) {
510
- 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
+ });
511
550
  }
512
551
  const payloadWithPresets = this.accountability
513
- ? 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
+ })
514
561
  : payloadAfterHooks;
515
562
  if (opts.preMutationError) {
516
563
  throw opts.preMutationError;
@@ -521,8 +568,8 @@ export class ItemsService {
521
568
  knex: trx,
522
569
  schema: this.schema,
523
570
  });
524
- const { payload: payloadWithM2O, revisions: revisionsM2O, nestedActionEvents: nestedActionEventsM2O, } = await payloadService.processM2O(payloadWithPresets, opts);
525
- 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);
526
573
  const payloadWithoutAliasAndPK = pick(payloadWithA2O, without(fields, primaryKeyField, ...aliases));
527
574
  const payloadWithTypeCasting = await payloadService.processValues('update', payloadWithoutAliasAndPK);
528
575
  if (Object.keys(payloadWithTypeCasting).length > 0) {
@@ -534,12 +581,25 @@ export class ItemsService {
534
581
  }
535
582
  }
536
583
  const childrenRevisions = [...revisionsM2O, ...revisionsA2O];
584
+ let userIntegrityCheckFlags = opts.userIntegrityCheckFlags ??
585
+ UserIntegrityCheckFlag.None | userIntegrityCheckFlagsM2O | userIntegrityCheckFlagsA2O;
537
586
  nestedActionEvents.push(...nestedActionEventsM2O);
538
587
  nestedActionEvents.push(...nestedActionEventsA2O);
539
588
  for (const key of keys) {
540
- const { revisions, nestedActionEvents: nestedActionEventsO2M } = await payloadService.processO2M(payloadWithA2O, key, opts);
589
+ const { revisions, nestedActionEvents: nestedActionEventsO2M, userIntegrityCheckFlags: userIntegrityCheckFlagsO2M, } = await payloadService.processO2M(payloadWithA2O, key, opts);
541
590
  childrenRevisions.push(...revisions);
542
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
+ }
543
603
  }
544
604
  // If this is an authenticated action, and accountability tracking is enabled, save activity row
545
605
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
@@ -708,13 +768,16 @@ export class ItemsService {
708
768
  const { ActivityService } = await import('./activity.js');
709
769
  const primaryKeyField = this.schema.collections[this.collection].primary;
710
770
  validateKeys(this.schema, this.collection, primaryKeyField, keys);
711
- if (this.accountability && this.accountability.admin !== true) {
712
- const authorizationService = new AuthorizationService({
771
+ if (this.accountability) {
772
+ await validateAccess({
713
773
  accountability: this.accountability,
714
- schema: this.schema,
774
+ action: 'delete',
775
+ collection: this.collection,
776
+ primaryKeys: keys,
777
+ }, {
715
778
  knex: this.knex,
779
+ schema: this.schema,
716
780
  });
717
- await authorizationService.checkAccess('delete', this.collection, keys);
718
781
  }
719
782
  if (opts.preMutationError) {
720
783
  throw opts.preMutationError;
@@ -730,6 +793,14 @@ export class ItemsService {
730
793
  }
731
794
  await transaction(this.knex, async (trx) => {
732
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
+ }
733
804
  if (this.accountability && this.schema.collections[this.collection].accountability !== null) {
734
805
  const activityService = new ActivityService({
735
806
  knex: trx,
@@ -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
  }
@@ -1,5 +1,7 @@
1
1
  import { useEnv } from '@directus/env';
2
2
  import { useLogger } from '../logger/index.js';
3
+ import { fetchRolesTree } from '../permissions/lib/fetch-roles-tree.js';
4
+ import { fetchGlobalAccess } from '../permissions/modules/fetch-global-access/fetch-global-access.js';
3
5
  import { md } from '../utils/md.js';
4
6
  import { Url } from '../utils/url.js';
5
7
  import { ItemsService } from './items.js';
@@ -23,18 +25,24 @@ export class NotificationsService extends ItemsService {
23
25
  async sendEmail(data) {
24
26
  if (data.recipient) {
25
27
  const user = await this.usersService.readOne(data.recipient, {
26
- fields: ['id', 'email', 'email_notifications', 'role.app_access'],
28
+ fields: ['id', 'email', 'email_notifications', 'role'],
27
29
  });
28
- const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
29
- .addPath('admin', 'users', user['id'])
30
- .toString();
31
- const html = data.message ? md(data.message) : '';
32
30
  if (user['email'] && user['email_notifications'] === true) {
31
+ const manageUserAccountUrl = new Url(env['PUBLIC_URL'])
32
+ .addPath('admin', 'users', user['id'])
33
+ .toString();
34
+ const html = data.message ? md(data.message) : '';
35
+ const roles = await fetchRolesTree(user['role'], this.knex);
36
+ const { app: app_access } = await fetchGlobalAccess({
37
+ user: user['id'],
38
+ roles,
39
+ ip: null,
40
+ }, this.knex);
33
41
  this.mailService
34
42
  .send({
35
43
  template: {
36
44
  name: 'base',
37
- data: user['role']?.app_access ? { url: manageUserAccountUrl, html } : { html },
45
+ data: app_access ? { url: manageUserAccountUrl, html } : { html },
38
46
  },
39
47
  to: user['email'],
40
48
  subject: data.subject,
@@ -2,6 +2,7 @@ import type { Accountability, Item, PrimaryKey, SchemaOverview } from '@directus
2
2
  import type { Knex } from 'knex';
3
3
  import type { Helpers } from '../database/helpers/index.js';
4
4
  import type { AbstractServiceOptions, ActionEventParams, MutationOptions } from '../types/index.js';
5
+ import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
5
6
  type Action = 'create' | 'read' | 'update';
6
7
  type Transformers = {
7
8
  [type: string]: (context: {
@@ -13,6 +14,11 @@ type Transformers = {
13
14
  helpers: Helpers;
14
15
  }) => Promise<any>;
15
16
  };
17
+ type PayloadServiceProcessRelationResult = {
18
+ revisions: PrimaryKey[];
19
+ nestedActionEvents: ActionEventParams[];
20
+ userIntegrityCheckFlags: UserIntegrityCheckFlag;
21
+ };
16
22
  /**
17
23
  * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
18
24
  * handled correctly.
@@ -46,26 +52,19 @@ export declare class PayloadService {
46
52
  /**
47
53
  * Recursively save/update all nested related Any-to-One items
48
54
  */
49
- processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
55
+ processA2O(data: Partial<Item>, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult & {
50
56
  payload: Partial<Item>;
51
- revisions: PrimaryKey[];
52
- nestedActionEvents: ActionEventParams[];
53
57
  }>;
54
58
  /**
55
59
  * Save/update all nested related m2o items inside the payload
56
60
  */
57
- processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<{
61
+ processM2O(data: Partial<Item>, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult & {
58
62
  payload: Partial<Item>;
59
- revisions: PrimaryKey[];
60
- nestedActionEvents: ActionEventParams[];
61
63
  }>;
62
64
  /**
63
65
  * Recursively save/update all nested related o2m items
64
66
  */
65
- processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<{
66
- revisions: PrimaryKey[];
67
- nestedActionEvents: ActionEventParams[];
68
- }>;
67
+ processO2M(data: Partial<Item>, parent: PrimaryKey, opts?: MutationOptions): Promise<PayloadServiceProcessRelationResult>;
69
68
  /**
70
69
  * Transforms the input partial payload to match the output structure, to have consistency
71
70
  * between delta and data
@@ -9,6 +9,7 @@ import { parse as wktToGeoJSON } from 'wellknown';
9
9
  import { getHelpers } from '../database/helpers/index.js';
10
10
  import getDatabase from '../database/index.js';
11
11
  import { generateHash } from '../utils/generate-hash.js';
12
+ import { UserIntegrityCheckFlag } from '../utils/validate-user-count-integrity.js';
12
13
  /**
13
14
  * Process a given payload for a collection to ensure the special fields (hash, uuid, date etc) are
14
15
  * handled correctly.
@@ -317,6 +318,7 @@ export class PayloadService {
317
318
  return relation.collection === this.collection;
318
319
  });
319
320
  const revisions = [];
321
+ let userIntegrityCheckFlags = UserIntegrityCheckFlag.None;
320
322
  const nestedActionEvents = [];
321
323
  const payload = cloneDeep(data);
322
324
  // Only process related records that are actually in the payload
@@ -362,6 +364,7 @@ export class PayloadService {
362
364
  if (Object.keys(fieldsToUpdate).length > 0) {
363
365
  await service.updateOne(relatedPrimaryKey, relatedRecord, {
364
366
  onRevisionCreate: (pk) => revisions.push(pk),
367
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
365
368
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
366
369
  emitEvents: opts?.emitEvents,
367
370
  mutationTracker: opts?.mutationTracker,
@@ -371,6 +374,7 @@ export class PayloadService {
371
374
  else {
372
375
  relatedPrimaryKey = await service.createOne(relatedRecord, {
373
376
  onRevisionCreate: (pk) => revisions.push(pk),
377
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
374
378
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
375
379
  emitEvents: opts?.emitEvents,
376
380
  mutationTracker: opts?.mutationTracker,
@@ -379,7 +383,7 @@ export class PayloadService {
379
383
  // Overwrite the nested object with just the primary key, so the parent level can be saved correctly
380
384
  payload[relation.field] = relatedPrimaryKey;
381
385
  }
382
- return { payload, revisions, nestedActionEvents };
386
+ return { payload, revisions, nestedActionEvents, userIntegrityCheckFlags };
383
387
  }
384
388
  /**
385
389
  * Save/update all nested related m2o items inside the payload
@@ -388,6 +392,7 @@ export class PayloadService {
388
392
  const payload = cloneDeep(data);
389
393
  // All the revisions saved on this level
390
394
  const revisions = [];
395
+ let userIntegrityCheckFlags = UserIntegrityCheckFlag.None;
391
396
  const nestedActionEvents = [];
392
397
  // Many to one relations that exist on the current collection
393
398
  const relations = this.schema.relations.filter((relation) => {
@@ -424,6 +429,7 @@ export class PayloadService {
424
429
  if (Object.keys(fieldsToUpdate).length > 0) {
425
430
  await service.updateOne(relatedPrimaryKey, relatedRecord, {
426
431
  onRevisionCreate: (pk) => revisions.push(pk),
432
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
427
433
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
428
434
  emitEvents: opts?.emitEvents,
429
435
  mutationTracker: opts?.mutationTracker,
@@ -433,6 +439,7 @@ export class PayloadService {
433
439
  else {
434
440
  relatedPrimaryKey = await service.createOne(relatedRecord, {
435
441
  onRevisionCreate: (pk) => revisions.push(pk),
442
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
436
443
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
437
444
  emitEvents: opts?.emitEvents,
438
445
  mutationTracker: opts?.mutationTracker,
@@ -441,13 +448,14 @@ export class PayloadService {
441
448
  // Overwrite the nested object with just the primary key, so the parent level can be saved correctly
442
449
  payload[relation.field] = relatedPrimaryKey;
443
450
  }
444
- return { payload, revisions, nestedActionEvents };
451
+ return { payload, revisions, nestedActionEvents, userIntegrityCheckFlags };
445
452
  }
446
453
  /**
447
454
  * Recursively save/update all nested related o2m items
448
455
  */
449
456
  async processO2M(data, parent, opts) {
450
457
  const revisions = [];
458
+ let userIntegrityCheckFlags = UserIntegrityCheckFlag.None;
451
459
  const nestedActionEvents = [];
452
460
  const relations = this.schema.relations.filter((relation) => {
453
461
  return relation.related_collection === this.collection;
@@ -516,6 +524,7 @@ export class PayloadService {
516
524
  }
517
525
  savedPrimaryKeys.push(...(await service.upsertMany(recordsToUpsert, {
518
526
  onRevisionCreate: (pk) => revisions.push(pk),
527
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
519
528
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
520
529
  emitEvents: opts?.emitEvents,
521
530
  mutationTracker: opts?.mutationTracker,
@@ -540,6 +549,7 @@ export class PayloadService {
540
549
  if (relation.meta.one_deselect_action === 'delete') {
541
550
  // There's no revision for a deletion
542
551
  await service.deleteByQuery(query, {
552
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
543
553
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
544
554
  emitEvents: opts?.emitEvents,
545
555
  mutationTracker: opts?.mutationTracker,
@@ -548,6 +558,7 @@ export class PayloadService {
548
558
  else {
549
559
  await service.updateByQuery(query, { [relation.field]: null }, {
550
560
  onRevisionCreate: (pk) => revisions.push(pk),
561
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
551
562
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
552
563
  emitEvents: opts?.emitEvents,
553
564
  mutationTracker: opts?.mutationTracker,
@@ -590,6 +601,7 @@ export class PayloadService {
590
601
  }
591
602
  await service.createMany(createPayload, {
592
603
  onRevisionCreate: (pk) => revisions.push(pk),
604
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
593
605
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
594
606
  emitEvents: opts?.emitEvents,
595
607
  mutationTracker: opts?.mutationTracker,
@@ -603,6 +615,7 @@ export class PayloadService {
603
615
  [relation.field]: parent || payload[currentPrimaryKeyField],
604
616
  }, {
605
617
  onRevisionCreate: (pk) => revisions.push(pk),
618
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
606
619
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
607
620
  emitEvents: opts?.emitEvents,
608
621
  mutationTracker: opts?.mutationTracker,
@@ -628,6 +641,7 @@ export class PayloadService {
628
641
  };
629
642
  if (relation.meta.one_deselect_action === 'delete') {
630
643
  await service.deleteByQuery(query, {
644
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
631
645
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
632
646
  emitEvents: opts?.emitEvents,
633
647
  mutationTracker: opts?.mutationTracker,
@@ -636,6 +650,7 @@ export class PayloadService {
636
650
  else {
637
651
  await service.updateByQuery(query, { [relation.field]: null }, {
638
652
  onRevisionCreate: (pk) => revisions.push(pk),
653
+ onRequireUserIntegrityCheck: (flags) => (userIntegrityCheckFlags |= flags),
639
654
  bypassEmitAction: (params) => opts?.bypassEmitAction ? opts.bypassEmitAction(params) : nestedActionEvents.push(params),
640
655
  emitEvents: opts?.emitEvents,
641
656
  mutationTracker: opts?.mutationTracker,
@@ -644,7 +659,7 @@ export class PayloadService {
644
659
  }
645
660
  }
646
661
  }
647
- return { revisions, nestedActionEvents };
662
+ return { revisions, nestedActionEvents, userIntegrityCheckFlags };
648
663
  }
649
664
  /**
650
665
  * Transforms the input partial payload to match the output structure, to have consistency