@strapi/content-manager 5.60.0 → 5.61.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 (256) hide show
  1. package/dist/admin/constants/customOrder.js +14 -0
  2. package/dist/admin/constants/customOrder.js.map +1 -0
  3. package/dist/admin/constants/customOrder.mjs +10 -0
  4. package/dist/admin/constants/customOrder.mjs.map +1 -0
  5. package/dist/admin/hooks/useDocument.js +22 -11
  6. package/dist/admin/hooks/useDocument.js.map +1 -1
  7. package/dist/admin/hooks/useDocument.mjs +22 -11
  8. package/dist/admin/hooks/useDocument.mjs.map +1 -1
  9. package/dist/admin/hooks/useDocumentLayout.js.map +1 -1
  10. package/dist/admin/hooks/useDocumentLayout.mjs.map +1 -1
  11. package/dist/admin/hooks/useHasBeenNearViewport.js +70 -0
  12. package/dist/admin/hooks/useHasBeenNearViewport.js.map +1 -0
  13. package/dist/admin/hooks/useHasBeenNearViewport.mjs +46 -0
  14. package/dist/admin/hooks/useHasBeenNearViewport.mjs.map +1 -0
  15. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.js +102 -32
  16. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.js.map +1 -1
  17. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.mjs +104 -35
  18. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.mjs.map +1 -1
  19. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.js +20 -17
  20. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.js.map +1 -1
  21. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.mjs +20 -17
  22. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/Blocks/List.mjs.map +1 -1
  23. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js +15 -1
  24. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.js.map +1 -1
  25. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs +17 -3
  26. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/BlocksContent.mjs.map +1 -1
  27. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.js +21 -0
  28. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.js.map +1 -1
  29. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.mjs +22 -1
  30. package/dist/admin/pages/EditView/components/FormInputs/BlocksInput/plugins/withStrapiSchema.mjs.map +1 -1
  31. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js +4 -7
  32. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.js.map +1 -1
  33. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs +4 -7
  34. package/dist/admin/pages/EditView/components/FormInputs/Relations/RelationModal.mjs.map +1 -1
  35. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js +46 -10
  36. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.js.map +1 -1
  37. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs +47 -11
  38. package/dist/admin/pages/EditView/components/FormInputs/Relations/Relations.mjs.map +1 -1
  39. package/dist/admin/pages/EditView/components/FormLayout.js +201 -51
  40. package/dist/admin/pages/EditView/components/FormLayout.js.map +1 -1
  41. package/dist/admin/pages/EditView/components/FormLayout.mjs +202 -53
  42. package/dist/admin/pages/EditView/components/FormLayout.mjs.map +1 -1
  43. package/dist/admin/pages/ListConfiguration/components/Settings.js +31 -2
  44. package/dist/admin/pages/ListConfiguration/components/Settings.js.map +1 -1
  45. package/dist/admin/pages/ListConfiguration/components/Settings.mjs +31 -2
  46. package/dist/admin/pages/ListConfiguration/components/Settings.mjs.map +1 -1
  47. package/dist/admin/pages/ListView/ListViewPage.js +196 -132
  48. package/dist/admin/pages/ListView/ListViewPage.js.map +1 -1
  49. package/dist/admin/pages/ListView/ListViewPage.mjs +196 -132
  50. package/dist/admin/pages/ListView/ListViewPage.mjs.map +1 -1
  51. package/dist/admin/pages/ListView/components/CustomOrder.js +571 -0
  52. package/dist/admin/pages/ListView/components/CustomOrder.js.map +1 -0
  53. package/dist/admin/pages/ListView/components/CustomOrder.mjs +546 -0
  54. package/dist/admin/pages/ListView/components/CustomOrder.mjs.map +1 -0
  55. package/dist/admin/pages/ListView/components/TableActions.js +4 -1
  56. package/dist/admin/pages/ListView/components/TableActions.js.map +1 -1
  57. package/dist/admin/pages/ListView/components/TableActions.mjs +4 -1
  58. package/dist/admin/pages/ListView/components/TableActions.mjs.map +1 -1
  59. package/dist/admin/services/documents.js +22 -1
  60. package/dist/admin/services/documents.js.map +1 -1
  61. package/dist/admin/services/documents.mjs +21 -2
  62. package/dist/admin/services/documents.mjs.map +1 -1
  63. package/dist/admin/src/constants/customOrder.d.ts +8 -0
  64. package/dist/admin/src/exports.d.ts +1 -1
  65. package/dist/admin/src/hooks/useDocument.d.ts +4 -0
  66. package/dist/admin/src/hooks/useDocumentLayout.d.ts +6 -2
  67. package/dist/admin/src/hooks/useHasBeenNearViewport.d.ts +13 -0
  68. package/dist/admin/src/pages/EditView/components/FormInputs/BlocksInput/Blocks/Image.d.ts +13 -1
  69. package/dist/admin/src/pages/EditView/components/FormLayout.d.ts +11 -1
  70. package/dist/admin/src/pages/ListView/components/CustomOrder.d.ts +72 -0
  71. package/dist/admin/src/services/documents.d.ts +10 -2
  72. package/dist/admin/translations/en.json.js +9 -0
  73. package/dist/admin/translations/en.json.js.map +1 -1
  74. package/dist/admin/translations/en.json.mjs +9 -0
  75. package/dist/admin/translations/en.json.mjs.map +1 -1
  76. package/dist/server/audit-logs.js +36 -0
  77. package/dist/server/audit-logs.js.map +1 -0
  78. package/dist/server/audit-logs.mjs +33 -0
  79. package/dist/server/audit-logs.mjs.map +1 -0
  80. package/dist/server/bootstrap.js +8 -0
  81. package/dist/server/bootstrap.js.map +1 -1
  82. package/dist/server/bootstrap.mjs +8 -0
  83. package/dist/server/bootstrap.mjs.map +1 -1
  84. package/dist/server/controllers/collection-types.js +8 -0
  85. package/dist/server/controllers/collection-types.js.map +1 -1
  86. package/dist/server/controllers/collection-types.mjs +8 -0
  87. package/dist/server/controllers/collection-types.mjs.map +1 -1
  88. package/dist/server/controllers/content-types.js +15 -0
  89. package/dist/server/controllers/content-types.js.map +1 -1
  90. package/dist/server/controllers/content-types.mjs +16 -1
  91. package/dist/server/controllers/content-types.mjs.map +1 -1
  92. package/dist/server/controllers/index.js +3 -1
  93. package/dist/server/controllers/index.js.map +1 -1
  94. package/dist/server/controllers/index.mjs +2 -0
  95. package/dist/server/controllers/index.mjs.map +1 -1
  96. package/dist/server/controllers/relations.js +5 -1
  97. package/dist/server/controllers/relations.js.map +1 -1
  98. package/dist/server/controllers/relations.mjs +5 -1
  99. package/dist/server/controllers/relations.mjs.map +1 -1
  100. package/dist/server/controllers/single-types.js +10 -1
  101. package/dist/server/controllers/single-types.js.map +1 -1
  102. package/dist/server/controllers/single-types.mjs +10 -1
  103. package/dist/server/controllers/single-types.mjs.map +1 -1
  104. package/dist/server/controllers/validation/model-configuration.js +2 -1
  105. package/dist/server/controllers/validation/model-configuration.js.map +1 -1
  106. package/dist/server/controllers/validation/model-configuration.mjs +2 -1
  107. package/dist/server/controllers/validation/model-configuration.mjs.map +1 -1
  108. package/dist/server/custom-order/constants.js +21 -0
  109. package/dist/server/custom-order/constants.js.map +1 -0
  110. package/dist/server/custom-order/constants.mjs +17 -0
  111. package/dist/server/custom-order/constants.mjs.map +1 -0
  112. package/dist/server/custom-order/controllers/custom-order.js +77 -0
  113. package/dist/server/custom-order/controllers/custom-order.js.map +1 -0
  114. package/dist/server/custom-order/controllers/custom-order.mjs +75 -0
  115. package/dist/server/custom-order/controllers/custom-order.mjs.map +1 -0
  116. package/dist/server/custom-order/controllers/index.js +10 -0
  117. package/dist/server/custom-order/controllers/index.js.map +1 -0
  118. package/dist/server/custom-order/controllers/index.mjs +8 -0
  119. package/dist/server/custom-order/controllers/index.mjs.map +1 -0
  120. package/dist/server/custom-order/index.js +71 -0
  121. package/dist/server/custom-order/index.js.map +1 -0
  122. package/dist/server/custom-order/index.mjs +69 -0
  123. package/dist/server/custom-order/index.mjs.map +1 -0
  124. package/dist/server/custom-order/routes/custom-order.js +38 -0
  125. package/dist/server/custom-order/routes/custom-order.js.map +1 -0
  126. package/dist/server/custom-order/routes/custom-order.mjs +36 -0
  127. package/dist/server/custom-order/routes/custom-order.mjs.map +1 -0
  128. package/dist/server/custom-order/routes/index.js +13 -0
  129. package/dist/server/custom-order/routes/index.js.map +1 -0
  130. package/dist/server/custom-order/routes/index.mjs +11 -0
  131. package/dist/server/custom-order/routes/index.mjs.map +1 -0
  132. package/dist/server/custom-order/services/custom-order.js +286 -0
  133. package/dist/server/custom-order/services/custom-order.js.map +1 -0
  134. package/dist/server/custom-order/services/custom-order.mjs +284 -0
  135. package/dist/server/custom-order/services/custom-order.mjs.map +1 -0
  136. package/dist/server/custom-order/services/document-middleware.js +83 -0
  137. package/dist/server/custom-order/services/document-middleware.js.map +1 -0
  138. package/dist/server/custom-order/services/document-middleware.mjs +81 -0
  139. package/dist/server/custom-order/services/document-middleware.mjs.map +1 -0
  140. package/dist/server/custom-order/services/index.js +10 -0
  141. package/dist/server/custom-order/services/index.js.map +1 -0
  142. package/dist/server/custom-order/services/index.mjs +8 -0
  143. package/dist/server/custom-order/services/index.mjs.map +1 -0
  144. package/dist/server/custom-order/services/utils.js +152 -0
  145. package/dist/server/custom-order/services/utils.js.map +1 -0
  146. package/dist/server/custom-order/services/utils.mjs +148 -0
  147. package/dist/server/custom-order/services/utils.mjs.map +1 -0
  148. package/dist/server/custom-order/utils.js +13 -0
  149. package/dist/server/custom-order/utils.js.map +1 -0
  150. package/dist/server/custom-order/utils.mjs +10 -0
  151. package/dist/server/custom-order/utils.mjs.map +1 -0
  152. package/dist/server/history/constants.js +2 -1
  153. package/dist/server/history/constants.js.map +1 -1
  154. package/dist/server/history/constants.mjs +2 -1
  155. package/dist/server/history/constants.mjs.map +1 -1
  156. package/dist/server/history/controllers/history-version.js +6 -2
  157. package/dist/server/history/controllers/history-version.js.map +1 -1
  158. package/dist/server/history/controllers/history-version.mjs +6 -2
  159. package/dist/server/history/controllers/history-version.mjs.map +1 -1
  160. package/dist/server/history/services/history.js +23 -6
  161. package/dist/server/history/services/history.js.map +1 -1
  162. package/dist/server/history/services/history.mjs +24 -7
  163. package/dist/server/history/services/history.mjs.map +1 -1
  164. package/dist/server/history/services/lifecycles.js +2 -16
  165. package/dist/server/history/services/lifecycles.js.map +1 -1
  166. package/dist/server/history/services/lifecycles.mjs +2 -16
  167. package/dist/server/history/services/lifecycles.mjs.map +1 -1
  168. package/dist/server/register.js +4 -0
  169. package/dist/server/register.js.map +1 -1
  170. package/dist/server/register.mjs +4 -0
  171. package/dist/server/register.mjs.map +1 -1
  172. package/dist/server/routes/index.js +3 -1
  173. package/dist/server/routes/index.js.map +1 -1
  174. package/dist/server/routes/index.mjs +2 -0
  175. package/dist/server/routes/index.mjs.map +1 -1
  176. package/dist/server/services/content-types.js +2 -2
  177. package/dist/server/services/content-types.js.map +1 -1
  178. package/dist/server/services/content-types.mjs +2 -2
  179. package/dist/server/services/content-types.mjs.map +1 -1
  180. package/dist/server/services/document-manager.js +13 -0
  181. package/dist/server/services/document-manager.js.map +1 -1
  182. package/dist/server/services/document-manager.mjs +14 -1
  183. package/dist/server/services/document-manager.mjs.map +1 -1
  184. package/dist/server/services/index.js +3 -1
  185. package/dist/server/services/index.js.map +1 -1
  186. package/dist/server/services/index.mjs +2 -0
  187. package/dist/server/services/index.mjs.map +1 -1
  188. package/dist/server/services/utils/configuration/settings.js +2 -1
  189. package/dist/server/services/utils/configuration/settings.js.map +1 -1
  190. package/dist/server/services/utils/configuration/settings.mjs +2 -1
  191. package/dist/server/services/utils/configuration/settings.mjs.map +1 -1
  192. package/dist/server/src/audit-logs.d.ts +40 -0
  193. package/dist/server/src/audit-logs.d.ts.map +1 -0
  194. package/dist/server/src/bootstrap.d.ts.map +1 -1
  195. package/dist/server/src/controllers/collection-types.d.ts +1 -1
  196. package/dist/server/src/controllers/collection-types.d.ts.map +1 -1
  197. package/dist/server/src/controllers/content-types.d.ts.map +1 -1
  198. package/dist/server/src/controllers/index.d.ts +21 -3
  199. package/dist/server/src/controllers/index.d.ts.map +1 -1
  200. package/dist/server/src/controllers/relations.d.ts +19 -1
  201. package/dist/server/src/controllers/relations.d.ts.map +1 -1
  202. package/dist/server/src/controllers/single-types.d.ts +1 -1
  203. package/dist/server/src/controllers/single-types.d.ts.map +1 -1
  204. package/dist/server/src/custom-order/constants.d.ts +18 -0
  205. package/dist/server/src/custom-order/constants.d.ts.map +1 -0
  206. package/dist/server/src/custom-order/controllers/custom-order.d.ts +8 -0
  207. package/dist/server/src/custom-order/controllers/custom-order.d.ts.map +1 -0
  208. package/dist/server/src/custom-order/controllers/index.d.ts +3 -0
  209. package/dist/server/src/custom-order/controllers/index.d.ts.map +1 -0
  210. package/dist/server/src/custom-order/index.d.ts +8 -0
  211. package/dist/server/src/custom-order/index.d.ts.map +1 -0
  212. package/dist/server/src/custom-order/routes/custom-order.d.ts +4 -0
  213. package/dist/server/src/custom-order/routes/custom-order.d.ts.map +1 -0
  214. package/dist/server/src/custom-order/routes/index.d.ts +10 -0
  215. package/dist/server/src/custom-order/routes/index.d.ts.map +1 -0
  216. package/dist/server/src/custom-order/services/custom-order.d.ts +43 -0
  217. package/dist/server/src/custom-order/services/custom-order.d.ts.map +1 -0
  218. package/dist/server/src/custom-order/services/document-middleware.d.ts +16 -0
  219. package/dist/server/src/custom-order/services/document-middleware.d.ts.map +1 -0
  220. package/dist/server/src/custom-order/services/index.d.ts +24 -0
  221. package/dist/server/src/custom-order/services/index.d.ts.map +1 -0
  222. package/dist/server/src/custom-order/services/utils.d.ts +34 -0
  223. package/dist/server/src/custom-order/services/utils.d.ts.map +1 -0
  224. package/dist/server/src/custom-order/utils.d.ts +6 -0
  225. package/dist/server/src/custom-order/utils.d.ts.map +1 -0
  226. package/dist/server/src/history/constants.d.ts.map +1 -1
  227. package/dist/server/src/history/controllers/history-version.d.ts.map +1 -1
  228. package/dist/server/src/history/services/history.d.ts.map +1 -1
  229. package/dist/server/src/history/services/lifecycles.d.ts.map +1 -1
  230. package/dist/server/src/index.d.ts +22 -6
  231. package/dist/server/src/index.d.ts.map +1 -1
  232. package/dist/server/src/register.d.ts.map +1 -1
  233. package/dist/server/src/routes/index.d.ts.map +1 -1
  234. package/dist/server/src/services/content-types.d.ts +1 -3
  235. package/dist/server/src/services/content-types.d.ts.map +1 -1
  236. package/dist/server/src/services/document-manager.d.ts.map +1 -1
  237. package/dist/server/src/services/index.d.ts +1 -3
  238. package/dist/server/src/services/index.d.ts.map +1 -1
  239. package/dist/server/src/services/utils/configuration/settings.d.ts.map +1 -1
  240. package/dist/shared/contracts/content-types.d.ts +5 -0
  241. package/dist/shared/contracts/content-types.d.ts.map +1 -1
  242. package/dist/shared/contracts/custom-order.d.ts +33 -0
  243. package/dist/shared/contracts/custom-order.d.ts.map +1 -0
  244. package/dist/shared/contracts/custom-order.js +3 -0
  245. package/dist/shared/contracts/custom-order.js.map +1 -0
  246. package/dist/shared/contracts/custom-order.mjs +2 -0
  247. package/dist/shared/contracts/custom-order.mjs.map +1 -0
  248. package/dist/shared/contracts/history-versions.d.ts +0 -1
  249. package/dist/shared/contracts/history-versions.d.ts.map +1 -1
  250. package/dist/shared/contracts/index.d.ts +1 -0
  251. package/dist/shared/contracts/index.d.ts.map +1 -1
  252. package/dist/shared/contracts/index.js +2 -0
  253. package/dist/shared/contracts/index.js.map +1 -1
  254. package/dist/shared/contracts/index.mjs +2 -0
  255. package/dist/shared/contracts/index.mjs.map +1 -1
  256. package/package.json +8 -8
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ var constants = require('../constants.js');
4
+
5
+ /**
6
+ * Works out how to place a document right before or after an anchor document.
7
+ *
8
+ * Positions are unique integers that may have gaps. Only the documents sitting between
9
+ * the moved document and the anchor are shifted, by one, towards the slot the moved
10
+ * document leaves behind.
11
+ *
12
+ * @returns null when the document is already in place.
13
+ */ const computeMove = ({ from, anchor, placement })=>{
14
+ if (from === anchor) {
15
+ // Two documents should never share a position. If it happens anyway (e.g. concurrent
16
+ // writes), open a slot next to the anchor instead of relying on the slot left behind.
17
+ return placement === 'before' ? {
18
+ position: anchor,
19
+ shift: {
20
+ from: anchor,
21
+ to: null,
22
+ by: 1
23
+ }
24
+ } : {
25
+ position: anchor + 1,
26
+ shift: {
27
+ from: anchor + 1,
28
+ to: null,
29
+ by: 1
30
+ }
31
+ };
32
+ }
33
+ const movingDown = from < anchor;
34
+ if (placement === 'before') {
35
+ if (movingDown) {
36
+ const position = anchor - 1;
37
+ return from === position ? null : {
38
+ position,
39
+ shift: {
40
+ from: from + 1,
41
+ to: position,
42
+ by: -1
43
+ }
44
+ };
45
+ }
46
+ return {
47
+ position: anchor,
48
+ shift: {
49
+ from: anchor,
50
+ to: from - 1,
51
+ by: 1
52
+ }
53
+ };
54
+ }
55
+ if (movingDown) {
56
+ return {
57
+ position: anchor,
58
+ shift: {
59
+ from: from + 1,
60
+ to: anchor,
61
+ by: -1
62
+ }
63
+ };
64
+ }
65
+ const position = anchor + 1;
66
+ return from === position ? null : {
67
+ position,
68
+ shift: {
69
+ from: position,
70
+ to: from - 1,
71
+ by: 1
72
+ }
73
+ };
74
+ };
75
+ /**
76
+ * Whether a Document Service `sort` param is absent or empty.
77
+ */ const isEmptySort = (sort)=>{
78
+ if (sort === undefined || sort === null || sort === '') {
79
+ return true;
80
+ }
81
+ if (Array.isArray(sort)) {
82
+ return sort.length === 0;
83
+ }
84
+ if (typeof sort === 'object') {
85
+ return Object.keys(sort).length === 0;
86
+ }
87
+ return false;
88
+ };
89
+ const parseSortEntry = (entry)=>{
90
+ if (typeof entry === 'string') {
91
+ const [field, direction] = entry.trim().split(':');
92
+ return field ? {
93
+ field,
94
+ direction: toDirection(direction)
95
+ } : null;
96
+ }
97
+ if (entry && typeof entry === 'object') {
98
+ const fields = Object.keys(entry);
99
+ const field = fields[fields.length - 1];
100
+ return field ? {
101
+ field,
102
+ direction: toDirection(entry[field])
103
+ } : null;
104
+ }
105
+ return null;
106
+ };
107
+ const toDirection = (value)=>typeof value === 'string' && value.toLowerCase() === 'desc' ? 'desc' : 'asc';
108
+ /**
109
+ * Completes a sort that ends on the position with the `id` of the row.
110
+ *
111
+ * Positions alone don't make a total order: every row of a document shares its position,
112
+ * rows written outside of the Document Service have none, and without a tie-breaker each
113
+ * database would place those rows differently, and not always on the same page. Among
114
+ * equals the most recent row comes first, like new entries do.
115
+ *
116
+ * @returns the same `sort` when it does not end on the position.
117
+ */ const toSortEntries = (sort)=>{
118
+ if (typeof sort === 'string') {
119
+ return sort.split(',');
120
+ }
121
+ return Array.isArray(sort) ? sort : [
122
+ sort
123
+ ];
124
+ };
125
+ const withTieBreaker = (sort)=>{
126
+ const entries = toSortEntries(sort);
127
+ const last = parseSortEntry(entries[entries.length - 1]);
128
+ if (!last || last.field !== constants.POSITION_ATTRIBUTE) {
129
+ return sort;
130
+ }
131
+ const tieBreaker = last.direction === 'asc' ? 'desc' : 'asc';
132
+ if (typeof sort === 'string') {
133
+ return `${sort},id:${tieBreaker}`;
134
+ }
135
+ if (Array.isArray(sort)) {
136
+ return [
137
+ ...sort,
138
+ typeof entries[entries.length - 1] === 'string' ? `id:${tieBreaker}` : {
139
+ id: tieBreaker
140
+ }
141
+ ];
142
+ }
143
+ return {
144
+ ...sort,
145
+ id: tieBreaker
146
+ };
147
+ };
148
+
149
+ exports.computeMove = computeMove;
150
+ exports.isEmptySort = isEmptySort;
151
+ exports.withTieBreaker = withTieBreaker;
152
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../../../server/src/custom-order/services/utils.ts"],"sourcesContent":["import { POSITION_ATTRIBUTE } from '../constants';\n\nexport type Placement = 'before' | 'after';\n\nexport interface MovePlan {\n /** New position of the moved document. */\n position: number;\n /**\n * Positions to shift to make room, bounds included. `to: null` means every position\n * from `from` upwards.\n */\n shift: { from: number; to: number | null; by: 1 | -1 } | null;\n}\n\n/**\n * Works out how to place a document right before or after an anchor document.\n *\n * Positions are unique integers that may have gaps. Only the documents sitting between\n * the moved document and the anchor are shifted, by one, towards the slot the moved\n * document leaves behind.\n *\n * @returns null when the document is already in place.\n */\nexport const computeMove = ({\n from,\n anchor,\n placement,\n}: {\n from: number;\n anchor: number;\n placement: Placement;\n}): MovePlan | null => {\n if (from === anchor) {\n // Two documents should never share a position. If it happens anyway (e.g. concurrent\n // writes), open a slot next to the anchor instead of relying on the slot left behind.\n return placement === 'before'\n ? { position: anchor, shift: { from: anchor, to: null, by: 1 } }\n : { position: anchor + 1, shift: { from: anchor + 1, to: null, by: 1 } };\n }\n\n const movingDown = from < anchor;\n\n if (placement === 'before') {\n if (movingDown) {\n const position = anchor - 1;\n\n return from === position\n ? null\n : { position, shift: { from: from + 1, to: position, by: -1 } };\n }\n\n return { position: anchor, shift: { from: anchor, to: from - 1, by: 1 } };\n }\n\n if (movingDown) {\n return { position: anchor, shift: { from: from + 1, to: anchor, by: -1 } };\n }\n\n const position = anchor + 1;\n\n return from === position ? null : { position, shift: { from: position, to: from - 1, by: 1 } };\n};\n\n/**\n * Whether a Document Service `sort` param is absent or empty.\n */\nexport const isEmptySort = (sort: unknown): boolean => {\n if (sort === undefined || sort === null || sort === '') {\n return true;\n }\n\n if (Array.isArray(sort)) {\n return sort.length === 0;\n }\n\n if (typeof sort === 'object') {\n return Object.keys(sort).length === 0;\n }\n\n return false;\n};\n\ntype Direction = 'asc' | 'desc';\n\nconst parseSortEntry = (entry: unknown): { field: string; direction: Direction } | null => {\n if (typeof entry === 'string') {\n const [field, direction] = entry.trim().split(':');\n\n return field ? { field, direction: toDirection(direction) } : null;\n }\n\n if (entry && typeof entry === 'object') {\n const fields = Object.keys(entry);\n const field = fields[fields.length - 1];\n\n return field\n ? { field, direction: toDirection((entry as Record<string, unknown>)[field]) }\n : null;\n }\n\n return null;\n};\n\nconst toDirection = (value: unknown): Direction =>\n typeof value === 'string' && value.toLowerCase() === 'desc' ? 'desc' : 'asc';\n\n/**\n * Completes a sort that ends on the position with the `id` of the row.\n *\n * Positions alone don't make a total order: every row of a document shares its position,\n * rows written outside of the Document Service have none, and without a tie-breaker each\n * database would place those rows differently, and not always on the same page. Among\n * equals the most recent row comes first, like new entries do.\n *\n * @returns the same `sort` when it does not end on the position.\n */\nconst toSortEntries = (sort: unknown): unknown[] => {\n if (typeof sort === 'string') {\n return sort.split(',');\n }\n\n return Array.isArray(sort) ? sort : [sort];\n};\n\nexport const withTieBreaker = (sort: unknown): unknown => {\n const entries = toSortEntries(sort);\n const last = parseSortEntry(entries[entries.length - 1]);\n\n if (!last || last.field !== POSITION_ATTRIBUTE) {\n return sort;\n }\n\n const tieBreaker: Direction = last.direction === 'asc' ? 'desc' : 'asc';\n\n if (typeof sort === 'string') {\n return `${sort},id:${tieBreaker}`;\n }\n\n if (Array.isArray(sort)) {\n return [\n ...sort,\n typeof entries[entries.length - 1] === 'string' ? `id:${tieBreaker}` : { id: tieBreaker },\n ];\n }\n\n return { ...(sort as Record<string, unknown>), id: tieBreaker };\n};\n"],"names":["computeMove","from","anchor","placement","position","shift","to","by","movingDown","isEmptySort","sort","undefined","Array","isArray","length","Object","keys","parseSortEntry","entry","field","direction","trim","split","toDirection","fields","value","toLowerCase","toSortEntries","withTieBreaker","entries","last","POSITION_ATTRIBUTE","tieBreaker","id"],"mappings":"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"}
@@ -0,0 +1,148 @@
1
+ import { POSITION_ATTRIBUTE } from '../constants.mjs';
2
+
3
+ /**
4
+ * Works out how to place a document right before or after an anchor document.
5
+ *
6
+ * Positions are unique integers that may have gaps. Only the documents sitting between
7
+ * the moved document and the anchor are shifted, by one, towards the slot the moved
8
+ * document leaves behind.
9
+ *
10
+ * @returns null when the document is already in place.
11
+ */ const computeMove = ({ from, anchor, placement })=>{
12
+ if (from === anchor) {
13
+ // Two documents should never share a position. If it happens anyway (e.g. concurrent
14
+ // writes), open a slot next to the anchor instead of relying on the slot left behind.
15
+ return placement === 'before' ? {
16
+ position: anchor,
17
+ shift: {
18
+ from: anchor,
19
+ to: null,
20
+ by: 1
21
+ }
22
+ } : {
23
+ position: anchor + 1,
24
+ shift: {
25
+ from: anchor + 1,
26
+ to: null,
27
+ by: 1
28
+ }
29
+ };
30
+ }
31
+ const movingDown = from < anchor;
32
+ if (placement === 'before') {
33
+ if (movingDown) {
34
+ const position = anchor - 1;
35
+ return from === position ? null : {
36
+ position,
37
+ shift: {
38
+ from: from + 1,
39
+ to: position,
40
+ by: -1
41
+ }
42
+ };
43
+ }
44
+ return {
45
+ position: anchor,
46
+ shift: {
47
+ from: anchor,
48
+ to: from - 1,
49
+ by: 1
50
+ }
51
+ };
52
+ }
53
+ if (movingDown) {
54
+ return {
55
+ position: anchor,
56
+ shift: {
57
+ from: from + 1,
58
+ to: anchor,
59
+ by: -1
60
+ }
61
+ };
62
+ }
63
+ const position = anchor + 1;
64
+ return from === position ? null : {
65
+ position,
66
+ shift: {
67
+ from: position,
68
+ to: from - 1,
69
+ by: 1
70
+ }
71
+ };
72
+ };
73
+ /**
74
+ * Whether a Document Service `sort` param is absent or empty.
75
+ */ const isEmptySort = (sort)=>{
76
+ if (sort === undefined || sort === null || sort === '') {
77
+ return true;
78
+ }
79
+ if (Array.isArray(sort)) {
80
+ return sort.length === 0;
81
+ }
82
+ if (typeof sort === 'object') {
83
+ return Object.keys(sort).length === 0;
84
+ }
85
+ return false;
86
+ };
87
+ const parseSortEntry = (entry)=>{
88
+ if (typeof entry === 'string') {
89
+ const [field, direction] = entry.trim().split(':');
90
+ return field ? {
91
+ field,
92
+ direction: toDirection(direction)
93
+ } : null;
94
+ }
95
+ if (entry && typeof entry === 'object') {
96
+ const fields = Object.keys(entry);
97
+ const field = fields[fields.length - 1];
98
+ return field ? {
99
+ field,
100
+ direction: toDirection(entry[field])
101
+ } : null;
102
+ }
103
+ return null;
104
+ };
105
+ const toDirection = (value)=>typeof value === 'string' && value.toLowerCase() === 'desc' ? 'desc' : 'asc';
106
+ /**
107
+ * Completes a sort that ends on the position with the `id` of the row.
108
+ *
109
+ * Positions alone don't make a total order: every row of a document shares its position,
110
+ * rows written outside of the Document Service have none, and without a tie-breaker each
111
+ * database would place those rows differently, and not always on the same page. Among
112
+ * equals the most recent row comes first, like new entries do.
113
+ *
114
+ * @returns the same `sort` when it does not end on the position.
115
+ */ const toSortEntries = (sort)=>{
116
+ if (typeof sort === 'string') {
117
+ return sort.split(',');
118
+ }
119
+ return Array.isArray(sort) ? sort : [
120
+ sort
121
+ ];
122
+ };
123
+ const withTieBreaker = (sort)=>{
124
+ const entries = toSortEntries(sort);
125
+ const last = parseSortEntry(entries[entries.length - 1]);
126
+ if (!last || last.field !== POSITION_ATTRIBUTE) {
127
+ return sort;
128
+ }
129
+ const tieBreaker = last.direction === 'asc' ? 'desc' : 'asc';
130
+ if (typeof sort === 'string') {
131
+ return `${sort},id:${tieBreaker}`;
132
+ }
133
+ if (Array.isArray(sort)) {
134
+ return [
135
+ ...sort,
136
+ typeof entries[entries.length - 1] === 'string' ? `id:${tieBreaker}` : {
137
+ id: tieBreaker
138
+ }
139
+ ];
140
+ }
141
+ return {
142
+ ...sort,
143
+ id: tieBreaker
144
+ };
145
+ };
146
+
147
+ export { computeMove, isEmptySort, withTieBreaker };
148
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","sources":["../../../../server/src/custom-order/services/utils.ts"],"sourcesContent":["import { POSITION_ATTRIBUTE } from '../constants';\n\nexport type Placement = 'before' | 'after';\n\nexport interface MovePlan {\n /** New position of the moved document. */\n position: number;\n /**\n * Positions to shift to make room, bounds included. `to: null` means every position\n * from `from` upwards.\n */\n shift: { from: number; to: number | null; by: 1 | -1 } | null;\n}\n\n/**\n * Works out how to place a document right before or after an anchor document.\n *\n * Positions are unique integers that may have gaps. Only the documents sitting between\n * the moved document and the anchor are shifted, by one, towards the slot the moved\n * document leaves behind.\n *\n * @returns null when the document is already in place.\n */\nexport const computeMove = ({\n from,\n anchor,\n placement,\n}: {\n from: number;\n anchor: number;\n placement: Placement;\n}): MovePlan | null => {\n if (from === anchor) {\n // Two documents should never share a position. If it happens anyway (e.g. concurrent\n // writes), open a slot next to the anchor instead of relying on the slot left behind.\n return placement === 'before'\n ? { position: anchor, shift: { from: anchor, to: null, by: 1 } }\n : { position: anchor + 1, shift: { from: anchor + 1, to: null, by: 1 } };\n }\n\n const movingDown = from < anchor;\n\n if (placement === 'before') {\n if (movingDown) {\n const position = anchor - 1;\n\n return from === position\n ? null\n : { position, shift: { from: from + 1, to: position, by: -1 } };\n }\n\n return { position: anchor, shift: { from: anchor, to: from - 1, by: 1 } };\n }\n\n if (movingDown) {\n return { position: anchor, shift: { from: from + 1, to: anchor, by: -1 } };\n }\n\n const position = anchor + 1;\n\n return from === position ? null : { position, shift: { from: position, to: from - 1, by: 1 } };\n};\n\n/**\n * Whether a Document Service `sort` param is absent or empty.\n */\nexport const isEmptySort = (sort: unknown): boolean => {\n if (sort === undefined || sort === null || sort === '') {\n return true;\n }\n\n if (Array.isArray(sort)) {\n return sort.length === 0;\n }\n\n if (typeof sort === 'object') {\n return Object.keys(sort).length === 0;\n }\n\n return false;\n};\n\ntype Direction = 'asc' | 'desc';\n\nconst parseSortEntry = (entry: unknown): { field: string; direction: Direction } | null => {\n if (typeof entry === 'string') {\n const [field, direction] = entry.trim().split(':');\n\n return field ? { field, direction: toDirection(direction) } : null;\n }\n\n if (entry && typeof entry === 'object') {\n const fields = Object.keys(entry);\n const field = fields[fields.length - 1];\n\n return field\n ? { field, direction: toDirection((entry as Record<string, unknown>)[field]) }\n : null;\n }\n\n return null;\n};\n\nconst toDirection = (value: unknown): Direction =>\n typeof value === 'string' && value.toLowerCase() === 'desc' ? 'desc' : 'asc';\n\n/**\n * Completes a sort that ends on the position with the `id` of the row.\n *\n * Positions alone don't make a total order: every row of a document shares its position,\n * rows written outside of the Document Service have none, and without a tie-breaker each\n * database would place those rows differently, and not always on the same page. Among\n * equals the most recent row comes first, like new entries do.\n *\n * @returns the same `sort` when it does not end on the position.\n */\nconst toSortEntries = (sort: unknown): unknown[] => {\n if (typeof sort === 'string') {\n return sort.split(',');\n }\n\n return Array.isArray(sort) ? sort : [sort];\n};\n\nexport const withTieBreaker = (sort: unknown): unknown => {\n const entries = toSortEntries(sort);\n const last = parseSortEntry(entries[entries.length - 1]);\n\n if (!last || last.field !== POSITION_ATTRIBUTE) {\n return sort;\n }\n\n const tieBreaker: Direction = last.direction === 'asc' ? 'desc' : 'asc';\n\n if (typeof sort === 'string') {\n return `${sort},id:${tieBreaker}`;\n }\n\n if (Array.isArray(sort)) {\n return [\n ...sort,\n typeof entries[entries.length - 1] === 'string' ? `id:${tieBreaker}` : { id: tieBreaker },\n ];\n }\n\n return { ...(sort as Record<string, unknown>), id: tieBreaker };\n};\n"],"names":["computeMove","from","anchor","placement","position","shift","to","by","movingDown","isEmptySort","sort","undefined","Array","isArray","length","Object","keys","parseSortEntry","entry","field","direction","trim","split","toDirection","fields","value","toLowerCase","toSortEntries","withTieBreaker","entries","last","POSITION_ATTRIBUTE","tieBreaker","id"],"mappings":";;AAcA;;;;;;;;IASO,MAAMA,WAAAA,GAAc,CAAC,EAC1BC,IAAI,EACJC,MAAM,EACNC,SAAS,EAKV,GAAA;AACC,IAAA,IAAIF,SAASC,MAAAA,EAAQ;;;AAGnB,QAAA,OAAOC,cAAc,QAAA,GACjB;YAAEC,QAAAA,EAAUF,MAAAA;YAAQG,KAAAA,EAAO;gBAAEJ,IAAAA,EAAMC,MAAAA;gBAAQI,EAAAA,EAAI,IAAA;gBAAMC,EAAAA,EAAI;AAAE;SAAE,GAC7D;AAAEH,YAAAA,QAAAA,EAAUF,MAAAA,GAAS,CAAA;YAAGG,KAAAA,EAAO;AAAEJ,gBAAAA,IAAAA,EAAMC,MAAAA,GAAS,CAAA;gBAAGI,EAAAA,EAAI,IAAA;gBAAMC,EAAAA,EAAI;AAAE;AAAE,SAAA;AAC3E,IAAA;AAEA,IAAA,MAAMC,aAAaP,IAAAA,GAAOC,MAAAA;AAE1B,IAAA,IAAIC,cAAc,QAAA,EAAU;AAC1B,QAAA,IAAIK,UAAAA,EAAY;AACd,YAAA,MAAMJ,WAAWF,MAAAA,GAAS,CAAA;YAE1B,OAAOD,IAAAA,KAASG,WACZ,IAAA,GACA;AAAEA,gBAAAA,QAAAA;gBAAUC,KAAAA,EAAO;AAAEJ,oBAAAA,IAAAA,EAAMA,IAAAA,GAAO,CAAA;oBAAGK,EAAAA,EAAIF,QAAAA;AAAUG,oBAAAA,EAAAA,EAAI;AAAG;AAAE,aAAA;AAClE,QAAA;QAEA,OAAO;YAAEH,QAAAA,EAAUF,MAAAA;YAAQG,KAAAA,EAAO;gBAAEJ,IAAAA,EAAMC,MAAAA;AAAQI,gBAAAA,EAAAA,EAAIL,IAAAA,GAAO,CAAA;gBAAGM,EAAAA,EAAI;AAAE;AAAE,SAAA;AAC1E,IAAA;AAEA,IAAA,IAAIC,UAAAA,EAAY;QACd,OAAO;YAAEJ,QAAAA,EAAUF,MAAAA;YAAQG,KAAAA,EAAO;AAAEJ,gBAAAA,IAAAA,EAAMA,IAAAA,GAAO,CAAA;gBAAGK,EAAAA,EAAIJ,MAAAA;AAAQK,gBAAAA,EAAAA,EAAI;AAAG;AAAE,SAAA;AAC3E,IAAA;AAEA,IAAA,MAAMH,WAAWF,MAAAA,GAAS,CAAA;IAE1B,OAAOD,IAAAA,KAASG,WAAW,IAAA,GAAO;AAAEA,QAAAA,QAAAA;QAAUC,KAAAA,EAAO;YAAEJ,IAAAA,EAAMG,QAAAA;AAAUE,YAAAA,EAAAA,EAAIL,IAAAA,GAAO,CAAA;YAAGM,EAAAA,EAAI;AAAE;AAAE,KAAA;AAC/F;AAEA;;IAGO,MAAME,WAAAA,GAAc,CAACC,IAAAA,GAAAA;AAC1B,IAAA,IAAIA,IAAAA,KAASC,SAAAA,IAAaD,IAAAA,KAAS,IAAA,IAAQA,SAAS,EAAA,EAAI;QACtD,OAAO,IAAA;AACT,IAAA;IAEA,IAAIE,KAAAA,CAAMC,OAAO,CAACH,IAAAA,CAAAA,EAAO;QACvB,OAAOA,IAAAA,CAAKI,MAAM,KAAK,CAAA;AACzB,IAAA;IAEA,IAAI,OAAOJ,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAOK,MAAAA,CAAOC,IAAI,CAACN,IAAAA,CAAAA,CAAMI,MAAM,KAAK,CAAA;AACtC,IAAA;IAEA,OAAO,KAAA;AACT;AAIA,MAAMG,iBAAiB,CAACC,KAAAA,GAAAA;IACtB,IAAI,OAAOA,UAAU,QAAA,EAAU;QAC7B,MAAM,CAACC,OAAOC,SAAAA,CAAU,GAAGF,MAAMG,IAAI,EAAA,CAAGC,KAAK,CAAC,GAAA,CAAA;AAE9C,QAAA,OAAOH,KAAAA,GAAQ;AAAEA,YAAAA,KAAAA;AAAOC,YAAAA,SAAAA,EAAWG,WAAAA,CAAYH,SAAAA;SAAW,GAAI,IAAA;AAChE,IAAA;IAEA,IAAIF,KAAAA,IAAS,OAAOA,KAAAA,KAAU,QAAA,EAAU;QACtC,MAAMM,MAAAA,GAAST,MAAAA,CAAOC,IAAI,CAACE,KAAAA,CAAAA;AAC3B,QAAA,MAAMC,QAAQK,MAAM,CAACA,MAAAA,CAAOV,MAAM,GAAG,CAAA,CAAE;AAEvC,QAAA,OAAOK,KAAAA,GACH;AAAEA,YAAAA,KAAAA;AAAOC,YAAAA,SAAAA,EAAWG,WAAAA,CAAaL,KAAiC,CAACC,KAAAA,CAAM;SAAE,GAC3E,IAAA;AACN,IAAA;IAEA,OAAO,IAAA;AACT,CAAA;AAEA,MAAMI,WAAAA,GAAc,CAACE,KAAAA,GACnB,OAAOA,KAAAA,KAAU,YAAYA,KAAAA,CAAMC,WAAW,EAAA,KAAO,MAAA,GAAS,MAAA,GAAS,KAAA;AAEzE;;;;;;;;;IAUA,MAAMC,gBAAgB,CAACjB,IAAAA,GAAAA;IACrB,IAAI,OAAOA,SAAS,QAAA,EAAU;QAC5B,OAAOA,IAAAA,CAAKY,KAAK,CAAC,GAAA,CAAA;AACpB,IAAA;AAEA,IAAA,OAAOV,KAAAA,CAAMC,OAAO,CAACH,IAAAA,CAAAA,GAAQA,IAAAA,GAAO;AAACA,QAAAA;AAAK,KAAA;AAC5C,CAAA;AAEO,MAAMkB,iBAAiB,CAAClB,IAAAA,GAAAA;AAC7B,IAAA,MAAMmB,UAAUF,aAAAA,CAAcjB,IAAAA,CAAAA;AAC9B,IAAA,MAAMoB,OAAOb,cAAAA,CAAeY,OAAO,CAACA,OAAAA,CAAQf,MAAM,GAAG,CAAA,CAAE,CAAA;AAEvD,IAAA,IAAI,CAACgB,IAAAA,IAAQA,IAAAA,CAAKX,KAAK,KAAKY,kBAAAA,EAAoB;QAC9C,OAAOrB,IAAAA;AACT,IAAA;AAEA,IAAA,MAAMsB,UAAAA,GAAwBF,IAAAA,CAAKV,SAAS,KAAK,QAAQ,MAAA,GAAS,KAAA;IAElE,IAAI,OAAOV,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAO,CAAA,EAAGA,IAAAA,CAAK,IAAI,EAAEsB,UAAAA,CAAAA,CAAY;AACnC,IAAA;IAEA,IAAIpB,KAAAA,CAAMC,OAAO,CAACH,IAAAA,CAAAA,EAAO;QACvB,OAAO;AACFA,YAAAA,GAAAA,IAAAA;AACH,YAAA,OAAOmB,OAAO,CAACA,OAAAA,CAAQf,MAAM,GAAG,CAAA,CAAE,KAAK,QAAA,GAAW,CAAC,GAAG,EAAEkB,UAAAA,CAAAA,CAAY,GAAG;gBAAEC,EAAAA,EAAID;AAAW;AACzF,SAAA;AACH,IAAA;IAEA,OAAO;AAAE,QAAA,GAAItB,IAAI;QAA8BuB,EAAAA,EAAID;AAAW,KAAA;AAChE;;;;"}
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ var constants = require('./constants.js');
4
+
5
+ function getService(strapi, name) {
6
+ // Cast is needed because the return type of strapi.service is too vague
7
+ return strapi.service(`plugin::content-manager.${name}`);
8
+ }
9
+ const isFeatureEnabled = (strapi)=>strapi.features.future.isEnabled(constants.FUTURE_FLAG);
10
+
11
+ exports.getService = getService;
12
+ exports.isFeatureEnabled = isFeatureEnabled;
13
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sources":["../../../server/src/custom-order/utils.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { FUTURE_FLAG } from './constants';\n\ntype CustomOrderServices = typeof import('./services').services;\n\nfunction getService<T extends keyof CustomOrderServices>(strapi: Core.Strapi, name: T) {\n // Cast is needed because the return type of strapi.service is too vague\n return strapi.service(`plugin::content-manager.${name}`) as ReturnType<CustomOrderServices[T]>;\n}\n\nconst isFeatureEnabled = (strapi: Core.Strapi) => strapi.features.future.isEnabled(FUTURE_FLAG);\n\nexport { getService, isFeatureEnabled };\n"],"names":["getService","strapi","name","service","isFeatureEnabled","features","future","isEnabled","FUTURE_FLAG"],"mappings":";;;;AAMA,SAASA,UAAAA,CAAgDC,MAAmB,EAAEC,IAAO,EAAA;;AAEnF,IAAA,OAAOD,OAAOE,OAAO,CAAC,CAAC,wBAAwB,EAAED,IAAAA,CAAAA,CAAM,CAAA;AACzD;AAEA,MAAME,gBAAAA,GAAmB,CAACH,MAAAA,GAAwBA,MAAAA,CAAOI,QAAQ,CAACC,MAAM,CAACC,SAAS,CAACC,qBAAAA;;;;;"}
@@ -0,0 +1,10 @@
1
+ import { FUTURE_FLAG } from './constants.mjs';
2
+
3
+ function getService(strapi, name) {
4
+ // Cast is needed because the return type of strapi.service is too vague
5
+ return strapi.service(`plugin::content-manager.${name}`);
6
+ }
7
+ const isFeatureEnabled = (strapi)=>strapi.features.future.isEnabled(FUTURE_FLAG);
8
+
9
+ export { getService, isFeatureEnabled };
10
+ //# sourceMappingURL=utils.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.mjs","sources":["../../../server/src/custom-order/utils.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { FUTURE_FLAG } from './constants';\n\ntype CustomOrderServices = typeof import('./services').services;\n\nfunction getService<T extends keyof CustomOrderServices>(strapi: Core.Strapi, name: T) {\n // Cast is needed because the return type of strapi.service is too vague\n return strapi.service(`plugin::content-manager.${name}`) as ReturnType<CustomOrderServices[T]>;\n}\n\nconst isFeatureEnabled = (strapi: Core.Strapi) => strapi.features.future.isEnabled(FUTURE_FLAG);\n\nexport { getService, isFeatureEnabled };\n"],"names":["getService","strapi","name","service","isFeatureEnabled","features","future","isEnabled","FUTURE_FLAG"],"mappings":";;AAMA,SAASA,UAAAA,CAAgDC,MAAmB,EAAEC,IAAO,EAAA;;AAEnF,IAAA,OAAOD,OAAOE,OAAO,CAAC,CAAC,wBAAwB,EAAED,IAAAA,CAAAA,CAAM,CAAA;AACzD;AAEA,MAAME,gBAAAA,GAAmB,CAACH,MAAAA,GAAwBA,MAAAA,CAAOI,QAAQ,CAACC,MAAM,CAACC,SAAS,CAACC,WAAAA;;;;"}
@@ -8,7 +8,8 @@ const FIELDS_TO_IGNORE = [
8
8
  'createdBy',
9
9
  'updatedBy',
10
10
  'strapi_stage',
11
- 'strapi_assignee'
11
+ 'strapi_assignee',
12
+ 'strapi_position'
12
13
  ];
13
14
 
14
15
  exports.FIELDS_TO_IGNORE = FIELDS_TO_IGNORE;
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sources":["../../../server/src/history/constants.ts"],"sourcesContent":["export const HISTORY_VERSION_UID = 'plugin::content-manager.history-version';\nexport const FIELDS_TO_IGNORE = [\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'createdBy',\n 'updatedBy',\n 'strapi_stage',\n 'strapi_assignee',\n];\n"],"names":["HISTORY_VERSION_UID","FIELDS_TO_IGNORE"],"mappings":";;AAAO,MAAMA,sBAAsB;MACtBC,gBAAAA,GAAmB;AAC9B,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,aAAA;AACA,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,cAAA;AACA,IAAA;;;;;;"}
1
+ {"version":3,"file":"constants.js","sources":["../../../server/src/history/constants.ts"],"sourcesContent":["export const HISTORY_VERSION_UID = 'plugin::content-manager.history-version';\nexport const FIELDS_TO_IGNORE = [\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'createdBy',\n 'updatedBy',\n 'strapi_stage',\n 'strapi_assignee',\n 'strapi_position',\n];\n"],"names":["HISTORY_VERSION_UID","FIELDS_TO_IGNORE"],"mappings":";;AAAO,MAAMA,sBAAsB;MACtBC,gBAAAA,GAAmB;AAC9B,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,aAAA;AACA,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,cAAA;AACA,IAAA,iBAAA;AACA,IAAA;;;;;;"}
@@ -6,7 +6,8 @@ const FIELDS_TO_IGNORE = [
6
6
  'createdBy',
7
7
  'updatedBy',
8
8
  'strapi_stage',
9
- 'strapi_assignee'
9
+ 'strapi_assignee',
10
+ 'strapi_position'
10
11
  ];
11
12
 
12
13
  export { FIELDS_TO_IGNORE, HISTORY_VERSION_UID };
@@ -1 +1 @@
1
- {"version":3,"file":"constants.mjs","sources":["../../../server/src/history/constants.ts"],"sourcesContent":["export const HISTORY_VERSION_UID = 'plugin::content-manager.history-version';\nexport const FIELDS_TO_IGNORE = [\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'createdBy',\n 'updatedBy',\n 'strapi_stage',\n 'strapi_assignee',\n];\n"],"names":["HISTORY_VERSION_UID","FIELDS_TO_IGNORE"],"mappings":"AAAO,MAAMA,sBAAsB;MACtBC,gBAAAA,GAAmB;AAC9B,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,aAAA;AACA,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,cAAA;AACA,IAAA;;;;;"}
1
+ {"version":3,"file":"constants.mjs","sources":["../../../server/src/history/constants.ts"],"sourcesContent":["export const HISTORY_VERSION_UID = 'plugin::content-manager.history-version';\nexport const FIELDS_TO_IGNORE = [\n 'createdAt',\n 'updatedAt',\n 'publishedAt',\n 'createdBy',\n 'updatedBy',\n 'strapi_stage',\n 'strapi_assignee',\n 'strapi_position',\n];\n"],"names":["HISTORY_VERSION_UID","FIELDS_TO_IGNORE"],"mappings":"AAAO,MAAMA,sBAAsB;MACtBC,gBAAAA,GAAmB;AAC9B,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,aAAA;AACA,IAAA,WAAA;AACA,IAAA,WAAA;AACA,IAAA,cAAA;AACA,IAAA,iBAAA;AACA,IAAA;;;;;"}
@@ -13,7 +13,6 @@ var historyVersion = require('./validation/history-version.js');
13
13
  let pageSizeNumber = 20;
14
14
  if (page) {
15
15
  const parsedPage = parseInt(page, 10);
16
- pageNumber = parseInt(page, 10);
17
16
  if (!Number.isNaN(parsedPage) && parsedPage >= 1) {
18
17
  pageNumber = parsedPage;
19
18
  }
@@ -93,7 +92,12 @@ const createHistoryVersionController = ({ strapi })=>{
93
92
  if (permissionChecker.cannot.update()) {
94
93
  throw new strapiUtils.errors.ForbiddenError();
95
94
  }
96
- const restoredDocument = await utils.getService(strapi, 'history').restoreVersion(request.params.versionId);
95
+ // Version ids are positive integers, other values cannot match a version
96
+ const versionId = Number(request.params.versionId);
97
+ if (Number.isInteger(versionId) === false || versionId < 1) {
98
+ throw new strapiUtils.errors.NotFoundError('History version not found');
99
+ }
100
+ const restoredDocument = await utils.getService(strapi, 'history').restoreVersion(versionId);
97
101
  return {
98
102
  data: {
99
103
  documentId: restoredDocument.documentId
@@ -1 +1 @@
1
- {"version":3,"file":"history-version.js","sources":["../../../../server/src/history/controllers/history-version.ts"],"sourcesContent":["import { async, errors } from '@strapi/utils';\nimport type { Core, UID } from '@strapi/types';\nimport { pick } from 'lodash';\n\nimport { getService as getContentManagerService } from '../../utils';\nimport { getService } from '../utils';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport { validateRestoreVersion } from './validation/history-version';\n\n/**\n * Parses pagination params and makes sure they're within valid ranges\n */\nconst getValidPagination = ({ page, pageSize }: { page: any; pageSize: any }) => {\n let pageNumber = 1;\n let pageSizeNumber = 20;\n\n if (page) {\n const parsedPage = parseInt(page, 10);\n pageNumber = parseInt(page, 10);\n\n if (!Number.isNaN(parsedPage) && parsedPage >= 1) {\n pageNumber = parsedPage;\n }\n }\n\n if (pageSize) {\n const parsedPageSize = parseInt(pageSize, 10);\n\n if (!Number.isNaN(parsedPageSize) && parsedPageSize >= 1 && parsedPageSize <= 100) {\n pageSizeNumber = parsedPageSize;\n }\n }\n\n return { page: pageNumber, pageSize: pageSizeNumber };\n};\n\nconst createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async findMany(ctx) {\n const contentTypeUid = ctx.query.contentType as UID.ContentType;\n const isSingleType = strapi.getModel(contentTypeUid)?.kind === 'singleType';\n\n if (isSingleType && !contentTypeUid) {\n throw new errors.ForbiddenError('contentType is required');\n }\n\n if (!isSingleType && (!contentTypeUid || !ctx.query.documentId)) {\n throw new errors.ForbiddenError('contentType and documentId are required');\n }\n\n /**\n * There are no permissions specifically for history versions,\n * but we need to check that the user can read the content type\n */\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: ctx.query.contentType,\n });\n\n if (permissionChecker.cannot.read()) {\n return ctx.forbidden();\n }\n\n const query: HistoryVersions.GetHistoryVersions.Request['query'] =\n await permissionChecker.sanitizeQuery(ctx.query);\n\n const { results, pagination } = await getService(strapi, 'history').findVersionsPage({\n query: {\n ...query,\n ...getValidPagination({ page: query.page, pageSize: query.pageSize }),\n },\n state: { userAbility: ctx.state.userAbility },\n });\n\n const sanitizedResults = await async.map(\n results,\n async (version: HistoryVersions.HistoryVersionDataResponse & { locale: string }) => {\n return {\n ...version,\n data: await permissionChecker.sanitizeOutput(version.data),\n createdBy: version.createdBy\n ? pick(version.createdBy, ['id', 'firstname', 'lastname', 'username', 'email'])\n : undefined,\n };\n }\n );\n\n return {\n data: sanitizedResults,\n meta: { pagination },\n };\n },\n\n async restoreVersion(ctx) {\n const request = ctx.request as unknown as HistoryVersions.RestoreHistoryVersion.Request;\n\n await validateRestoreVersion(request.body, 'contentType is required');\n\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: request.body.contentType,\n });\n\n if (permissionChecker.cannot.update()) {\n throw new errors.ForbiddenError();\n }\n\n const restoredDocument = await getService(strapi, 'history').restoreVersion(\n request.params.versionId\n );\n\n return {\n data: { documentId: restoredDocument.documentId },\n } satisfies HistoryVersions.RestoreHistoryVersion.Response;\n },\n } satisfies Core.Controller;\n};\n\nexport { createHistoryVersionController };\n"],"names":["getValidPagination","page","pageSize","pageNumber","pageSizeNumber","parsedPage","parseInt","Number","isNaN","parsedPageSize","createHistoryVersionController","strapi","findMany","ctx","contentTypeUid","query","contentType","isSingleType","getModel","kind","errors","ForbiddenError","documentId","permissionChecker","getContentManagerService","create","userAbility","state","model","cannot","read","forbidden","sanitizeQuery","results","pagination","getService","findVersionsPage","sanitizedResults","async","map","version","data","sanitizeOutput","createdBy","pick","undefined","meta","restoreVersion","request","validateRestoreVersion","body","update","restoredDocument","params","versionId"],"mappings":"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"}
1
+ {"version":3,"file":"history-version.js","sources":["../../../../server/src/history/controllers/history-version.ts"],"sourcesContent":["import { async, errors } from '@strapi/utils';\nimport type { Core, UID } from '@strapi/types';\nimport { pick } from 'lodash';\n\nimport { getService as getContentManagerService } from '../../utils';\nimport { getService } from '../utils';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport { validateRestoreVersion } from './validation/history-version';\n\n/**\n * Parses pagination params and makes sure they're within valid ranges\n */\nconst getValidPagination = ({ page, pageSize }: { page: any; pageSize: any }) => {\n let pageNumber = 1;\n let pageSizeNumber = 20;\n\n if (page) {\n const parsedPage = parseInt(page, 10);\n\n if (!Number.isNaN(parsedPage) && parsedPage >= 1) {\n pageNumber = parsedPage;\n }\n }\n\n if (pageSize) {\n const parsedPageSize = parseInt(pageSize, 10);\n\n if (!Number.isNaN(parsedPageSize) && parsedPageSize >= 1 && parsedPageSize <= 100) {\n pageSizeNumber = parsedPageSize;\n }\n }\n\n return { page: pageNumber, pageSize: pageSizeNumber };\n};\n\nconst createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async findMany(ctx) {\n const contentTypeUid = ctx.query.contentType as UID.ContentType;\n const isSingleType = strapi.getModel(contentTypeUid)?.kind === 'singleType';\n\n if (isSingleType && !contentTypeUid) {\n throw new errors.ForbiddenError('contentType is required');\n }\n\n if (!isSingleType && (!contentTypeUid || !ctx.query.documentId)) {\n throw new errors.ForbiddenError('contentType and documentId are required');\n }\n\n /**\n * There are no permissions specifically for history versions,\n * but we need to check that the user can read the content type\n */\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: ctx.query.contentType,\n });\n\n if (permissionChecker.cannot.read()) {\n return ctx.forbidden();\n }\n\n const query: HistoryVersions.GetHistoryVersions.Request['query'] =\n await permissionChecker.sanitizeQuery(ctx.query);\n\n const { results, pagination } = await getService(strapi, 'history').findVersionsPage({\n query: {\n ...query,\n ...getValidPagination({ page: query.page, pageSize: query.pageSize }),\n },\n state: { userAbility: ctx.state.userAbility },\n });\n\n const sanitizedResults = await async.map(\n results,\n async (version: HistoryVersions.HistoryVersionDataResponse & { locale: string }) => {\n return {\n ...version,\n data: await permissionChecker.sanitizeOutput(version.data),\n createdBy: version.createdBy\n ? pick(version.createdBy, ['id', 'firstname', 'lastname', 'username', 'email'])\n : undefined,\n };\n }\n );\n\n return {\n data: sanitizedResults,\n meta: { pagination },\n };\n },\n\n async restoreVersion(ctx) {\n const request = ctx.request as unknown as HistoryVersions.RestoreHistoryVersion.Request;\n\n await validateRestoreVersion(request.body, 'contentType is required');\n\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: request.body.contentType,\n });\n\n if (permissionChecker.cannot.update()) {\n throw new errors.ForbiddenError();\n }\n\n // Version ids are positive integers, other values cannot match a version\n const versionId = Number(request.params.versionId);\n if (Number.isInteger(versionId) === false || versionId < 1) {\n throw new errors.NotFoundError('History version not found');\n }\n\n const restoredDocument = await getService(strapi, 'history').restoreVersion(versionId);\n\n return {\n data: { documentId: restoredDocument.documentId },\n } satisfies HistoryVersions.RestoreHistoryVersion.Response;\n },\n } satisfies Core.Controller;\n};\n\nexport { createHistoryVersionController };\n"],"names":["getValidPagination","page","pageSize","pageNumber","pageSizeNumber","parsedPage","parseInt","Number","isNaN","parsedPageSize","createHistoryVersionController","strapi","findMany","ctx","contentTypeUid","query","contentType","isSingleType","getModel","kind","errors","ForbiddenError","documentId","permissionChecker","getContentManagerService","create","userAbility","state","model","cannot","read","forbidden","sanitizeQuery","results","pagination","getService","findVersionsPage","sanitizedResults","async","map","version","data","sanitizeOutput","createdBy","pick","undefined","meta","restoreVersion","request","validateRestoreVersion","body","update","versionId","params","isInteger","NotFoundError","restoredDocument"],"mappings":"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"}
@@ -11,7 +11,6 @@ import { validateRestoreVersion } from './validation/history-version.mjs';
11
11
  let pageSizeNumber = 20;
12
12
  if (page) {
13
13
  const parsedPage = parseInt(page, 10);
14
- pageNumber = parseInt(page, 10);
15
14
  if (!Number.isNaN(parsedPage) && parsedPage >= 1) {
16
15
  pageNumber = parsedPage;
17
16
  }
@@ -91,7 +90,12 @@ const createHistoryVersionController = ({ strapi })=>{
91
90
  if (permissionChecker.cannot.update()) {
92
91
  throw new errors.ForbiddenError();
93
92
  }
94
- const restoredDocument = await getService$1(strapi, 'history').restoreVersion(request.params.versionId);
93
+ // Version ids are positive integers, other values cannot match a version
94
+ const versionId = Number(request.params.versionId);
95
+ if (Number.isInteger(versionId) === false || versionId < 1) {
96
+ throw new errors.NotFoundError('History version not found');
97
+ }
98
+ const restoredDocument = await getService$1(strapi, 'history').restoreVersion(versionId);
95
99
  return {
96
100
  data: {
97
101
  documentId: restoredDocument.documentId
@@ -1 +1 @@
1
- {"version":3,"file":"history-version.mjs","sources":["../../../../server/src/history/controllers/history-version.ts"],"sourcesContent":["import { async, errors } from '@strapi/utils';\nimport type { Core, UID } from '@strapi/types';\nimport { pick } from 'lodash';\n\nimport { getService as getContentManagerService } from '../../utils';\nimport { getService } from '../utils';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport { validateRestoreVersion } from './validation/history-version';\n\n/**\n * Parses pagination params and makes sure they're within valid ranges\n */\nconst getValidPagination = ({ page, pageSize }: { page: any; pageSize: any }) => {\n let pageNumber = 1;\n let pageSizeNumber = 20;\n\n if (page) {\n const parsedPage = parseInt(page, 10);\n pageNumber = parseInt(page, 10);\n\n if (!Number.isNaN(parsedPage) && parsedPage >= 1) {\n pageNumber = parsedPage;\n }\n }\n\n if (pageSize) {\n const parsedPageSize = parseInt(pageSize, 10);\n\n if (!Number.isNaN(parsedPageSize) && parsedPageSize >= 1 && parsedPageSize <= 100) {\n pageSizeNumber = parsedPageSize;\n }\n }\n\n return { page: pageNumber, pageSize: pageSizeNumber };\n};\n\nconst createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async findMany(ctx) {\n const contentTypeUid = ctx.query.contentType as UID.ContentType;\n const isSingleType = strapi.getModel(contentTypeUid)?.kind === 'singleType';\n\n if (isSingleType && !contentTypeUid) {\n throw new errors.ForbiddenError('contentType is required');\n }\n\n if (!isSingleType && (!contentTypeUid || !ctx.query.documentId)) {\n throw new errors.ForbiddenError('contentType and documentId are required');\n }\n\n /**\n * There are no permissions specifically for history versions,\n * but we need to check that the user can read the content type\n */\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: ctx.query.contentType,\n });\n\n if (permissionChecker.cannot.read()) {\n return ctx.forbidden();\n }\n\n const query: HistoryVersions.GetHistoryVersions.Request['query'] =\n await permissionChecker.sanitizeQuery(ctx.query);\n\n const { results, pagination } = await getService(strapi, 'history').findVersionsPage({\n query: {\n ...query,\n ...getValidPagination({ page: query.page, pageSize: query.pageSize }),\n },\n state: { userAbility: ctx.state.userAbility },\n });\n\n const sanitizedResults = await async.map(\n results,\n async (version: HistoryVersions.HistoryVersionDataResponse & { locale: string }) => {\n return {\n ...version,\n data: await permissionChecker.sanitizeOutput(version.data),\n createdBy: version.createdBy\n ? pick(version.createdBy, ['id', 'firstname', 'lastname', 'username', 'email'])\n : undefined,\n };\n }\n );\n\n return {\n data: sanitizedResults,\n meta: { pagination },\n };\n },\n\n async restoreVersion(ctx) {\n const request = ctx.request as unknown as HistoryVersions.RestoreHistoryVersion.Request;\n\n await validateRestoreVersion(request.body, 'contentType is required');\n\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: request.body.contentType,\n });\n\n if (permissionChecker.cannot.update()) {\n throw new errors.ForbiddenError();\n }\n\n const restoredDocument = await getService(strapi, 'history').restoreVersion(\n request.params.versionId\n );\n\n return {\n data: { documentId: restoredDocument.documentId },\n } satisfies HistoryVersions.RestoreHistoryVersion.Response;\n },\n } satisfies Core.Controller;\n};\n\nexport { createHistoryVersionController };\n"],"names":["getValidPagination","page","pageSize","pageNumber","pageSizeNumber","parsedPage","parseInt","Number","isNaN","parsedPageSize","createHistoryVersionController","strapi","findMany","ctx","contentTypeUid","query","contentType","isSingleType","getModel","kind","errors","ForbiddenError","documentId","permissionChecker","getContentManagerService","create","userAbility","state","model","cannot","read","forbidden","sanitizeQuery","results","pagination","getService","findVersionsPage","sanitizedResults","async","map","version","data","sanitizeOutput","createdBy","pick","undefined","meta","restoreVersion","request","validateRestoreVersion","body","update","restoredDocument","params","versionId"],"mappings":"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"}
1
+ {"version":3,"file":"history-version.mjs","sources":["../../../../server/src/history/controllers/history-version.ts"],"sourcesContent":["import { async, errors } from '@strapi/utils';\nimport type { Core, UID } from '@strapi/types';\nimport { pick } from 'lodash';\n\nimport { getService as getContentManagerService } from '../../utils';\nimport { getService } from '../utils';\nimport type { HistoryVersions } from '../../../../shared/contracts';\nimport { validateRestoreVersion } from './validation/history-version';\n\n/**\n * Parses pagination params and makes sure they're within valid ranges\n */\nconst getValidPagination = ({ page, pageSize }: { page: any; pageSize: any }) => {\n let pageNumber = 1;\n let pageSizeNumber = 20;\n\n if (page) {\n const parsedPage = parseInt(page, 10);\n\n if (!Number.isNaN(parsedPage) && parsedPage >= 1) {\n pageNumber = parsedPage;\n }\n }\n\n if (pageSize) {\n const parsedPageSize = parseInt(pageSize, 10);\n\n if (!Number.isNaN(parsedPageSize) && parsedPageSize >= 1 && parsedPageSize <= 100) {\n pageSizeNumber = parsedPageSize;\n }\n }\n\n return { page: pageNumber, pageSize: pageSizeNumber };\n};\n\nconst createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => {\n return {\n async findMany(ctx) {\n const contentTypeUid = ctx.query.contentType as UID.ContentType;\n const isSingleType = strapi.getModel(contentTypeUid)?.kind === 'singleType';\n\n if (isSingleType && !contentTypeUid) {\n throw new errors.ForbiddenError('contentType is required');\n }\n\n if (!isSingleType && (!contentTypeUid || !ctx.query.documentId)) {\n throw new errors.ForbiddenError('contentType and documentId are required');\n }\n\n /**\n * There are no permissions specifically for history versions,\n * but we need to check that the user can read the content type\n */\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: ctx.query.contentType,\n });\n\n if (permissionChecker.cannot.read()) {\n return ctx.forbidden();\n }\n\n const query: HistoryVersions.GetHistoryVersions.Request['query'] =\n await permissionChecker.sanitizeQuery(ctx.query);\n\n const { results, pagination } = await getService(strapi, 'history').findVersionsPage({\n query: {\n ...query,\n ...getValidPagination({ page: query.page, pageSize: query.pageSize }),\n },\n state: { userAbility: ctx.state.userAbility },\n });\n\n const sanitizedResults = await async.map(\n results,\n async (version: HistoryVersions.HistoryVersionDataResponse & { locale: string }) => {\n return {\n ...version,\n data: await permissionChecker.sanitizeOutput(version.data),\n createdBy: version.createdBy\n ? pick(version.createdBy, ['id', 'firstname', 'lastname', 'username', 'email'])\n : undefined,\n };\n }\n );\n\n return {\n data: sanitizedResults,\n meta: { pagination },\n };\n },\n\n async restoreVersion(ctx) {\n const request = ctx.request as unknown as HistoryVersions.RestoreHistoryVersion.Request;\n\n await validateRestoreVersion(request.body, 'contentType is required');\n\n const permissionChecker = getContentManagerService('permission-checker').create({\n userAbility: ctx.state.userAbility,\n model: request.body.contentType,\n });\n\n if (permissionChecker.cannot.update()) {\n throw new errors.ForbiddenError();\n }\n\n // Version ids are positive integers, other values cannot match a version\n const versionId = Number(request.params.versionId);\n if (Number.isInteger(versionId) === false || versionId < 1) {\n throw new errors.NotFoundError('History version not found');\n }\n\n const restoredDocument = await getService(strapi, 'history').restoreVersion(versionId);\n\n return {\n data: { documentId: restoredDocument.documentId },\n } satisfies HistoryVersions.RestoreHistoryVersion.Response;\n },\n } satisfies Core.Controller;\n};\n\nexport { createHistoryVersionController };\n"],"names":["getValidPagination","page","pageSize","pageNumber","pageSizeNumber","parsedPage","parseInt","Number","isNaN","parsedPageSize","createHistoryVersionController","strapi","findMany","ctx","contentTypeUid","query","contentType","isSingleType","getModel","kind","errors","ForbiddenError","documentId","permissionChecker","getContentManagerService","create","userAbility","state","model","cannot","read","forbidden","sanitizeQuery","results","pagination","getService","findVersionsPage","sanitizedResults","async","map","version","data","sanitizeOutput","createdBy","pick","undefined","meta","restoreVersion","request","validateRestoreVersion","body","update","versionId","params","isInteger","NotFoundError","restoredDocument"],"mappings":";;;;;;AASA;;AAEC,IACD,MAAMA,kBAAAA,GAAqB,CAAC,EAAEC,IAAI,EAAEC,QAAQ,EAAgC,GAAA;AAC1E,IAAA,IAAIC,UAAAA,GAAa,CAAA;AACjB,IAAA,IAAIC,cAAAA,GAAiB,EAAA;AAErB,IAAA,IAAIH,IAAAA,EAAM;QACR,MAAMI,UAAAA,GAAaC,SAASL,IAAAA,EAAM,EAAA,CAAA;AAElC,QAAA,IAAI,CAACM,MAAAA,CAAOC,KAAK,CAACH,UAAAA,CAAAA,IAAeA,cAAc,CAAA,EAAG;YAChDF,UAAAA,GAAaE,UAAAA;AACf,QAAA;AACF,IAAA;AAEA,IAAA,IAAIH,QAAAA,EAAU;QACZ,MAAMO,cAAAA,GAAiBH,SAASJ,QAAAA,EAAU,EAAA,CAAA;QAE1C,IAAI,CAACK,OAAOC,KAAK,CAACC,mBAAmBA,cAAAA,IAAkB,CAAA,IAAKA,kBAAkB,GAAA,EAAK;YACjFL,cAAAA,GAAiBK,cAAAA;AACnB,QAAA;AACF,IAAA;IAEA,OAAO;QAAER,IAAAA,EAAME,UAAAA;QAAYD,QAAAA,EAAUE;AAAe,KAAA;AACtD,CAAA;AAEA,MAAMM,8BAAAA,GAAiC,CAAC,EAAEC,MAAM,EAA2B,GAAA;IACzE,OAAO;AACL,QAAA,MAAMC,UAASC,GAAG,EAAA;AAChB,YAAA,MAAMC,cAAAA,GAAiBD,GAAAA,CAAIE,KAAK,CAACC,WAAW;AAC5C,YAAA,MAAMC,YAAAA,GAAeN,MAAAA,CAAOO,QAAQ,CAACJ,iBAAiBK,IAAAA,KAAS,YAAA;YAE/D,IAAIF,YAAAA,IAAgB,CAACH,cAAAA,EAAgB;gBACnC,MAAM,IAAIM,MAAAA,CAAOC,cAAc,CAAC,yBAAA,CAAA;AAClC,YAAA;YAEA,IAAI,CAACJ,YAAAA,KAAiB,CAACH,cAAAA,IAAkB,CAACD,GAAAA,CAAIE,KAAK,CAACO,UAAS,CAAA,EAAI;gBAC/D,MAAM,IAAIF,MAAAA,CAAOC,cAAc,CAAC,yCAAA,CAAA;AAClC,YAAA;AAEA;;;AAGC,UACD,MAAME,iBAAAA,GAAoBC,UAAAA,CAAyB,oBAAA,CAAA,CAAsBC,MAAM,CAAC;gBAC9EC,WAAAA,EAAab,GAAAA,CAAIc,KAAK,CAACD,WAAW;gBAClCE,KAAAA,EAAOf,GAAAA,CAAIE,KAAK,CAACC;AACnB,aAAA,CAAA;AAEA,YAAA,IAAIO,iBAAAA,CAAkBM,MAAM,CAACC,IAAI,EAAA,EAAI;AACnC,gBAAA,OAAOjB,IAAIkB,SAAS,EAAA;AACtB,YAAA;AAEA,YAAA,MAAMhB,QACJ,MAAMQ,iBAAAA,CAAkBS,aAAa,CAACnB,IAAIE,KAAK,CAAA;YAEjD,MAAM,EAAEkB,OAAO,EAAEC,UAAU,EAAE,GAAG,MAAMC,YAAAA,CAAWxB,MAAAA,EAAQ,SAAA,CAAA,CAAWyB,gBAAgB,CAAC;gBACnFrB,KAAAA,EAAO;AACL,oBAAA,GAAGA,KAAK;AACR,oBAAA,GAAGf,kBAAAA,CAAmB;AAAEC,wBAAAA,IAAAA,EAAMc,MAAMd,IAAI;AAAEC,wBAAAA,QAAAA,EAAUa,MAAMb;qBAAS;AACrE,iBAAA;gBACAyB,KAAAA,EAAO;oBAAED,WAAAA,EAAab,GAAAA,CAAIc,KAAK,CAACD;AAAY;AAC9C,aAAA,CAAA;AAEA,YAAA,MAAMW,mBAAmB,MAAMC,KAAAA,CAAMC,GAAG,CACtCN,SACA,OAAOO,OAAAA,GAAAA;gBACL,OAAO;AACL,oBAAA,GAAGA,OAAO;AACVC,oBAAAA,IAAAA,EAAM,MAAMlB,iBAAAA,CAAkBmB,cAAc,CAACF,QAAQC,IAAI,CAAA;AACzDE,oBAAAA,SAAAA,EAAWH,QAAQG,SAAS,GACxBC,IAAAA,CAAKJ,OAAAA,CAAQG,SAAS,EAAE;AAAC,wBAAA,IAAA;AAAM,wBAAA,WAAA;AAAa,wBAAA,UAAA;AAAY,wBAAA,UAAA;AAAY,wBAAA;qBAAQ,CAAA,GAC5EE;AACN,iBAAA;AACF,YAAA,CAAA,CAAA;YAGF,OAAO;gBACLJ,IAAAA,EAAMJ,gBAAAA;gBACNS,IAAAA,EAAM;AAAEZ,oBAAAA;AAAW;AACrB,aAAA;AACF,QAAA,CAAA;AAEA,QAAA,MAAMa,gBAAelC,GAAG,EAAA;YACtB,MAAMmC,OAAAA,GAAUnC,IAAImC,OAAO;YAE3B,MAAMC,sBAAAA,CAAuBD,OAAAA,CAAQE,IAAI,EAAE,yBAAA,CAAA;AAE3C,YAAA,MAAM3B,iBAAAA,GAAoBC,UAAAA,CAAyB,oBAAA,CAAA,CAAsBC,MAAM,CAAC;gBAC9EC,WAAAA,EAAab,GAAAA,CAAIc,KAAK,CAACD,WAAW;gBAClCE,KAAAA,EAAOoB,OAAAA,CAAQE,IAAI,CAAClC;AACtB,aAAA,CAAA;AAEA,YAAA,IAAIO,iBAAAA,CAAkBM,MAAM,CAACsB,MAAM,EAAA,EAAI;gBACrC,MAAM,IAAI/B,OAAOC,cAAc,EAAA;AACjC,YAAA;;AAGA,YAAA,MAAM+B,SAAAA,GAAY7C,MAAAA,CAAOyC,OAAAA,CAAQK,MAAM,CAACD,SAAS,CAAA;AACjD,YAAA,IAAI7C,OAAO+C,SAAS,CAACF,SAAAA,CAAAA,KAAe,KAAA,IAASA,YAAY,CAAA,EAAG;gBAC1D,MAAM,IAAIhC,MAAAA,CAAOmC,aAAa,CAAC,2BAAA,CAAA;AACjC,YAAA;AAEA,YAAA,MAAMC,mBAAmB,MAAMrB,YAAAA,CAAWxB,MAAAA,EAAQ,SAAA,CAAA,CAAWoC,cAAc,CAACK,SAAAA,CAAAA;YAE5E,OAAO;gBACLX,IAAAA,EAAM;AAAEnB,oBAAAA,UAAAA,EAAYkC,iBAAiBlC;AAAW;AAClD,aAAA;AACF,QAAA;AACF,KAAA;AACF;;;;"}
@@ -5,6 +5,7 @@ var _ = require('lodash');
5
5
  var constants = require('../constants.js');
6
6
  var utils = require('./utils.js');
7
7
  var index = require('../../utils/index.js');
8
+ var auditLogs = require('../../audit-logs.js');
8
9
 
9
10
  const createHistoryService = ({ strapi })=>{
10
11
  const query = strapi.db.query(constants.HISTORY_VERSION_UID);
@@ -80,15 +81,16 @@ const createHistoryService = ({ strapi })=>{
80
81
  const results = ids.map((id)=>versionsById.get(id)).filter(Boolean);
81
82
  const populateEntry = async (entry)=>{
82
83
  return strapiUtils.traverseEntity(async (options, utils)=>{
84
+ if (options.key === 'id' && options.schema.modelType === 'component') {
85
+ // Ids on components throw an error when restoring
86
+ utils.remove('id');
87
+ return;
88
+ }
83
89
  if (!options.attribute) return;
84
90
  if (!options.value) return;
85
91
  const currentValue = Array.isArray(options.value) ? options.value : [
86
92
  options.value
87
93
  ];
88
- if (options.attribute.type === 'component') {
89
- // Ids on components throw an error when restoring
90
- utils.remove('id');
91
- }
92
94
  if (options.attribute.type === 'relation' && // TODO: handle polymorphic relations
93
95
  options.attribute.relation !== 'morphToOne' && options.attribute.relation !== 'morphToMany') {
94
96
  if (options.attribute.target === 'admin::user') {
@@ -158,6 +160,9 @@ const createHistoryService = ({ strapi })=>{
158
160
  id: versionId
159
161
  }
160
162
  });
163
+ if (version === null) {
164
+ throw new strapiUtils.errors.NotFoundError('History version not found');
165
+ }
161
166
  const contentTypeSchemaAttributes = strapi.getModel(version.contentType).attributes;
162
167
  const schemaDiff = serviceUtils.getSchemaAttributesDiff(version.schema, contentTypeSchemaAttributes);
163
168
  // Set all added attribute values to null
@@ -170,10 +175,13 @@ const createHistoryService = ({ strapi })=>{
170
175
  const schema = structuredClone(version.schema);
171
176
  schema.attributes = _.omit(contentTypeSchemaAttributes, constants.FIELDS_TO_IGNORE);
172
177
  const dataWithoutMissingRelations = await strapiUtils.traverseEntity(async (options, utils)=>{
173
- if (!options.attribute) return;
174
- if (options.attribute.type === 'component') {
178
+ if (options.key === 'id' && options.schema.modelType === 'component') {
175
179
  // Ids on components throw an error when restoring
176
180
  utils.remove('id');
181
+ return;
182
+ }
183
+ if (!options.attribute) return;
184
+ if (options.attribute.type === 'component') {
177
185
  if (options.attribute.repeatable && options.value === null) {
178
186
  // Repeatable Components should always be an array
179
187
  utils.set(options.key, []);
@@ -212,6 +220,15 @@ const createHistoryService = ({ strapi })=>{
212
220
  if (!restoredDocument) {
213
221
  throw new strapiUtils.errors.ApplicationError('Failed to restore version');
214
222
  }
223
+ await strapiUtils.emitAudit({
224
+ strapi
225
+ }, auditLogs.AUDITED_EVENTS.ENTRY_HISTORY_RESTORE, {
226
+ uid: version.contentType,
227
+ documentId: restoredDocument.documentId,
228
+ locale: version.locale ?? null,
229
+ versionId: version.id,
230
+ versionCreatedAt: new Date(version.createdAt).toISOString()
231
+ });
215
232
  return restoredDocument;
216
233
  }
217
234
  };