@qkix/strapi-plugin-rewind 0.1.4 → 0.2.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.
- package/README.md +11 -2
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +145 -121
- package/dist/server/index.js +2 -2
- package/dist/server/index.mjs +197 -164
- package/dist/server/src/controllers/index.d.ts +1 -0
- package/dist/server/src/controllers/version.d.ts +6 -0
- package/dist/server/src/index.d.ts +9 -0
- package/dist/server/src/services/index.d.ts +8 -0
- package/dist/server/src/services/pin.d.ts +19 -0
- package/package.json +2 -2
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createHash as
|
|
2
|
-
import { AsyncLocalStorage as
|
|
3
|
-
const
|
|
1
|
+
import { createHash as J } from "node:crypto";
|
|
2
|
+
import { AsyncLocalStorage as Z } from "node:async_hooks";
|
|
3
|
+
const Q = ["id", "documentId", "locale"], S = [
|
|
4
4
|
"createdAt",
|
|
5
5
|
"updatedAt",
|
|
6
6
|
"publishedAt",
|
|
@@ -10,15 +10,15 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
10
10
|
"strapi_assignee",
|
|
11
11
|
"locale",
|
|
12
12
|
"localizations"
|
|
13
|
-
], I = (e, t) => e.getModel(t),
|
|
13
|
+
], I = (e, t) => e.getModel(t), x = (e) => e?.type === "relation" || e?.type === "media", $ = (e) => e?.type === "relation" && String(e.relation ?? "").toLowerCase().startsWith("morph"), F = (e, t) => Object.entries(I(e, t).attributes).filter(
|
|
14
14
|
([, n]) => !["relation", "media", "component", "dynamiczone"].includes(n.type)
|
|
15
|
-
).map(([n]) => n),
|
|
16
|
-
const
|
|
17
|
-
return Object.entries(
|
|
18
|
-
(a, [s,
|
|
19
|
-
switch (
|
|
15
|
+
).map(([n]) => n), E = (e, t, n = !0) => {
|
|
16
|
+
const i = I(e, t), o = n ? "select" : "fields";
|
|
17
|
+
return Object.entries(i.attributes).reduce(
|
|
18
|
+
(a, [s, r]) => {
|
|
19
|
+
switch (r.type) {
|
|
20
20
|
case "relation": {
|
|
21
|
-
if ($(
|
|
21
|
+
if ($(r)) break;
|
|
22
22
|
a[s] = { [o]: ["documentId", "locale", "publishedAt"] };
|
|
23
23
|
break;
|
|
24
24
|
}
|
|
@@ -28,17 +28,17 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
28
28
|
}
|
|
29
29
|
case "component": {
|
|
30
30
|
a[s] = {
|
|
31
|
-
populate:
|
|
32
|
-
[o]:
|
|
31
|
+
populate: E(e, r.component, n),
|
|
32
|
+
[o]: F(e, r.component)
|
|
33
33
|
};
|
|
34
34
|
break;
|
|
35
35
|
}
|
|
36
36
|
case "dynamiczone": {
|
|
37
37
|
a[s] = {
|
|
38
|
-
on: (
|
|
38
|
+
on: (r.components ?? []).reduce(
|
|
39
39
|
(c, l) => (c[l] = {
|
|
40
|
-
populate:
|
|
41
|
-
[o]:
|
|
40
|
+
populate: E(e, l, n),
|
|
41
|
+
[o]: F(e, l)
|
|
42
42
|
}, c),
|
|
43
43
|
{}
|
|
44
44
|
)
|
|
@@ -51,17 +51,17 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
51
51
|
{}
|
|
52
52
|
);
|
|
53
53
|
}, ee = (e, t) => {
|
|
54
|
-
const n = {},
|
|
54
|
+
const n = {}, i = (a) => {
|
|
55
55
|
if (n[a]) return;
|
|
56
56
|
const s = I(e, a).attributes;
|
|
57
57
|
n[a] = R(s, S);
|
|
58
|
-
for (const
|
|
59
|
-
if (
|
|
60
|
-
for (const c of
|
|
58
|
+
for (const r of Object.values(s))
|
|
59
|
+
if (r.type === "component" && i(r.component), r.type === "dynamiczone")
|
|
60
|
+
for (const c of r.components ?? []) i(c);
|
|
61
61
|
}, o = I(e, t).attributes;
|
|
62
62
|
for (const a of Object.values(o))
|
|
63
|
-
if (a.type === "component" &&
|
|
64
|
-
for (const s of a.components ?? [])
|
|
63
|
+
if (a.type === "component" && i(a.component), a.type === "dynamiczone")
|
|
64
|
+
for (const s of a.components ?? []) i(s);
|
|
65
65
|
return {
|
|
66
66
|
attributes: R(o, S),
|
|
67
67
|
components: n
|
|
@@ -73,77 +73,77 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
73
73
|
targetUid: t
|
|
74
74
|
}
|
|
75
75
|
), ne = (e, t, n) => {
|
|
76
|
-
const
|
|
77
|
-
for (const [
|
|
78
|
-
const l = r
|
|
79
|
-
if (l &&
|
|
76
|
+
const i = I(e, t).attributes, o = {}, a = {}, s = R(n, [...S, ...Q]);
|
|
77
|
+
for (const [r, c] of Object.entries(s)) {
|
|
78
|
+
const l = i[r];
|
|
79
|
+
if (l && x(l)) {
|
|
80
80
|
if ($(l)) continue;
|
|
81
81
|
const d = l.type === "media" ? "plugin::upload.file" : l.target;
|
|
82
|
-
a[
|
|
82
|
+
a[r] = te(c, d, l.type === "media");
|
|
83
83
|
continue;
|
|
84
84
|
}
|
|
85
|
-
o[
|
|
85
|
+
o[r] = l ? Y(e, l, c) : c;
|
|
86
86
|
}
|
|
87
87
|
return { data: o, relations: a, schemaSnapshot: ee(e, t) };
|
|
88
88
|
}, Y = (e, t, n) => {
|
|
89
89
|
if (n == null) return n;
|
|
90
90
|
if (t.type === "component") {
|
|
91
|
-
const
|
|
92
|
-
return Array.isArray(n) ? n.map((o) =>
|
|
91
|
+
const i = (o) => _(e, t.component, o);
|
|
92
|
+
return Array.isArray(n) ? n.map((o) => i(o)) : i(n);
|
|
93
93
|
}
|
|
94
|
-
return t.type === "dynamiczone" && Array.isArray(n) ? n.map((
|
|
95
|
-
const o =
|
|
94
|
+
return t.type === "dynamiczone" && Array.isArray(n) ? n.map((i) => {
|
|
95
|
+
const o = i;
|
|
96
96
|
return _(e, o.__component, o);
|
|
97
97
|
}) : n;
|
|
98
98
|
}, _ = (e, t, n) => {
|
|
99
99
|
if (!n || !t) return n;
|
|
100
|
-
const
|
|
100
|
+
const i = I(e, t).attributes, o = {};
|
|
101
101
|
for (const [a, s] of Object.entries(n)) {
|
|
102
102
|
if (a === "id") continue;
|
|
103
|
-
const
|
|
104
|
-
o[a] =
|
|
103
|
+
const r = i[a];
|
|
104
|
+
o[a] = r ? Y(e, r, s) : s;
|
|
105
105
|
}
|
|
106
106
|
return o;
|
|
107
107
|
}, H = (e) => {
|
|
108
|
-
const t = /* @__PURE__ */ new Map(), n = async (
|
|
109
|
-
if (t.has(
|
|
108
|
+
const t = /* @__PURE__ */ new Map(), n = async (i) => {
|
|
109
|
+
if (t.has(i)) return t.get(i);
|
|
110
110
|
let o;
|
|
111
111
|
try {
|
|
112
|
-
o = (await e.plugin("content-manager").service("content-types").findConfiguration(e.contentTypes[
|
|
112
|
+
o = (await e.plugin("content-manager").service("content-types").findConfiguration(e.contentTypes[i]))?.settings?.mainField;
|
|
113
113
|
} catch {
|
|
114
114
|
}
|
|
115
|
-
return t.set(
|
|
115
|
+
return t.set(i, o), o;
|
|
116
116
|
};
|
|
117
|
-
return async (
|
|
117
|
+
return async (i, o) => {
|
|
118
118
|
if (!o) return null;
|
|
119
|
-
const s = [await n(
|
|
120
|
-
for (const
|
|
121
|
-
const c = o[
|
|
119
|
+
const s = [await n(i), "title", "name", "label"].filter(Boolean);
|
|
120
|
+
for (const r of s) {
|
|
121
|
+
const c = o[r];
|
|
122
122
|
if (typeof c == "string" && c.trim())
|
|
123
123
|
return c.trim().slice(0, 255);
|
|
124
124
|
}
|
|
125
125
|
return null;
|
|
126
126
|
};
|
|
127
|
-
}, v = "plugin::rewind.version", oe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), se = (e) =>
|
|
128
|
-
const t = H(e), n = (
|
|
129
|
-
const d = r
|
|
130
|
-
return e.db.query(
|
|
127
|
+
}, v = "plugin::rewind.version", oe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), se = (e) => J("sha256").update(JSON.stringify(e)).digest("hex"), ie = ({ strapi: e }) => {
|
|
128
|
+
const t = H(e), n = (r) => !!e.contentTypes[r]?.options?.draftAndPublish, i = (r) => !!(e.getModel(r)?.pluginOptions?.i18n?.localized ?? e.contentTypes[r]?.pluginOptions?.i18n?.localized), o = async (r, c, l) => {
|
|
129
|
+
const d = i(r) && l.filter(Boolean).length > 0 ? { locale: { $in: l.filter(Boolean) } } : {};
|
|
130
|
+
return e.db.query(r).findMany({
|
|
131
131
|
where: {
|
|
132
132
|
documentId: c,
|
|
133
133
|
...d,
|
|
134
|
-
...n(
|
|
134
|
+
...n(r) ? { publishedAt: null } : {}
|
|
135
135
|
},
|
|
136
|
-
populate:
|
|
136
|
+
populate: E(e, r)
|
|
137
137
|
});
|
|
138
|
-
}, a = async (
|
|
138
|
+
}, a = async (r, c) => {
|
|
139
139
|
try {
|
|
140
|
-
const l = e.plugin("content-manager").service("document-metadata"), d = await l.getMetadata(
|
|
140
|
+
const l = e.plugin("content-manager").service("document-metadata"), d = await l.getMetadata(r, c);
|
|
141
141
|
return l.getStatus(c, d.availableStatus);
|
|
142
142
|
} catch {
|
|
143
143
|
return "draft";
|
|
144
144
|
}
|
|
145
|
-
}, s = async (
|
|
146
|
-
where: { contentType:
|
|
145
|
+
}, s = async (r, c, l) => e.db.query(v).findOne({
|
|
146
|
+
where: { contentType: r, relatedDocumentId: c, locale: l },
|
|
147
147
|
orderBy: { createdAt: "desc" }
|
|
148
148
|
});
|
|
149
149
|
return {
|
|
@@ -154,12 +154,12 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
154
154
|
* Called from the transaction's commit hook and never awaited by the write
|
|
155
155
|
* path - a failure here must not be able to reach the editor's save.
|
|
156
156
|
*/
|
|
157
|
-
async captureAll(
|
|
158
|
-
for (const l of
|
|
157
|
+
async captureAll(r, c) {
|
|
158
|
+
for (const l of r)
|
|
159
159
|
await this.capture(l, c);
|
|
160
160
|
},
|
|
161
|
-
async capture(
|
|
162
|
-
const { uid: l, relatedDocumentId: d, locale: p, origin: b } =
|
|
161
|
+
async capture(r, c) {
|
|
162
|
+
const { uid: l, relatedDocumentId: d, locale: p, origin: b } = r, m = r.before ?? await o(l, d, [p]);
|
|
163
163
|
for (const u of m) {
|
|
164
164
|
const h = u.locale ?? null, { data: f, relations: w, schemaSnapshot: T } = ne(e, l, u), g = se({ data: f, relations: w });
|
|
165
165
|
!oe.has(b) && (await s(l, d, h))?.contentHash === g || await e.db.query(v).create({
|
|
@@ -195,13 +195,13 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
195
195
|
}, ae = (e) => {
|
|
196
196
|
const t = /* @__PURE__ */ new Map();
|
|
197
197
|
for (const n of e) {
|
|
198
|
-
const
|
|
198
|
+
const i = `${n.uid}:${n.relatedDocumentId}:${n.locale ?? ""}`, o = t.get(i);
|
|
199
199
|
if (!o) {
|
|
200
|
-
t.set(
|
|
200
|
+
t.set(i, n);
|
|
201
201
|
continue;
|
|
202
202
|
}
|
|
203
203
|
const a = j.indexOf(n.origin) < j.indexOf(o.origin);
|
|
204
|
-
t.set(
|
|
204
|
+
t.set(i, {
|
|
205
205
|
// A winning `publish` still wants the rows an earlier `discardDraft`
|
|
206
206
|
// captured - losing them would lose the only copy of that state.
|
|
207
207
|
...a ? n : o,
|
|
@@ -209,10 +209,10 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
209
209
|
});
|
|
210
210
|
}
|
|
211
211
|
return [...t.values()];
|
|
212
|
-
}, W = new
|
|
213
|
-
const n = e.db.query(t),
|
|
214
|
-
if (
|
|
215
|
-
e.log.info(`[rewind] labelling ${
|
|
212
|
+
}, W = new Z(), ce = (e) => W.run({ active: !0 }, e), le = () => W.getStore()?.active === !0, de = 200, ue = async (e, t) => {
|
|
213
|
+
const n = e.db.query(t), i = await n.count({ where: { label: null } });
|
|
214
|
+
if (i === 0) return 0;
|
|
215
|
+
e.log.info(`[rewind] labelling ${i} older version(s)…`);
|
|
216
216
|
const o = H(e);
|
|
217
217
|
let a = 0;
|
|
218
218
|
for (; ; ) {
|
|
@@ -222,10 +222,10 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
222
222
|
limit: de
|
|
223
223
|
});
|
|
224
224
|
if (s.length === 0) break;
|
|
225
|
-
for (const
|
|
226
|
-
const c = await o(
|
|
225
|
+
for (const r of s) {
|
|
226
|
+
const c = await o(r.contentType, r.data);
|
|
227
227
|
await n.update({
|
|
228
|
-
where: { id:
|
|
228
|
+
where: { id: r.id },
|
|
229
229
|
data: { label: c ?? "" }
|
|
230
230
|
}), a += 1;
|
|
231
231
|
}
|
|
@@ -251,14 +251,14 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
251
251
|
`[rewind] could not label older versions: ${o instanceof Error ? o.message : String(o)}`
|
|
252
252
|
)
|
|
253
253
|
), he(e, t);
|
|
254
|
-
const n = () => e.plugin("rewind").service("snapshot"),
|
|
254
|
+
const n = () => e.plugin("rewind").service("snapshot"), i = (o) => {
|
|
255
255
|
if (!pe.includes(o.action)) return !1;
|
|
256
256
|
const a = o.contentType?.uid ?? o.uid ?? "";
|
|
257
257
|
return !(!a.startsWith("api::") || !t.contentTypes.includes(a) || le() || !t.trackApiWrites && !e.requestContext.get()?.request?.url?.startsWith("/content-manager"));
|
|
258
258
|
};
|
|
259
259
|
e.documents.use(async (o, a) => {
|
|
260
|
-
if (!
|
|
261
|
-
const s = o.contentType?.uid ?? o.uid,
|
|
260
|
+
if (!i(o)) return a();
|
|
261
|
+
const s = o.contentType?.uid ?? o.uid, r = o.params?.locale ?? null, c = o.action === "discardDraft" && o.params?.documentId ? await n().readRows(s, o.params.documentId, [r]) : null, l = await a(), d = o.action === "create" || o.action === "clone" ? l?.documentId : o.params?.documentId;
|
|
262
262
|
if (!d) return l;
|
|
263
263
|
const p = ge(e);
|
|
264
264
|
return await e.db.transaction(async ({ trx: b, onCommit: m }) => {
|
|
@@ -266,7 +266,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
266
266
|
u.intents.push({
|
|
267
267
|
uid: s,
|
|
268
268
|
relatedDocumentId: d,
|
|
269
|
-
locale:
|
|
269
|
+
locale: r,
|
|
270
270
|
origin: o.action,
|
|
271
271
|
before: c
|
|
272
272
|
}), !u.flushRegistered && (u.flushRegistered = !0, m(() => {
|
|
@@ -296,12 +296,12 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
296
296
|
const n = e.plugin("rewind").service("lock");
|
|
297
297
|
if (await n.acquire("prune", 30 * 60 * 1e3))
|
|
298
298
|
try {
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
`[rewind] thinned ${
|
|
299
|
+
const i = await e.plugin("rewind").service("retention").prune();
|
|
300
|
+
i.deleted > 0 && e.log.info(
|
|
301
|
+
`[rewind] thinned ${i.deleted} version(s) across ${i.documents} document(s)`
|
|
302
302
|
);
|
|
303
|
-
} catch (
|
|
304
|
-
e.log.error("[rewind] thinning failed",
|
|
303
|
+
} catch (i) {
|
|
304
|
+
e.log.error("[rewind] thinning failed", i);
|
|
305
305
|
} finally {
|
|
306
306
|
await n.release("prune");
|
|
307
307
|
}
|
|
@@ -371,10 +371,10 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
371
371
|
throw new Error(
|
|
372
372
|
'[rewind] config.contentTypes must be an array of content type uids, e.g. ["api::article.article"]'
|
|
373
373
|
);
|
|
374
|
-
const
|
|
375
|
-
if (
|
|
374
|
+
const i = t.filter((o) => !o.startsWith("api::"));
|
|
375
|
+
if (i.length > 0)
|
|
376
376
|
throw new Error(
|
|
377
|
-
`[rewind] config.contentTypes only supports api:: content types, received: ${
|
|
377
|
+
`[rewind] config.contentTypes only supports api:: content types, received: ${i.join(", ")}`
|
|
378
378
|
);
|
|
379
379
|
}
|
|
380
380
|
}, be = "collectionType", Te = "rewind_versions", ve = {
|
|
@@ -454,18 +454,18 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
454
454
|
userId: {
|
|
455
455
|
type: "integer"
|
|
456
456
|
}
|
|
457
|
-
},
|
|
457
|
+
}, Oe = {
|
|
458
458
|
kind: be,
|
|
459
459
|
collectionName: Te,
|
|
460
460
|
info: ve,
|
|
461
461
|
options: De,
|
|
462
462
|
pluginOptions: Ae,
|
|
463
463
|
attributes: Ie
|
|
464
|
-
},
|
|
465
|
-
version: { schema:
|
|
464
|
+
}, ke = {
|
|
465
|
+
version: { schema: Oe }
|
|
466
466
|
}, Se = ({ strapi: e }) => {
|
|
467
467
|
e.cron.remove("rewindPrune");
|
|
468
|
-
},
|
|
468
|
+
}, qe = [
|
|
469
469
|
"id",
|
|
470
470
|
"contentType",
|
|
471
471
|
"relatedDocumentId",
|
|
@@ -477,24 +477,24 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
477
477
|
"pinned",
|
|
478
478
|
"contentHash",
|
|
479
479
|
"createdAt"
|
|
480
|
-
],
|
|
480
|
+
], Ee = ({ strapi: e }) => ({
|
|
481
481
|
async find(t) {
|
|
482
|
-
const { contentType: n, documentId:
|
|
483
|
-
if (!n || !
|
|
482
|
+
const { contentType: n, documentId: i, locale: o, page: a = 1, pageSize: s = 10 } = t.query;
|
|
483
|
+
if (!n || !i)
|
|
484
484
|
return t.badRequest("contentType and documentId are required.");
|
|
485
|
-
const
|
|
485
|
+
const r = {
|
|
486
486
|
contentType: n,
|
|
487
|
-
relatedDocumentId:
|
|
487
|
+
relatedDocumentId: i,
|
|
488
488
|
...o ? { locale: o } : {}
|
|
489
489
|
}, c = Math.min(Number(s) || 10, 100), l = (Math.max(Number(a) || 1, 1) - 1) * c, [d, p] = await Promise.all([
|
|
490
490
|
e.db.query(v).findMany({
|
|
491
|
-
select:
|
|
492
|
-
where:
|
|
491
|
+
select: qe,
|
|
492
|
+
where: r,
|
|
493
493
|
orderBy: { createdAt: "desc" },
|
|
494
494
|
limit: c,
|
|
495
495
|
offset: l
|
|
496
496
|
}),
|
|
497
|
-
e.db.query(v).count({ where:
|
|
497
|
+
e.db.query(v).count({ where: r })
|
|
498
498
|
]);
|
|
499
499
|
t.body = {
|
|
500
500
|
data: await U(e, d),
|
|
@@ -512,9 +512,9 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
512
512
|
t.body = {
|
|
513
513
|
data: await e.plugin("rewind").service("diff").between(Number(t.params.id), n ? Number(n) : void 0)
|
|
514
514
|
};
|
|
515
|
-
} catch (
|
|
515
|
+
} catch (i) {
|
|
516
516
|
return t.badRequest(
|
|
517
|
-
|
|
517
|
+
i instanceof Error ? i.message : "Could not compare these versions."
|
|
518
518
|
);
|
|
519
519
|
}
|
|
520
520
|
},
|
|
@@ -527,6 +527,23 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
527
527
|
return t.badRequest(n instanceof Error ? n.message : "Preview failed.");
|
|
528
528
|
}
|
|
529
529
|
},
|
|
530
|
+
/**
|
|
531
|
+
* `pinned` is read from the body rather than inferred as a toggle, so that two
|
|
532
|
+
* editors with the panel open cannot flip the same version past each other:
|
|
533
|
+
* each request states the state it wants, and the last one wins visibly.
|
|
534
|
+
*/
|
|
535
|
+
async pin(t) {
|
|
536
|
+
const { pinned: n } = t.request.body ?? {};
|
|
537
|
+
if (typeof n != "boolean")
|
|
538
|
+
return t.badRequest("pinned must be true or false.");
|
|
539
|
+
try {
|
|
540
|
+
t.body = {
|
|
541
|
+
data: await e.plugin("rewind").service("pin").set(Number(t.params.id), n)
|
|
542
|
+
};
|
|
543
|
+
} catch (i) {
|
|
544
|
+
return t.notFound(i instanceof Error ? i.message : "No such version.");
|
|
545
|
+
}
|
|
546
|
+
},
|
|
530
547
|
async restore(t) {
|
|
531
548
|
try {
|
|
532
549
|
t.body = {
|
|
@@ -539,7 +556,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
539
556
|
}), U = async (e, t) => {
|
|
540
557
|
const n = [...new Set(t.map((a) => a.userId).filter(Boolean))];
|
|
541
558
|
if (n.length === 0) return t.map((a) => ({ ...a, user: null }));
|
|
542
|
-
const
|
|
559
|
+
const i = await e.db.query("admin::user").findMany({ select: ["id", "firstname", "lastname", "email"], where: { id: n } }), o = new Map(i.map((a) => [a.id, a]));
|
|
543
560
|
return t.map((a) => {
|
|
544
561
|
const s = o.get(a.userId);
|
|
545
562
|
return {
|
|
@@ -551,7 +568,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
551
568
|
};
|
|
552
569
|
});
|
|
553
570
|
}, Re = {
|
|
554
|
-
version:
|
|
571
|
+
version: Ee
|
|
555
572
|
}, $e = {
|
|
556
573
|
type: "admin",
|
|
557
574
|
routes: [
|
|
@@ -579,6 +596,12 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
579
596
|
handler: "version.preview",
|
|
580
597
|
config: { policies: ["admin::isAuthenticatedAdmin"] }
|
|
581
598
|
},
|
|
599
|
+
{
|
|
600
|
+
method: "PUT",
|
|
601
|
+
path: "/versions/:id/pin",
|
|
602
|
+
handler: "version.pin",
|
|
603
|
+
config: { policies: ["admin::isAuthenticatedAdmin"] }
|
|
604
|
+
},
|
|
582
605
|
{
|
|
583
606
|
method: "POST",
|
|
584
607
|
path: "/versions/:id/restore",
|
|
@@ -586,31 +609,31 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
586
609
|
config: { policies: ["admin::isAuthenticatedAdmin"] }
|
|
587
610
|
}
|
|
588
611
|
]
|
|
589
|
-
},
|
|
612
|
+
}, Ne = {
|
|
590
613
|
admin: $e
|
|
591
|
-
}, B = (e) => e.match(/\S+\s*/g) ?? [], L = 2500,
|
|
592
|
-
const n = B(e),
|
|
593
|
-
if (n.length > L ||
|
|
614
|
+
}, B = (e) => e.match(/\S+\s*/g) ?? [], L = 2500, Fe = (e, t) => {
|
|
615
|
+
const n = B(e), i = B(t);
|
|
616
|
+
if (n.length > L || i.length > L)
|
|
594
617
|
return [
|
|
595
618
|
{ op: "removed", value: e },
|
|
596
619
|
{ op: "added", value: t }
|
|
597
620
|
];
|
|
598
621
|
const o = Array.from(
|
|
599
622
|
{ length: n.length + 1 },
|
|
600
|
-
() => new Array(
|
|
623
|
+
() => new Array(i.length + 1).fill(0)
|
|
601
624
|
);
|
|
602
625
|
for (let l = n.length - 1; l >= 0; l -= 1)
|
|
603
|
-
for (let d =
|
|
604
|
-
o[l][d] = n[l] ===
|
|
626
|
+
for (let d = i.length - 1; d >= 0; d -= 1)
|
|
627
|
+
o[l][d] = n[l] === i[d] ? o[l + 1][d + 1] + 1 : Math.max(o[l + 1][d], o[l][d + 1]);
|
|
605
628
|
const a = [], s = (l, d) => {
|
|
606
629
|
const p = a[a.length - 1];
|
|
607
630
|
p && p.op === l ? p.value += d : a.push({ op: l, value: d });
|
|
608
631
|
};
|
|
609
|
-
let
|
|
610
|
-
for (;
|
|
611
|
-
n[
|
|
612
|
-
for (;
|
|
613
|
-
for (; c <
|
|
632
|
+
let r = 0, c = 0;
|
|
633
|
+
for (; r < n.length && c < i.length; )
|
|
634
|
+
n[r] === i[c] ? (s("equal", n[r]), r += 1, c += 1) : o[r + 1][c] >= o[r][c + 1] ? (s("removed", n[r]), r += 1) : (s("added", i[c]), c += 1);
|
|
635
|
+
for (; r < n.length; ) s("removed", n[r++]);
|
|
636
|
+
for (; c < i.length; ) s("added", i[c++]);
|
|
614
637
|
return a;
|
|
615
638
|
}, _e = /* @__PURE__ */ new Set([
|
|
616
639
|
"id",
|
|
@@ -620,19 +643,19 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
620
643
|
"level",
|
|
621
644
|
"language",
|
|
622
645
|
"url"
|
|
623
|
-
]),
|
|
646
|
+
]), q = (e, t = 0) => t > 12 || e == null ? "" : typeof e == "string" ? e : typeof e == "boolean" ? "" : typeof e == "number" ? String(e) : Array.isArray(e) ? e.map((n) => q(n, t + 1)).filter(Boolean).join(" ") : typeof e == "object" ? Object.entries(e).filter(([n]) => !_e.has(n)).map(([, n]) => q(n, t + 1)).filter(Boolean).join(" ") : "", z = (e) => e.replace(/\s+/g, " ").trim(), je = 6, Ce = (e, t = je) => {
|
|
624
647
|
const n = [];
|
|
625
|
-
return e.forEach((
|
|
626
|
-
if (
|
|
627
|
-
n.push(
|
|
648
|
+
return e.forEach((i, o) => {
|
|
649
|
+
if (i.op !== "equal") {
|
|
650
|
+
n.push(i);
|
|
628
651
|
return;
|
|
629
652
|
}
|
|
630
|
-
const a =
|
|
653
|
+
const a = i.value.match(/\S+\s*/g) ?? [];
|
|
631
654
|
if (a.length <= t * 2 + 1) {
|
|
632
|
-
n.push(
|
|
655
|
+
n.push(i);
|
|
633
656
|
return;
|
|
634
657
|
}
|
|
635
|
-
const s = o === 0,
|
|
658
|
+
const s = o === 0, r = o === e.length - 1, c = s ? [] : a.slice(0, t), l = r ? [] : a.slice(-t);
|
|
636
659
|
c.length && n.push({ op: "equal", value: c.join("") }), n.push({ op: "equal", value: " … " }), l.length && n.push({ op: "equal", value: l.join("") });
|
|
637
660
|
}), n;
|
|
638
661
|
}, Me = /* @__PURE__ */ new Set([
|
|
@@ -650,7 +673,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
650
673
|
"uid",
|
|
651
674
|
"email",
|
|
652
675
|
"password"
|
|
653
|
-
]), Ue = (e, t) => e === "text" || e === "richtext" || e === "blocks" || e === "json" || e === "customField" || e === "component" || e === "dynamiczone" || e === "string" && typeof t == "string" && t.length > 80,
|
|
676
|
+
]), Ue = (e, t) => e === "text" || e === "richtext" || e === "blocks" || e === "json" || e === "customField" || e === "component" || e === "dynamiczone" || e === "string" && typeof t == "string" && t.length > 80, k = (e) => `${e.targetUid}:${e.documentId ?? e.id}`, Be = ({ strapi: e }) => {
|
|
654
677
|
const t = (o) => o && {
|
|
655
678
|
id: o.id,
|
|
656
679
|
label: o.label ?? null,
|
|
@@ -660,7 +683,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
660
683
|
const a = await e.db.query(v).findOne({ where: { id: o } });
|
|
661
684
|
if (!a) throw new Error(`No version with id ${o}`);
|
|
662
685
|
return a;
|
|
663
|
-
},
|
|
686
|
+
}, i = async (o) => {
|
|
664
687
|
const [a] = await e.db.query(v).findMany({
|
|
665
688
|
where: {
|
|
666
689
|
contentType: o.contentType,
|
|
@@ -683,7 +706,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
683
706
|
* change?", is what `restore.preview()` already answers.
|
|
684
707
|
*/
|
|
685
708
|
async between(o, a) {
|
|
686
|
-
const s = await n(o),
|
|
709
|
+
const s = await n(o), r = a ? await n(a) : await i(s), c = e.getModel(s.contentType)?.attributes, l = s.data ?? {}, d = r?.data ?? {}, p = s.relations ?? {}, b = r?.relations ?? {}, m = [];
|
|
687
710
|
for (const u of /* @__PURE__ */ new Set([...Object.keys(d), ...Object.keys(l)])) {
|
|
688
711
|
const h = d[u], f = l[u];
|
|
689
712
|
if (JSON.stringify(h) === JSON.stringify(f)) continue;
|
|
@@ -692,7 +715,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
692
715
|
m.push({ field: u, type: w, customField: T, kind: g, before: h, after: f });
|
|
693
716
|
continue;
|
|
694
717
|
}
|
|
695
|
-
const D = z(
|
|
718
|
+
const D = z(q(h)), A = z(q(f));
|
|
696
719
|
if (D === A) {
|
|
697
720
|
m.push({ field: u, type: w, customField: T, kind: g, before: void 0, after: void 0 });
|
|
698
721
|
continue;
|
|
@@ -705,11 +728,11 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
705
728
|
// Collapsed here rather than in the panel: sending an entire article
|
|
706
729
|
// to highlight one word wastes the payload as well as the reader's
|
|
707
730
|
// attention.
|
|
708
|
-
spans: Ce(
|
|
731
|
+
spans: Ce(Fe(D, A))
|
|
709
732
|
});
|
|
710
733
|
}
|
|
711
734
|
for (const u of /* @__PURE__ */ new Set([...Object.keys(b), ...Object.keys(p)])) {
|
|
712
|
-
const h = b[u] ?? [], f = p[u] ?? [], w = new Set(h.map(
|
|
735
|
+
const h = b[u] ?? [], f = p[u] ?? [], w = new Set(h.map(k)), T = new Set(f.map(k)), g = f.filter((A) => !w.has(k(A))), D = h.filter((A) => !T.has(k(A)));
|
|
713
736
|
g.length === 0 && D.length === 0 || m.push({
|
|
714
737
|
field: u,
|
|
715
738
|
type: c?.[u]?.type ?? "relation",
|
|
@@ -719,7 +742,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
719
742
|
});
|
|
720
743
|
}
|
|
721
744
|
return {
|
|
722
|
-
from:
|
|
745
|
+
from: r ? t(r) : null,
|
|
723
746
|
to: t(s),
|
|
724
747
|
changes: m,
|
|
725
748
|
identical: m.length === 0
|
|
@@ -733,52 +756,61 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
733
756
|
key: `lock:${n}`
|
|
734
757
|
});
|
|
735
758
|
return {
|
|
736
|
-
async acquire(n,
|
|
759
|
+
async acquire(n, i) {
|
|
737
760
|
const o = await e.store.get(t(n));
|
|
738
761
|
return o && o.expires > Date.now() ? !1 : (await e.store.set({
|
|
739
762
|
...t(n),
|
|
740
|
-
value: { expires: Date.now() +
|
|
763
|
+
value: { expires: Date.now() + i }
|
|
741
764
|
}), !0);
|
|
742
765
|
},
|
|
743
766
|
async release(n) {
|
|
744
767
|
await e.store.set({ ...t(n), value: { expires: 0 } });
|
|
745
768
|
}
|
|
746
769
|
};
|
|
747
|
-
},
|
|
770
|
+
}, ze = ({ strapi: e }) => ({
|
|
771
|
+
async set(t, n) {
|
|
772
|
+
if (!Number.isInteger(t))
|
|
773
|
+
throw new Error("A version id is required.");
|
|
774
|
+
const i = await e.db.query(v).findOne({ where: { id: t }, select: ["id", "pinned"] });
|
|
775
|
+
if (!i)
|
|
776
|
+
throw new Error(`No version with id ${t}`);
|
|
777
|
+
return !!i.pinned === n ? { id: t, pinned: n } : (await e.db.query(v).update({ where: { id: t }, data: { pinned: n } }), { id: t, pinned: n });
|
|
778
|
+
}
|
|
779
|
+
}), K = {
|
|
748
780
|
keepAllDays: 7,
|
|
749
781
|
dailyUntilDays: 30,
|
|
750
782
|
maxAgeDays: 365,
|
|
751
783
|
keepAnchors: !0
|
|
752
|
-
},
|
|
784
|
+
}, Pe = /* @__PURE__ */ new Set(["publish", "unpublish", "discardDraft", "restore"]), G = 24 * 60 * 60 * 1e3, Ye = (e, t) => (t.getTime() - new Date(e).getTime()) / G, He = (e) => e.toISOString().slice(0, 10), We = (e) => {
|
|
753
785
|
const t = new Date(Date.UTC(e.getUTCFullYear(), e.getUTCMonth(), e.getUTCDate())), n = t.getUTCDay() || 7;
|
|
754
786
|
t.setUTCDate(t.getUTCDate() + 4 - n);
|
|
755
|
-
const
|
|
787
|
+
const i = new Date(Date.UTC(t.getUTCFullYear(), 0, 1)), o = Math.ceil(((t.getTime() - i.getTime()) / G + 1) / 7);
|
|
756
788
|
return `${t.getUTCFullYear()}-W${o}`;
|
|
757
|
-
},
|
|
758
|
-
const
|
|
759
|
-
(s,
|
|
789
|
+
}, Ke = (e, t, n = K) => {
|
|
790
|
+
const i = /* @__PURE__ */ new Map(), o = [], a = [...e].sort(
|
|
791
|
+
(s, r) => new Date(r.createdAt).getTime() - new Date(s.createdAt).getTime()
|
|
760
792
|
);
|
|
761
793
|
for (const s of a) {
|
|
762
|
-
if (s.pinned || n.keepAnchors &&
|
|
763
|
-
const
|
|
764
|
-
if (
|
|
765
|
-
if (
|
|
794
|
+
if (s.pinned || n.keepAnchors && Pe.has(s.origin)) continue;
|
|
795
|
+
const r = Ye(s.createdAt, t);
|
|
796
|
+
if (r <= n.keepAllDays) continue;
|
|
797
|
+
if (r > n.maxAgeDays) {
|
|
766
798
|
o.push(s.id);
|
|
767
799
|
continue;
|
|
768
800
|
}
|
|
769
|
-
const c = new Date(s.createdAt), l =
|
|
770
|
-
|
|
801
|
+
const c = new Date(s.createdAt), l = r <= n.dailyUntilDays ? `d:${He(c)}` : `w:${We(c)}`;
|
|
802
|
+
i.has(l) ? o.push(s.id) : i.set(l, s);
|
|
771
803
|
}
|
|
772
804
|
return o;
|
|
773
|
-
}, P = 500,
|
|
774
|
-
const t = () => e.db.query(v), n = async (
|
|
805
|
+
}, P = 500, Ge = ({ strapi: e }) => {
|
|
806
|
+
const t = () => e.db.query(v), n = async (i) => {
|
|
775
807
|
const o = await t().findMany({
|
|
776
808
|
select: ["contentType", "relatedDocumentId", "locale"],
|
|
777
|
-
where: { createdAt: { $lt:
|
|
809
|
+
where: { createdAt: { $lt: i } }
|
|
778
810
|
}), a = /* @__PURE__ */ new Map();
|
|
779
811
|
for (const s of o) {
|
|
780
|
-
const
|
|
781
|
-
a.has(
|
|
812
|
+
const r = `${s.contentType}|${s.relatedDocumentId}|${s.locale ?? ""}`;
|
|
813
|
+
a.has(r) || a.set(r, {
|
|
782
814
|
contentType: s.contentType,
|
|
783
815
|
relatedDocumentId: s.relatedDocumentId,
|
|
784
816
|
locale: s.locale ?? null
|
|
@@ -793,13 +825,13 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
793
825
|
* Returns what it did rather than logging and forgetting, so the cron can
|
|
794
826
|
* report it and a test can assert on it.
|
|
795
827
|
*/
|
|
796
|
-
async prune(
|
|
828
|
+
async prune(i = /* @__PURE__ */ new Date()) {
|
|
797
829
|
const o = e.config.get("plugin::rewind"), a = { ...K, ...o?.retention };
|
|
798
830
|
if (o?.retention?.enabled === !1)
|
|
799
831
|
return { scanned: 0, deleted: 0, documents: 0 };
|
|
800
|
-
const s = new Date(
|
|
832
|
+
const s = new Date(i.getTime() - a.keepAllDays * 24 * 60 * 60 * 1e3), r = await n(s);
|
|
801
833
|
let c = 0, l = 0;
|
|
802
|
-
for (const d of
|
|
834
|
+
for (const d of r) {
|
|
803
835
|
const p = await t().findMany({
|
|
804
836
|
select: ["id", "createdAt", "origin", "pinned"],
|
|
805
837
|
where: {
|
|
@@ -809,22 +841,22 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
809
841
|
}
|
|
810
842
|
});
|
|
811
843
|
c += p.length;
|
|
812
|
-
const b =
|
|
844
|
+
const b = Ke(p, i, a);
|
|
813
845
|
for (let m = 0; m < b.length; m += P) {
|
|
814
846
|
const u = b.slice(m, m + P);
|
|
815
847
|
await t().deleteMany({ where: { id: { $in: u } } }), l += u.length;
|
|
816
848
|
}
|
|
817
849
|
}
|
|
818
|
-
return { scanned: c, deleted: l, documents:
|
|
850
|
+
return { scanned: c, deleted: l, documents: r.length };
|
|
819
851
|
}
|
|
820
852
|
};
|
|
821
|
-
},
|
|
853
|
+
}, Ve = ({ strapi: e }) => {
|
|
822
854
|
const t = (s) => e.getModel(s), n = async (s) => {
|
|
823
|
-
const
|
|
824
|
-
if (!
|
|
855
|
+
const r = await e.db.query(v).findOne({ where: { id: s } });
|
|
856
|
+
if (!r)
|
|
825
857
|
throw new Error(`No version with id ${s}`);
|
|
826
|
-
return
|
|
827
|
-
},
|
|
858
|
+
return r;
|
|
859
|
+
}, i = (s) => s?.pluginOptions?.i18n?.localized === !0, o = (s) => e.contentTypes[s]?.pluginOptions?.i18n?.localized === !0, a = async (s) => s.targetUid === "plugin::upload.file" ? !!await e.db.query("plugin::upload.file").findOne({ where: { id: s.id } }) : !!await e.db.query(s.targetUid).findOne({
|
|
828
860
|
where: {
|
|
829
861
|
documentId: s.documentId,
|
|
830
862
|
...s.locale ? { locale: s.locale } : {}
|
|
@@ -840,11 +872,11 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
840
872
|
* version of a document quietly rewrites a field in every other language.
|
|
841
873
|
*/
|
|
842
874
|
async preview(s) {
|
|
843
|
-
const
|
|
875
|
+
const r = await n(s), c = r.contentType, l = t(c).attributes, d = r.schemaSnapshot?.attributes ?? {}, p = r.data ?? {}, b = r.relations ?? {}, m = Object.keys(l).filter((y) => !S.includes(y)), u = Object.keys(d), h = u.filter((y) => !m.includes(y)), f = m.filter((y) => !u.includes(y)), w = u.filter((y) => m.includes(y)), T = w.filter((y) => $(l[y])), g = w.filter((y) => !T.includes(y)), D = o(c) ? g.filter((y) => !i(l[y])) : [], A = [], N = [];
|
|
844
876
|
for (const [y, X] of Object.entries(b))
|
|
845
877
|
if (g.includes(y))
|
|
846
|
-
for (const
|
|
847
|
-
await a(
|
|
878
|
+
for (const O of X)
|
|
879
|
+
await a(O) || (O.targetUid === "plugin::upload.file" ? N.push(O) : A.push(O));
|
|
848
880
|
const V = D.length ? await e.plugin("i18n")?.service("locales").find() : [];
|
|
849
881
|
return {
|
|
850
882
|
fieldsToRestore: g.filter((y) => y in p),
|
|
@@ -853,7 +885,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
853
885
|
crossLocaleFields: D,
|
|
854
886
|
unsupportedFields: T,
|
|
855
887
|
brokenRelations: A,
|
|
856
|
-
missingMedia:
|
|
888
|
+
missingMedia: N,
|
|
857
889
|
affectedLocales: V.map((y) => y.code)
|
|
858
890
|
};
|
|
859
891
|
},
|
|
@@ -863,7 +895,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
863
895
|
* The document lands in "modified" and publishing stays a deliberate act -
|
|
864
896
|
* restoring must never change what the public sees on its own.
|
|
865
897
|
*/
|
|
866
|
-
async apply(s,
|
|
898
|
+
async apply(s, r) {
|
|
867
899
|
const c = await n(s), l = c.contentType, d = await this.preview(s), p = [];
|
|
868
900
|
await e.plugin("rewind").service("snapshot").capture(
|
|
869
901
|
{
|
|
@@ -873,7 +905,7 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
873
905
|
origin: "restore",
|
|
874
906
|
before: null
|
|
875
907
|
},
|
|
876
|
-
|
|
908
|
+
r
|
|
877
909
|
);
|
|
878
910
|
const b = {}, m = c.data ?? {}, u = c.relations ?? {};
|
|
879
911
|
for (const f of d.fieldsToRestore)
|
|
@@ -910,21 +942,22 @@ const Z = ["id", "documentId", "locale"], S = [
|
|
|
910
942
|
}), { documentId: c.relatedDocumentId, warnings: p };
|
|
911
943
|
}
|
|
912
944
|
};
|
|
913
|
-
},
|
|
945
|
+
}, Xe = {
|
|
914
946
|
snapshot: ie,
|
|
915
|
-
restore:
|
|
947
|
+
restore: Ve,
|
|
916
948
|
diff: Be,
|
|
917
|
-
retention:
|
|
918
|
-
lock: Le
|
|
919
|
-
|
|
949
|
+
retention: Ge,
|
|
950
|
+
lock: Le,
|
|
951
|
+
pin: ze
|
|
952
|
+
}, Qe = {
|
|
920
953
|
bootstrap: me,
|
|
921
954
|
destroy: Se,
|
|
922
955
|
config: we,
|
|
923
|
-
contentTypes:
|
|
956
|
+
contentTypes: ke,
|
|
924
957
|
controllers: Re,
|
|
925
|
-
routes:
|
|
926
|
-
services:
|
|
958
|
+
routes: Ne,
|
|
959
|
+
services: Xe
|
|
927
960
|
};
|
|
928
961
|
export {
|
|
929
|
-
|
|
962
|
+
Qe as default
|
|
930
963
|
};
|