@strapi/content-releases 0.0.0-experimental.defd8568ae03ef8d52f86e1f3541979f953c3941 → 0.0.0-experimental.e02b4637b3906c6d31048d00600d09a23a0edc3d
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/LICENSE +17 -1
- package/dist/_chunks/{App-O0ZO-S35.mjs → App-BA2xDdy0.mjs} +431 -400
- package/dist/_chunks/App-BA2xDdy0.mjs.map +1 -0
- package/dist/_chunks/{App-C0DlH0im.js → App-D4Wira1X.js} +434 -405
- package/dist/_chunks/App-D4Wira1X.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-DAHdUpAA.js → PurchaseContentReleases-Be3acS2L.js} +4 -3
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-Ex09YpKR.mjs → PurchaseContentReleases-_MxP6-Dt.mjs} +5 -4
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-BAlbMWpw.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-BAlbMWpw.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-xhFyRXCM.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-xhFyRXCM.js.map +1 -0
- package/dist/_chunks/{en-DtFJ5ViE.js → en-CmYoEnA7.js} +9 -2
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/{en-B9Ur3VsE.mjs → en-D0yVZFqf.mjs} +9 -2
- package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
- package/dist/_chunks/{index-DjDPK8kb.mjs → index-CCFFG3Zs.mjs} +723 -599
- package/dist/_chunks/index-CCFFG3Zs.mjs.map +1 -0
- package/dist/_chunks/{index-DoZNNtsb.js → index-DxkQGp4N.js} +714 -592
- package/dist/_chunks/index-DxkQGp4N.js.map +1 -0
- package/dist/_chunks/schemas-BE1LxE9J.js +62 -0
- package/dist/_chunks/schemas-BE1LxE9J.js.map +1 -0
- package/dist/_chunks/schemas-DdA2ic2U.mjs +44 -0
- package/dist/_chunks/schemas-DdA2ic2U.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +2 -2
- package/dist/admin/src/components/ReleaseAction.d.ts +1 -1
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +3 -3
- package/dist/admin/src/components/{CMReleasesContainer.d.ts → ReleaseActionModal.d.ts} +3 -1
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +3 -2
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +18 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -0
- package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
- package/dist/admin/src/services/release.d.ts +53 -370
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/time.d.ts +9 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +782 -580
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +783 -581
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/constants.d.ts +11 -2
- package/dist/server/src/constants.d.ts.map +1 -1
- package/dist/server/src/content-types/index.d.ts +3 -5
- package/dist/server/src/content-types/index.d.ts.map +1 -1
- package/dist/server/src/content-types/release-action/index.d.ts +3 -5
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -1
- package/dist/server/src/content-types/release-action/schema.d.ts +3 -5
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +6 -1
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/controllers/release-action.d.ts.map +1 -1
- package/dist/server/src/controllers/release.d.ts +7 -1
- package/dist/server/src/controllers/release.d.ts.map +1 -1
- package/dist/server/src/controllers/settings.d.ts +11 -0
- package/dist/server/src/controllers/settings.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +7 -1
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/settings.d.ts +3 -0
- package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +68 -49
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/middlewares/documents.d.ts +6 -0
- package/dist/server/src/middlewares/documents.d.ts.map +1 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
- package/dist/server/src/migrations/index.d.ts.map +1 -1
- package/dist/server/src/register.d.ts.map +1 -1
- package/dist/server/src/routes/index.d.ts +16 -0
- package/dist/server/src/routes/index.d.ts.map +1 -1
- package/dist/server/src/routes/release.d.ts.map +1 -1
- package/dist/server/src/routes/settings.d.ts +18 -0
- package/dist/server/src/routes/settings.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +40 -38
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/release-action.d.ts +38 -0
- package/dist/server/src/services/release-action.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +6 -41
- package/dist/server/src/services/release.d.ts.map +1 -1
- package/dist/server/src/services/settings.d.ts +13 -0
- package/dist/server/src/services/settings.d.ts.map +1 -0
- package/dist/server/src/services/validation.d.ts +1 -1
- package/dist/server/src/services/validation.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +29 -8
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/shared/contracts/release-actions.d.ts +9 -10
- package/dist/shared/contracts/release-actions.d.ts.map +1 -1
- package/dist/shared/contracts/releases.d.ts +9 -7
- package/dist/shared/contracts/releases.d.ts.map +1 -1
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/package.json +19 -19
- package/dist/_chunks/App-C0DlH0im.js.map +0 -1
- package/dist/_chunks/App-O0ZO-S35.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-DAHdUpAA.js.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-Ex09YpKR.mjs.map +0 -1
- package/dist/_chunks/en-B9Ur3VsE.mjs.map +0 -1
- package/dist/_chunks/en-DtFJ5ViE.js.map +0 -1
- package/dist/_chunks/index-DjDPK8kb.mjs.map +0 -1
- package/dist/_chunks/index-DoZNNtsb.js.map +0 -1
- package/dist/admin/src/services/axios.d.ts +0 -29
- package/dist/shared/validation-schemas.d.ts +0 -2
- package/dist/shared/validation-schemas.d.ts.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { contentTypes as contentTypes$1, async, setCreatorFields, errors,
|
|
1
|
+
import { contentTypes as contentTypes$1, async, setCreatorFields, errors, yup as yup$1, validateYupSchema } from "@strapi/utils";
|
|
2
2
|
import isEqual from "lodash/isEqual";
|
|
3
3
|
import { difference, keys } from "lodash";
|
|
4
4
|
import _ from "lodash/fp";
|
|
@@ -48,6 +48,23 @@ const ACTIONS = [
|
|
|
48
48
|
displayName: "Add an entry to a release",
|
|
49
49
|
uid: "create-action",
|
|
50
50
|
pluginName: "content-releases"
|
|
51
|
+
},
|
|
52
|
+
// Settings
|
|
53
|
+
{
|
|
54
|
+
uid: "settings.read",
|
|
55
|
+
section: "settings",
|
|
56
|
+
displayName: "Read",
|
|
57
|
+
category: "content releases",
|
|
58
|
+
subCategory: "options",
|
|
59
|
+
pluginName: "content-releases"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
uid: "settings.update",
|
|
63
|
+
section: "settings",
|
|
64
|
+
displayName: "Edit",
|
|
65
|
+
category: "content releases",
|
|
66
|
+
subCategory: "options",
|
|
67
|
+
pluginName: "content-releases"
|
|
51
68
|
}
|
|
52
69
|
];
|
|
53
70
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
@@ -56,16 +73,13 @@ const ALLOWED_WEBHOOK_EVENTS = {
|
|
|
56
73
|
const getService = (name, { strapi: strapi2 }) => {
|
|
57
74
|
return strapi2.plugin("content-releases").service(name);
|
|
58
75
|
};
|
|
59
|
-
const
|
|
76
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
60
77
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
61
|
-
const populate = await populateBuilderService(
|
|
62
|
-
const entry = await strapi2
|
|
63
|
-
|
|
64
|
-
populate
|
|
65
|
-
});
|
|
66
|
-
return entry;
|
|
78
|
+
const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
|
|
79
|
+
const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
|
|
80
|
+
return isEntryValid(contentType, entry, { strapi: strapi2 });
|
|
67
81
|
};
|
|
68
|
-
const
|
|
82
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
69
83
|
try {
|
|
70
84
|
await strapi2.entityValidator.validateEntityCreation(
|
|
71
85
|
strapi2.getModel(contentTypeUid),
|
|
@@ -79,6 +93,38 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) =
|
|
|
79
93
|
return false;
|
|
80
94
|
}
|
|
81
95
|
};
|
|
96
|
+
const getEntry = async ({
|
|
97
|
+
contentType,
|
|
98
|
+
documentId,
|
|
99
|
+
locale,
|
|
100
|
+
populate,
|
|
101
|
+
status = "draft"
|
|
102
|
+
}, { strapi: strapi2 }) => {
|
|
103
|
+
if (documentId) {
|
|
104
|
+
return strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
|
|
105
|
+
}
|
|
106
|
+
return strapi2.documents(contentType).findFirst({ locale, populate, status });
|
|
107
|
+
};
|
|
108
|
+
const getEntryStatus = async (contentType, entry) => {
|
|
109
|
+
if (entry.publishedAt) {
|
|
110
|
+
return "published";
|
|
111
|
+
}
|
|
112
|
+
const publishedEntry = await strapi.documents(contentType).findOne({
|
|
113
|
+
documentId: entry.documentId,
|
|
114
|
+
locale: entry.locale,
|
|
115
|
+
status: "published",
|
|
116
|
+
fields: ["updatedAt"]
|
|
117
|
+
});
|
|
118
|
+
if (!publishedEntry) {
|
|
119
|
+
return "draft";
|
|
120
|
+
}
|
|
121
|
+
const entryUpdatedAt = new Date(entry.updatedAt).getTime();
|
|
122
|
+
const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
|
|
123
|
+
if (entryUpdatedAt > publishedEntryUpdatedAt) {
|
|
124
|
+
return "modified";
|
|
125
|
+
}
|
|
126
|
+
return "published";
|
|
127
|
+
};
|
|
82
128
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
83
129
|
oldContentTypes,
|
|
84
130
|
contentTypes: contentTypes2
|
|
@@ -124,20 +170,22 @@ async function migrateIsValidAndStatusReleases() {
|
|
|
124
170
|
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
125
171
|
for (const action of notValidatedActions) {
|
|
126
172
|
if (action.entry) {
|
|
127
|
-
const
|
|
128
|
-
|
|
173
|
+
const isEntryValid2 = getDraftEntryValidStatus(
|
|
174
|
+
{
|
|
175
|
+
contentType: action.contentType,
|
|
176
|
+
documentId: action.entryDocumentId,
|
|
177
|
+
locale: action.locale
|
|
178
|
+
},
|
|
179
|
+
{ strapi }
|
|
180
|
+
);
|
|
181
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
182
|
+
where: {
|
|
183
|
+
id: action.id
|
|
184
|
+
},
|
|
185
|
+
data: {
|
|
186
|
+
isEntryValid: isEntryValid2
|
|
187
|
+
}
|
|
129
188
|
});
|
|
130
|
-
if (populatedEntry) {
|
|
131
|
-
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
132
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
133
|
-
where: {
|
|
134
|
-
id: action.id
|
|
135
|
-
},
|
|
136
|
-
data: {
|
|
137
|
-
isEntryValid
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
189
|
}
|
|
142
190
|
}
|
|
143
191
|
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
@@ -181,24 +229,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
|
|
|
181
229
|
}
|
|
182
230
|
});
|
|
183
231
|
await async.map(actions, async (action) => {
|
|
184
|
-
if (action.entry && action.release) {
|
|
185
|
-
const
|
|
186
|
-
|
|
232
|
+
if (action.entry && action.release && action.type === "publish") {
|
|
233
|
+
const isEntryValid2 = await getDraftEntryValidStatus(
|
|
234
|
+
{
|
|
235
|
+
contentType: contentTypeUID,
|
|
236
|
+
documentId: action.entryDocumentId,
|
|
237
|
+
locale: action.locale
|
|
238
|
+
},
|
|
239
|
+
{ strapi }
|
|
240
|
+
);
|
|
241
|
+
releasesAffected.add(action.release.id);
|
|
242
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
243
|
+
where: {
|
|
244
|
+
id: action.id
|
|
245
|
+
},
|
|
246
|
+
data: {
|
|
247
|
+
isEntryValid: isEntryValid2
|
|
248
|
+
}
|
|
187
249
|
});
|
|
188
|
-
if (populatedEntry) {
|
|
189
|
-
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
190
|
-
strapi
|
|
191
|
-
});
|
|
192
|
-
releasesAffected.add(action.release.id);
|
|
193
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
194
|
-
where: {
|
|
195
|
-
id: action.id
|
|
196
|
-
},
|
|
197
|
-
data: {
|
|
198
|
-
isEntryValid
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
250
|
}
|
|
203
251
|
});
|
|
204
252
|
}
|
|
@@ -255,9 +303,38 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
|
|
|
255
303
|
}
|
|
256
304
|
}
|
|
257
305
|
}
|
|
306
|
+
const addEntryDocumentToReleaseActions = {
|
|
307
|
+
name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
|
|
308
|
+
async up(trx, db) {
|
|
309
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
310
|
+
if (hasPolymorphicColumn) {
|
|
311
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
312
|
+
"strapi_release_actions",
|
|
313
|
+
"entry_document_id"
|
|
314
|
+
);
|
|
315
|
+
if (!hasEntryDocumentIdColumn) {
|
|
316
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
317
|
+
table.string("entry_document_id");
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
321
|
+
async.map(releaseActions, async (action) => {
|
|
322
|
+
const { target_type, target_id } = action;
|
|
323
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
324
|
+
if (entry) {
|
|
325
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
async down() {
|
|
331
|
+
throw new Error("not implemented");
|
|
332
|
+
}
|
|
333
|
+
};
|
|
258
334
|
const register = async ({ strapi: strapi2 }) => {
|
|
259
335
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
260
336
|
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
337
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
261
338
|
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
262
339
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
263
340
|
}
|
|
@@ -267,6 +344,104 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
267
344
|
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
268
345
|
}
|
|
269
346
|
};
|
|
347
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
348
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
349
|
+
where: {
|
|
350
|
+
actions: {
|
|
351
|
+
contentType,
|
|
352
|
+
entryDocumentId: entry.documentId,
|
|
353
|
+
locale: entry.locale
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
358
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
359
|
+
where: {
|
|
360
|
+
contentType,
|
|
361
|
+
entryDocumentId: entry.documentId,
|
|
362
|
+
locale: entry.locale
|
|
363
|
+
},
|
|
364
|
+
data: {
|
|
365
|
+
isEntryValid: entryStatus
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
for (const release2 of releases) {
|
|
369
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
373
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
374
|
+
where: {
|
|
375
|
+
actions: params
|
|
376
|
+
}
|
|
377
|
+
});
|
|
378
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
379
|
+
where: params
|
|
380
|
+
});
|
|
381
|
+
for (const release2 of releases) {
|
|
382
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
386
|
+
if (ctx.action !== "delete") {
|
|
387
|
+
return next();
|
|
388
|
+
}
|
|
389
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
390
|
+
return next();
|
|
391
|
+
}
|
|
392
|
+
const contentType = ctx.contentType.uid;
|
|
393
|
+
const { documentId, locale } = ctx.params;
|
|
394
|
+
const result = await next();
|
|
395
|
+
if (!result) {
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
400
|
+
contentType,
|
|
401
|
+
entryDocumentId: documentId,
|
|
402
|
+
...locale !== "*" && { locale }
|
|
403
|
+
});
|
|
404
|
+
} catch (error) {
|
|
405
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
406
|
+
error
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
return result;
|
|
410
|
+
};
|
|
411
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
412
|
+
if (ctx.action !== "update") {
|
|
413
|
+
return next();
|
|
414
|
+
}
|
|
415
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
416
|
+
return next();
|
|
417
|
+
}
|
|
418
|
+
const contentType = ctx.contentType.uid;
|
|
419
|
+
const result = await next();
|
|
420
|
+
if (!result) {
|
|
421
|
+
return result;
|
|
422
|
+
}
|
|
423
|
+
try {
|
|
424
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
425
|
+
} catch (error) {
|
|
426
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
427
|
+
error
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
return result;
|
|
431
|
+
};
|
|
432
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
433
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
434
|
+
where: {
|
|
435
|
+
actions: params
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
439
|
+
where: params
|
|
440
|
+
});
|
|
441
|
+
for (const release2 of releases) {
|
|
442
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
270
445
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
271
446
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
272
447
|
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
@@ -274,115 +449,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
274
449
|
);
|
|
275
450
|
strapi2.db.lifecycles.subscribe({
|
|
276
451
|
models: contentTypesWithDraftAndPublish,
|
|
277
|
-
async afterDelete(event) {
|
|
278
|
-
try {
|
|
279
|
-
const { model, result } = event;
|
|
280
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
281
|
-
const { id } = result;
|
|
282
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
283
|
-
where: {
|
|
284
|
-
actions: {
|
|
285
|
-
target_type: model.uid,
|
|
286
|
-
target_id: id
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
});
|
|
290
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
291
|
-
where: {
|
|
292
|
-
target_type: model.uid,
|
|
293
|
-
target_id: id
|
|
294
|
-
}
|
|
295
|
-
});
|
|
296
|
-
for (const release2 of releases) {
|
|
297
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
} catch (error) {
|
|
301
|
-
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
302
|
-
}
|
|
303
|
-
},
|
|
304
452
|
/**
|
|
305
|
-
* deleteMany
|
|
306
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
307
|
-
*/
|
|
308
|
-
async beforeDeleteMany(event) {
|
|
309
|
-
const { model, params } = event;
|
|
310
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
311
|
-
const { where } = params;
|
|
312
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
313
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
/**
|
|
317
|
-
* We delete the release actions related to deleted entries
|
|
318
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
453
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
319
454
|
*/
|
|
320
455
|
async afterDeleteMany(event) {
|
|
321
456
|
try {
|
|
322
|
-
const
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
target_id: {
|
|
330
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
336
|
-
where: {
|
|
337
|
-
target_type: model.uid,
|
|
338
|
-
target_id: {
|
|
339
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
340
|
-
}
|
|
341
|
-
}
|
|
457
|
+
const model = strapi2.getModel(event.model.uid);
|
|
458
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
459
|
+
const { where } = event.params;
|
|
460
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
461
|
+
contentType: model.uid,
|
|
462
|
+
locale: where.locale ?? null,
|
|
463
|
+
...where.documentId && { entryDocumentId: where.documentId }
|
|
342
464
|
});
|
|
343
|
-
for (const release2 of releases) {
|
|
344
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
345
|
-
}
|
|
346
465
|
}
|
|
347
466
|
} catch (error) {
|
|
348
467
|
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
349
468
|
error
|
|
350
469
|
});
|
|
351
470
|
}
|
|
352
|
-
},
|
|
353
|
-
async afterUpdate(event) {
|
|
354
|
-
try {
|
|
355
|
-
const { model, result } = event;
|
|
356
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
357
|
-
const isEntryValid = await getEntryValidStatus(model.uid, result, {
|
|
358
|
-
strapi: strapi2
|
|
359
|
-
});
|
|
360
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
361
|
-
where: {
|
|
362
|
-
target_type: model.uid,
|
|
363
|
-
target_id: result.id
|
|
364
|
-
},
|
|
365
|
-
data: {
|
|
366
|
-
isEntryValid
|
|
367
|
-
}
|
|
368
|
-
});
|
|
369
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
370
|
-
where: {
|
|
371
|
-
actions: {
|
|
372
|
-
target_type: model.uid,
|
|
373
|
-
target_id: result.id
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
});
|
|
377
|
-
for (const release2 of releases) {
|
|
378
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
} catch (error) {
|
|
382
|
-
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
383
|
-
}
|
|
384
471
|
}
|
|
385
472
|
});
|
|
473
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
474
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
386
475
|
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
387
476
|
strapi2.log.error(
|
|
388
477
|
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
@@ -474,15 +563,13 @@ const schema = {
|
|
|
474
563
|
enum: ["publish", "unpublish"],
|
|
475
564
|
required: true
|
|
476
565
|
},
|
|
477
|
-
entry: {
|
|
478
|
-
type: "relation",
|
|
479
|
-
relation: "morphToOne",
|
|
480
|
-
configurable: false
|
|
481
|
-
},
|
|
482
566
|
contentType: {
|
|
483
567
|
type: "string",
|
|
484
568
|
required: true
|
|
485
569
|
},
|
|
570
|
+
entryDocumentId: {
|
|
571
|
+
type: "string"
|
|
572
|
+
},
|
|
486
573
|
locale: {
|
|
487
574
|
type: "string"
|
|
488
575
|
},
|
|
@@ -504,18 +591,6 @@ const contentTypes = {
|
|
|
504
591
|
release: release$1,
|
|
505
592
|
"release-action": releaseAction$1
|
|
506
593
|
};
|
|
507
|
-
const getGroupName = (queryValue) => {
|
|
508
|
-
switch (queryValue) {
|
|
509
|
-
case "contentType":
|
|
510
|
-
return "contentType.displayName";
|
|
511
|
-
case "action":
|
|
512
|
-
return "type";
|
|
513
|
-
case "locale":
|
|
514
|
-
return _.getOr("No locale", "locale.name");
|
|
515
|
-
default:
|
|
516
|
-
return "contentType.displayName";
|
|
517
|
-
}
|
|
518
|
-
};
|
|
519
594
|
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
520
595
|
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
521
596
|
strapi2.eventHub.emit(event, {
|
|
@@ -524,93 +599,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
524
599
|
release: release2
|
|
525
600
|
});
|
|
526
601
|
};
|
|
527
|
-
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
528
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
529
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
530
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
531
|
-
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
532
|
-
try {
|
|
533
|
-
if (actionType === "publish") {
|
|
534
|
-
await entityManagerService.publish(entry, uid);
|
|
535
|
-
} else {
|
|
536
|
-
await entityManagerService.unpublish(entry, uid);
|
|
537
|
-
}
|
|
538
|
-
} catch (error) {
|
|
539
|
-
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
540
|
-
;
|
|
541
|
-
else {
|
|
542
|
-
throw error;
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
547
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
548
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
549
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
550
|
-
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
551
|
-
filters: {
|
|
552
|
-
id: {
|
|
553
|
-
$in: entriesToPublishIds
|
|
554
|
-
}
|
|
555
|
-
},
|
|
556
|
-
populate
|
|
557
|
-
});
|
|
558
|
-
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
559
|
-
filters: {
|
|
560
|
-
id: {
|
|
561
|
-
$in: entriestoUnpublishIds
|
|
562
|
-
}
|
|
563
|
-
},
|
|
564
|
-
populate
|
|
565
|
-
});
|
|
566
|
-
if (entriesToPublish.length > 0) {
|
|
567
|
-
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
568
|
-
}
|
|
569
|
-
if (entriesToUnpublish.length > 0) {
|
|
570
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
571
|
-
}
|
|
572
|
-
};
|
|
573
602
|
const getFormattedActions = async (releaseId) => {
|
|
574
603
|
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
575
604
|
where: {
|
|
576
605
|
release: {
|
|
577
606
|
id: releaseId
|
|
578
607
|
}
|
|
579
|
-
},
|
|
580
|
-
populate: {
|
|
581
|
-
entry: {
|
|
582
|
-
fields: ["id"]
|
|
583
|
-
}
|
|
584
608
|
}
|
|
585
609
|
});
|
|
586
610
|
if (actions.length === 0) {
|
|
587
611
|
throw new errors.ValidationError("No entries to publish");
|
|
588
612
|
}
|
|
589
|
-
const
|
|
590
|
-
const singleTypeActions = [];
|
|
613
|
+
const formattedActions = {};
|
|
591
614
|
for (const action of actions) {
|
|
592
615
|
const contentTypeUid = action.contentType;
|
|
593
|
-
if (
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
if (action.type === "publish") {
|
|
601
|
-
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
602
|
-
} else {
|
|
603
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
604
|
-
}
|
|
605
|
-
} else {
|
|
606
|
-
singleTypeActions.push({
|
|
607
|
-
uid: contentTypeUid,
|
|
608
|
-
action: action.type,
|
|
609
|
-
id: action.entry.id
|
|
610
|
-
});
|
|
616
|
+
if (!formattedActions[contentTypeUid]) {
|
|
617
|
+
formattedActions[contentTypeUid] = {
|
|
618
|
+
publish: [],
|
|
619
|
+
unpublish: []
|
|
620
|
+
};
|
|
611
621
|
}
|
|
622
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
623
|
+
documentId: action.entryDocumentId,
|
|
624
|
+
locale: action.locale
|
|
625
|
+
});
|
|
612
626
|
}
|
|
613
|
-
return
|
|
627
|
+
return formattedActions;
|
|
614
628
|
};
|
|
615
629
|
return {
|
|
616
630
|
async create(releaseData, { user }) {
|
|
@@ -657,91 +671,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
657
671
|
}
|
|
658
672
|
});
|
|
659
673
|
},
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
666
|
-
where: {
|
|
667
|
-
actions: {
|
|
668
|
-
target_type: contentTypeUid,
|
|
669
|
-
target_id: {
|
|
670
|
-
$in: entries
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
releasedAt: {
|
|
674
|
-
$null: true
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
populate: {
|
|
678
|
-
// Filter the action to get only the content type entry
|
|
679
|
-
actions: {
|
|
680
|
-
where: {
|
|
681
|
-
target_type: contentTypeUid,
|
|
682
|
-
target_id: {
|
|
683
|
-
$in: entries
|
|
684
|
-
}
|
|
685
|
-
},
|
|
686
|
-
populate: {
|
|
687
|
-
entry: {
|
|
688
|
-
select: ["id"]
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
});
|
|
694
|
-
return releases.map((release2) => {
|
|
695
|
-
if (release2.actions?.length) {
|
|
696
|
-
const actionsForEntry = release2.actions;
|
|
697
|
-
delete release2.actions;
|
|
698
|
-
return {
|
|
699
|
-
...release2,
|
|
700
|
-
actions: actionsForEntry
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
return release2;
|
|
704
|
-
});
|
|
705
|
-
},
|
|
706
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
707
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
708
|
-
where: {
|
|
709
|
-
releasedAt: {
|
|
710
|
-
$null: true
|
|
711
|
-
},
|
|
712
|
-
actions: {
|
|
713
|
-
target_type: contentTypeUid,
|
|
714
|
-
target_id: entryId
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
});
|
|
718
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
719
|
-
where: {
|
|
720
|
-
$or: [
|
|
721
|
-
{
|
|
722
|
-
id: {
|
|
723
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
724
|
-
}
|
|
725
|
-
},
|
|
726
|
-
{
|
|
727
|
-
actions: null
|
|
728
|
-
}
|
|
729
|
-
],
|
|
730
|
-
releasedAt: {
|
|
731
|
-
$null: true
|
|
732
|
-
}
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
return releases.map((release2) => {
|
|
736
|
-
if (release2.actions?.length) {
|
|
737
|
-
const [actionForEntry] = release2.actions;
|
|
738
|
-
delete release2.actions;
|
|
739
|
-
return {
|
|
740
|
-
...release2,
|
|
741
|
-
action: actionForEntry
|
|
742
|
-
};
|
|
743
|
-
}
|
|
744
|
-
return release2;
|
|
674
|
+
findMany(query) {
|
|
675
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
676
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
677
|
+
...dbQuery
|
|
745
678
|
});
|
|
746
679
|
},
|
|
747
680
|
async update(id, releaseData, { user }) {
|
|
@@ -777,131 +710,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
777
710
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
778
711
|
return updatedRelease;
|
|
779
712
|
},
|
|
780
|
-
async createAction(releaseId, action) {
|
|
781
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
782
|
-
strapi: strapi2
|
|
783
|
-
});
|
|
784
|
-
await Promise.all([
|
|
785
|
-
validateEntryContentType(action.entry.contentType),
|
|
786
|
-
validateUniqueEntry(releaseId, action)
|
|
787
|
-
]);
|
|
788
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
789
|
-
if (!release2) {
|
|
790
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
791
|
-
}
|
|
792
|
-
if (release2.releasedAt) {
|
|
793
|
-
throw new errors.ValidationError("Release already published");
|
|
794
|
-
}
|
|
795
|
-
const { entry, type } = action;
|
|
796
|
-
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
797
|
-
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
798
|
-
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
799
|
-
data: {
|
|
800
|
-
type,
|
|
801
|
-
contentType: entry.contentType,
|
|
802
|
-
locale: entry.locale,
|
|
803
|
-
isEntryValid,
|
|
804
|
-
entry: {
|
|
805
|
-
id: entry.id,
|
|
806
|
-
__type: entry.contentType,
|
|
807
|
-
__pivot: { field: "entry" }
|
|
808
|
-
},
|
|
809
|
-
release: releaseId
|
|
810
|
-
},
|
|
811
|
-
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
812
|
-
});
|
|
813
|
-
this.updateReleaseStatus(releaseId);
|
|
814
|
-
return releaseAction2;
|
|
815
|
-
},
|
|
816
|
-
async findActions(releaseId, query) {
|
|
817
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
818
|
-
where: { id: releaseId },
|
|
819
|
-
select: ["id"]
|
|
820
|
-
});
|
|
821
|
-
if (!release2) {
|
|
822
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
823
|
-
}
|
|
824
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
825
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
826
|
-
...dbQuery,
|
|
827
|
-
populate: {
|
|
828
|
-
entry: {
|
|
829
|
-
populate: "*"
|
|
830
|
-
}
|
|
831
|
-
},
|
|
832
|
-
where: {
|
|
833
|
-
release: releaseId
|
|
834
|
-
}
|
|
835
|
-
});
|
|
836
|
-
},
|
|
837
|
-
async countActions(query) {
|
|
838
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
839
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
840
|
-
},
|
|
841
|
-
async groupActions(actions, groupBy) {
|
|
842
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
843
|
-
if (!acc.includes(action.contentType)) {
|
|
844
|
-
acc.push(action.contentType);
|
|
845
|
-
}
|
|
846
|
-
return acc;
|
|
847
|
-
}, []);
|
|
848
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
|
|
849
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
850
|
-
const formattedData = actions.map((action) => {
|
|
851
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
852
|
-
return {
|
|
853
|
-
...action,
|
|
854
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
855
|
-
contentType: {
|
|
856
|
-
displayName,
|
|
857
|
-
mainFieldValue: action.entry[mainField],
|
|
858
|
-
uid: action.contentType
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
});
|
|
862
|
-
const groupName = getGroupName(groupBy);
|
|
863
|
-
return _.groupBy(groupName)(formattedData);
|
|
864
|
-
},
|
|
865
|
-
async getLocalesDataForActions() {
|
|
866
|
-
if (!strapi2.plugin("i18n")) {
|
|
867
|
-
return {};
|
|
868
|
-
}
|
|
869
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
870
|
-
return allLocales.reduce((acc, locale) => {
|
|
871
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
872
|
-
return acc;
|
|
873
|
-
}, {});
|
|
874
|
-
},
|
|
875
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
876
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
877
|
-
const contentTypesData = {};
|
|
878
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
879
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
880
|
-
uid: contentTypeUid
|
|
881
|
-
});
|
|
882
|
-
contentTypesData[contentTypeUid] = {
|
|
883
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
884
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
885
|
-
};
|
|
886
|
-
}
|
|
887
|
-
return contentTypesData;
|
|
888
|
-
},
|
|
889
|
-
getContentTypeModelsFromActions(actions) {
|
|
890
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
891
|
-
if (!acc.includes(action.contentType)) {
|
|
892
|
-
acc.push(action.contentType);
|
|
893
|
-
}
|
|
894
|
-
return acc;
|
|
895
|
-
}, []);
|
|
896
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
897
|
-
(acc, contentTypeUid) => {
|
|
898
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
899
|
-
return acc;
|
|
900
|
-
},
|
|
901
|
-
{}
|
|
902
|
-
);
|
|
903
|
-
return contentTypeModelsMap;
|
|
904
|
-
},
|
|
905
713
|
async getAllComponents() {
|
|
906
714
|
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
907
715
|
const components = await contentManagerComponentsService.findAllComponents();
|
|
@@ -967,20 +775,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
967
775
|
}
|
|
968
776
|
try {
|
|
969
777
|
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
970
|
-
const
|
|
971
|
-
await strapi2.db.transaction(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
});
|
|
778
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
779
|
+
await strapi2.db.transaction(
|
|
780
|
+
async () => Promise.all(
|
|
781
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
782
|
+
const contentType = contentTypeUid;
|
|
783
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
784
|
+
return Promise.all([
|
|
785
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
786
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
787
|
+
]);
|
|
788
|
+
})
|
|
789
|
+
)
|
|
790
|
+
);
|
|
984
791
|
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
985
792
|
where: {
|
|
986
793
|
id: releaseId
|
|
@@ -1010,13 +817,226 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1010
817
|
};
|
|
1011
818
|
}
|
|
1012
819
|
});
|
|
1013
|
-
if (error instanceof Error) {
|
|
1014
|
-
throw error;
|
|
820
|
+
if (error instanceof Error) {
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
823
|
+
return release2;
|
|
824
|
+
},
|
|
825
|
+
async updateReleaseStatus(releaseId) {
|
|
826
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
827
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
828
|
+
releaseActionService.countActions({
|
|
829
|
+
filters: {
|
|
830
|
+
release: releaseId
|
|
831
|
+
}
|
|
832
|
+
}),
|
|
833
|
+
releaseActionService.countActions({
|
|
834
|
+
filters: {
|
|
835
|
+
release: releaseId,
|
|
836
|
+
isEntryValid: false
|
|
837
|
+
}
|
|
838
|
+
})
|
|
839
|
+
]);
|
|
840
|
+
if (totalActions > 0) {
|
|
841
|
+
if (invalidActions > 0) {
|
|
842
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
843
|
+
where: {
|
|
844
|
+
id: releaseId
|
|
845
|
+
},
|
|
846
|
+
data: {
|
|
847
|
+
status: "blocked"
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
}
|
|
851
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
852
|
+
where: {
|
|
853
|
+
id: releaseId
|
|
854
|
+
},
|
|
855
|
+
data: {
|
|
856
|
+
status: "ready"
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
861
|
+
where: {
|
|
862
|
+
id: releaseId
|
|
863
|
+
},
|
|
864
|
+
data: {
|
|
865
|
+
status: "empty"
|
|
866
|
+
}
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
};
|
|
871
|
+
const getGroupName = (queryValue) => {
|
|
872
|
+
switch (queryValue) {
|
|
873
|
+
case "contentType":
|
|
874
|
+
return "contentType.displayName";
|
|
875
|
+
case "type":
|
|
876
|
+
return "type";
|
|
877
|
+
case "locale":
|
|
878
|
+
return _.getOr("No locale", "locale.name");
|
|
879
|
+
default:
|
|
880
|
+
return "contentType.displayName";
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
884
|
+
const getLocalesDataForActions = async () => {
|
|
885
|
+
if (!strapi2.plugin("i18n")) {
|
|
886
|
+
return {};
|
|
887
|
+
}
|
|
888
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
889
|
+
return allLocales.reduce((acc, locale) => {
|
|
890
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
891
|
+
return acc;
|
|
892
|
+
}, {});
|
|
893
|
+
};
|
|
894
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
895
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
896
|
+
const contentTypesData = {};
|
|
897
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
898
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
899
|
+
uid: contentTypeUid
|
|
900
|
+
});
|
|
901
|
+
contentTypesData[contentTypeUid] = {
|
|
902
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
903
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
return contentTypesData;
|
|
907
|
+
};
|
|
908
|
+
return {
|
|
909
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
910
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
911
|
+
strapi: strapi2
|
|
912
|
+
});
|
|
913
|
+
await Promise.all([
|
|
914
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
915
|
+
validateUniqueEntry(releaseId, action)
|
|
916
|
+
]);
|
|
917
|
+
const model = strapi2.contentType(action.contentType);
|
|
918
|
+
if (model.kind === "singleType") {
|
|
919
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
920
|
+
if (!document) {
|
|
921
|
+
throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
922
|
+
}
|
|
923
|
+
action.entryDocumentId = document.documentId;
|
|
924
|
+
}
|
|
925
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
926
|
+
if (!release2) {
|
|
927
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
928
|
+
}
|
|
929
|
+
if (release2.releasedAt) {
|
|
930
|
+
throw new errors.ValidationError("Release already published");
|
|
931
|
+
}
|
|
932
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
933
|
+
{
|
|
934
|
+
contentType: action.contentType,
|
|
935
|
+
documentId: action.entryDocumentId,
|
|
936
|
+
locale: action.locale
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
strapi: strapi2
|
|
940
|
+
}
|
|
941
|
+
) : true;
|
|
942
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
943
|
+
data: {
|
|
944
|
+
...action,
|
|
945
|
+
release: release2.id,
|
|
946
|
+
isEntryValid: actionStatus
|
|
947
|
+
},
|
|
948
|
+
populate: { release: { select: ["id"] } }
|
|
949
|
+
});
|
|
950
|
+
if (!disableUpdateReleaseStatus) {
|
|
951
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
1015
952
|
}
|
|
1016
|
-
return
|
|
953
|
+
return releaseAction2;
|
|
1017
954
|
},
|
|
1018
|
-
async
|
|
1019
|
-
const
|
|
955
|
+
async findPage(releaseId, query) {
|
|
956
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
957
|
+
where: { id: releaseId },
|
|
958
|
+
select: ["id"]
|
|
959
|
+
});
|
|
960
|
+
if (!release2) {
|
|
961
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
962
|
+
}
|
|
963
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
964
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
965
|
+
...dbQuery,
|
|
966
|
+
where: {
|
|
967
|
+
release: releaseId
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
971
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
972
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
973
|
+
const entry = await getEntry(
|
|
974
|
+
{
|
|
975
|
+
contentType: action.contentType,
|
|
976
|
+
documentId: action.entryDocumentId,
|
|
977
|
+
locale: action.locale,
|
|
978
|
+
populate,
|
|
979
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
980
|
+
},
|
|
981
|
+
{ strapi: strapi2 }
|
|
982
|
+
);
|
|
983
|
+
return {
|
|
984
|
+
...action,
|
|
985
|
+
entry,
|
|
986
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
987
|
+
};
|
|
988
|
+
});
|
|
989
|
+
return {
|
|
990
|
+
results: actionsWithEntry,
|
|
991
|
+
pagination
|
|
992
|
+
};
|
|
993
|
+
},
|
|
994
|
+
async groupActions(actions, groupBy) {
|
|
995
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
996
|
+
if (!acc.includes(action.contentType)) {
|
|
997
|
+
acc.push(action.contentType);
|
|
998
|
+
}
|
|
999
|
+
return acc;
|
|
1000
|
+
}, []);
|
|
1001
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1002
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1003
|
+
const formattedData = actions.map((action) => {
|
|
1004
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1005
|
+
return {
|
|
1006
|
+
...action,
|
|
1007
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1008
|
+
contentType: {
|
|
1009
|
+
displayName,
|
|
1010
|
+
mainFieldValue: action.entry[mainField],
|
|
1011
|
+
uid: action.contentType
|
|
1012
|
+
}
|
|
1013
|
+
};
|
|
1014
|
+
});
|
|
1015
|
+
const groupName = getGroupName(groupBy);
|
|
1016
|
+
return _.groupBy(groupName)(formattedData);
|
|
1017
|
+
},
|
|
1018
|
+
getContentTypeModelsFromActions(actions) {
|
|
1019
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1020
|
+
if (!acc.includes(action.contentType)) {
|
|
1021
|
+
acc.push(action.contentType);
|
|
1022
|
+
}
|
|
1023
|
+
return acc;
|
|
1024
|
+
}, []);
|
|
1025
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
1026
|
+
(acc, contentTypeUid) => {
|
|
1027
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
1028
|
+
return acc;
|
|
1029
|
+
},
|
|
1030
|
+
{}
|
|
1031
|
+
);
|
|
1032
|
+
return contentTypeModelsMap;
|
|
1033
|
+
},
|
|
1034
|
+
async countActions(query) {
|
|
1035
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1036
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1037
|
+
},
|
|
1038
|
+
async update(actionId, releaseId, update) {
|
|
1039
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1020
1040
|
where: {
|
|
1021
1041
|
id: actionId,
|
|
1022
1042
|
release: {
|
|
@@ -1025,17 +1045,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1025
1045
|
$null: true
|
|
1026
1046
|
}
|
|
1027
1047
|
}
|
|
1028
|
-
}
|
|
1029
|
-
data: update
|
|
1048
|
+
}
|
|
1030
1049
|
});
|
|
1031
|
-
if (!
|
|
1050
|
+
if (!action) {
|
|
1032
1051
|
throw new errors.NotFoundError(
|
|
1033
1052
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1034
1053
|
);
|
|
1035
1054
|
}
|
|
1055
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1056
|
+
{
|
|
1057
|
+
contentType: action.contentType,
|
|
1058
|
+
documentId: action.entryDocumentId,
|
|
1059
|
+
locale: action.locale
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
strapi: strapi2
|
|
1063
|
+
}
|
|
1064
|
+
) : true;
|
|
1065
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1066
|
+
where: {
|
|
1067
|
+
id: actionId,
|
|
1068
|
+
release: {
|
|
1069
|
+
id: releaseId,
|
|
1070
|
+
releasedAt: {
|
|
1071
|
+
$null: true
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
data: {
|
|
1076
|
+
...update,
|
|
1077
|
+
isEntryValid: actionStatus
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1036
1081
|
return updatedAction;
|
|
1037
1082
|
},
|
|
1038
|
-
async
|
|
1083
|
+
async delete(actionId, releaseId) {
|
|
1039
1084
|
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1040
1085
|
where: {
|
|
1041
1086
|
id: actionId,
|
|
@@ -1052,51 +1097,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1052
1097
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1053
1098
|
);
|
|
1054
1099
|
}
|
|
1055
|
-
|
|
1100
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1056
1101
|
return deletedAction;
|
|
1057
|
-
},
|
|
1058
|
-
async updateReleaseStatus(releaseId) {
|
|
1059
|
-
const [totalActions, invalidActions] = await Promise.all([
|
|
1060
|
-
this.countActions({
|
|
1061
|
-
filters: {
|
|
1062
|
-
release: releaseId
|
|
1063
|
-
}
|
|
1064
|
-
}),
|
|
1065
|
-
this.countActions({
|
|
1066
|
-
filters: {
|
|
1067
|
-
release: releaseId,
|
|
1068
|
-
isEntryValid: false
|
|
1069
|
-
}
|
|
1070
|
-
})
|
|
1071
|
-
]);
|
|
1072
|
-
if (totalActions > 0) {
|
|
1073
|
-
if (invalidActions > 0) {
|
|
1074
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1075
|
-
where: {
|
|
1076
|
-
id: releaseId
|
|
1077
|
-
},
|
|
1078
|
-
data: {
|
|
1079
|
-
status: "blocked"
|
|
1080
|
-
}
|
|
1081
|
-
});
|
|
1082
|
-
}
|
|
1083
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1084
|
-
where: {
|
|
1085
|
-
id: releaseId
|
|
1086
|
-
},
|
|
1087
|
-
data: {
|
|
1088
|
-
status: "ready"
|
|
1089
|
-
}
|
|
1090
|
-
});
|
|
1091
|
-
}
|
|
1092
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1093
|
-
where: {
|
|
1094
|
-
id: releaseId
|
|
1095
|
-
},
|
|
1096
|
-
data: {
|
|
1097
|
-
status: "empty"
|
|
1098
|
-
}
|
|
1099
|
-
});
|
|
1100
1102
|
}
|
|
1101
1103
|
};
|
|
1102
1104
|
};
|
|
@@ -1112,30 +1114,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
1112
1114
|
where: {
|
|
1113
1115
|
id: releaseId
|
|
1114
1116
|
},
|
|
1115
|
-
populate: {
|
|
1117
|
+
populate: {
|
|
1118
|
+
actions: true
|
|
1119
|
+
}
|
|
1116
1120
|
});
|
|
1117
1121
|
if (!release2) {
|
|
1118
1122
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1119
1123
|
}
|
|
1120
1124
|
const isEntryInRelease = release2.actions.some(
|
|
1121
|
-
(action) =>
|
|
1125
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
1122
1126
|
);
|
|
1123
1127
|
if (isEntryInRelease) {
|
|
1124
1128
|
throw new AlreadyOnReleaseError(
|
|
1125
|
-
`Entry with
|
|
1129
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
1126
1130
|
);
|
|
1127
1131
|
}
|
|
1128
1132
|
},
|
|
1129
|
-
|
|
1133
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
1130
1134
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
1131
1135
|
if (!contentType) {
|
|
1132
1136
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
1133
1137
|
}
|
|
1134
|
-
if (!contentType
|
|
1138
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
1135
1139
|
throw new errors.ValidationError(
|
|
1136
1140
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
1137
1141
|
);
|
|
1138
1142
|
}
|
|
1143
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1144
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1145
|
+
}
|
|
1139
1146
|
},
|
|
1140
1147
|
async validatePendingReleasesLimit() {
|
|
1141
1148
|
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
@@ -1224,78 +1231,165 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
1224
1231
|
}
|
|
1225
1232
|
};
|
|
1226
1233
|
};
|
|
1234
|
+
const DEFAULT_SETTINGS = {
|
|
1235
|
+
defaultTimezone: null
|
|
1236
|
+
};
|
|
1237
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1238
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1239
|
+
return {
|
|
1240
|
+
async update({ settings: settings2 }) {
|
|
1241
|
+
const store = await getStore();
|
|
1242
|
+
store.set({ key: "settings", value: settings2 });
|
|
1243
|
+
return settings2;
|
|
1244
|
+
},
|
|
1245
|
+
async find() {
|
|
1246
|
+
const store = await getStore();
|
|
1247
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1248
|
+
return {
|
|
1249
|
+
...DEFAULT_SETTINGS,
|
|
1250
|
+
...settings2 || {}
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
};
|
|
1227
1255
|
const services = {
|
|
1228
1256
|
release: createReleaseService,
|
|
1257
|
+
"release-action": createReleaseActionService,
|
|
1229
1258
|
"release-validation": createReleaseValidationService,
|
|
1230
|
-
scheduling: createSchedulingService
|
|
1259
|
+
scheduling: createSchedulingService,
|
|
1260
|
+
settings: createSettingsService
|
|
1231
1261
|
};
|
|
1232
|
-
const RELEASE_SCHEMA = yup.object().shape({
|
|
1233
|
-
name: yup.string().trim().required(),
|
|
1234
|
-
scheduledAt: yup.string().nullable(),
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
otherwise: yup.string().nullable()
|
|
1240
|
-
}),
|
|
1241
|
-
timezone: yup.string().when("isScheduled", {
|
|
1242
|
-
is: true,
|
|
1243
|
-
then: yup.string().required().nullable(),
|
|
1244
|
-
otherwise: yup.string().nullable()
|
|
1245
|
-
}),
|
|
1246
|
-
date: yup.string().when("isScheduled", {
|
|
1247
|
-
is: true,
|
|
1248
|
-
then: yup.string().required().nullable(),
|
|
1249
|
-
otherwise: yup.string().nullable()
|
|
1262
|
+
const RELEASE_SCHEMA = yup$1.object().shape({
|
|
1263
|
+
name: yup$1.string().trim().required(),
|
|
1264
|
+
scheduledAt: yup$1.string().nullable(),
|
|
1265
|
+
timezone: yup$1.string().when("scheduledAt", {
|
|
1266
|
+
is: (value) => value !== null && value !== void 0,
|
|
1267
|
+
then: yup$1.string().required(),
|
|
1268
|
+
otherwise: yup$1.string().nullable()
|
|
1250
1269
|
})
|
|
1251
1270
|
}).required().noUnknown();
|
|
1271
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
|
|
1272
|
+
contentType: yup$1.string().required(),
|
|
1273
|
+
entryDocumentId: yup$1.string().nullable(),
|
|
1274
|
+
hasEntryAttached: yup$1.string().nullable(),
|
|
1275
|
+
locale: yup$1.string().nullable()
|
|
1276
|
+
}).required().noUnknown();
|
|
1252
1277
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1278
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1279
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1280
|
+
);
|
|
1253
1281
|
const releaseController = {
|
|
1254
|
-
|
|
1282
|
+
/**
|
|
1283
|
+
* Find releases based on documents attached or not to the release.
|
|
1284
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1285
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1286
|
+
*/
|
|
1287
|
+
async findByDocumentAttached(ctx) {
|
|
1255
1288
|
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1256
1289
|
ability: ctx.state.userAbility,
|
|
1257
1290
|
model: RELEASE_MODEL_UID
|
|
1258
1291
|
});
|
|
1259
1292
|
await permissionsManager.validateQuery(ctx.query);
|
|
1260
1293
|
const releaseService = getService("release", { strapi });
|
|
1261
|
-
const
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
const
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1294
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1295
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1296
|
+
const model = strapi.getModel(query.contentType);
|
|
1297
|
+
if (model.kind && model.kind === "singleType") {
|
|
1298
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1299
|
+
if (!document) {
|
|
1300
|
+
throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1301
|
+
}
|
|
1302
|
+
query.entryDocumentId = document.documentId;
|
|
1303
|
+
}
|
|
1304
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1305
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1306
|
+
if (isEntryAttached) {
|
|
1307
|
+
const releases = await releaseService.findMany({
|
|
1308
|
+
where: {
|
|
1309
|
+
releasedAt: null,
|
|
1310
|
+
actions: {
|
|
1311
|
+
contentType,
|
|
1312
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1313
|
+
locale: locale ?? null
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
populate: {
|
|
1276
1317
|
actions: {
|
|
1277
|
-
|
|
1278
|
-
|
|
1318
|
+
fields: ["type"],
|
|
1319
|
+
filters: {
|
|
1320
|
+
contentType,
|
|
1321
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1322
|
+
locale: locale ?? null
|
|
1279
1323
|
}
|
|
1280
1324
|
}
|
|
1281
|
-
}
|
|
1325
|
+
}
|
|
1326
|
+
});
|
|
1327
|
+
ctx.body = { data: releases };
|
|
1328
|
+
} else {
|
|
1329
|
+
const relatedReleases = await releaseService.findMany({
|
|
1330
|
+
where: {
|
|
1331
|
+
releasedAt: null,
|
|
1332
|
+
actions: {
|
|
1333
|
+
contentType,
|
|
1334
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1335
|
+
locale: locale ?? null
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1282
1338
|
});
|
|
1283
|
-
const
|
|
1339
|
+
const releases = await releaseService.findMany({
|
|
1284
1340
|
where: {
|
|
1341
|
+
$or: [
|
|
1342
|
+
{
|
|
1343
|
+
id: {
|
|
1344
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
actions: null
|
|
1349
|
+
}
|
|
1350
|
+
],
|
|
1285
1351
|
releasedAt: null
|
|
1286
1352
|
}
|
|
1287
1353
|
});
|
|
1288
|
-
ctx.body = { data
|
|
1354
|
+
ctx.body = { data: releases };
|
|
1289
1355
|
}
|
|
1290
1356
|
},
|
|
1357
|
+
async findPage(ctx) {
|
|
1358
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1359
|
+
ability: ctx.state.userAbility,
|
|
1360
|
+
model: RELEASE_MODEL_UID
|
|
1361
|
+
});
|
|
1362
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1363
|
+
const releaseService = getService("release", { strapi });
|
|
1364
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1365
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1366
|
+
const data = results.map((release2) => {
|
|
1367
|
+
const { actions, ...releaseData } = release2;
|
|
1368
|
+
return {
|
|
1369
|
+
...releaseData,
|
|
1370
|
+
actions: {
|
|
1371
|
+
meta: {
|
|
1372
|
+
count: actions.count
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
});
|
|
1377
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1378
|
+
where: {
|
|
1379
|
+
releasedAt: null
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1383
|
+
},
|
|
1291
1384
|
async findOne(ctx) {
|
|
1292
1385
|
const id = ctx.params.id;
|
|
1293
1386
|
const releaseService = getService("release", { strapi });
|
|
1387
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1294
1388
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
1295
1389
|
if (!release2) {
|
|
1296
1390
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1297
1391
|
}
|
|
1298
|
-
const count = await
|
|
1392
|
+
const count = await releaseActionService.countActions({
|
|
1299
1393
|
filters: {
|
|
1300
1394
|
release: id
|
|
1301
1395
|
}
|
|
@@ -1315,28 +1409,43 @@ const releaseController = {
|
|
|
1315
1409
|
ctx.body = { data };
|
|
1316
1410
|
},
|
|
1317
1411
|
async mapEntriesToReleases(ctx) {
|
|
1318
|
-
const { contentTypeUid,
|
|
1319
|
-
if (!contentTypeUid || !
|
|
1412
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1413
|
+
if (!contentTypeUid || !documentIds) {
|
|
1320
1414
|
throw new errors.ValidationError("Missing required query parameters");
|
|
1321
1415
|
}
|
|
1322
1416
|
const releaseService = getService("release", { strapi });
|
|
1323
|
-
const releasesWithActions = await releaseService.
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1417
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1418
|
+
where: {
|
|
1419
|
+
releasedAt: null,
|
|
1420
|
+
actions: {
|
|
1421
|
+
contentType: contentTypeUid,
|
|
1422
|
+
entryDocumentId: {
|
|
1423
|
+
$in: documentIds
|
|
1424
|
+
},
|
|
1425
|
+
locale
|
|
1426
|
+
}
|
|
1427
|
+
},
|
|
1428
|
+
populate: {
|
|
1429
|
+
actions: true
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1327
1432
|
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1328
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1329
1433
|
(acc, release2) => {
|
|
1330
1434
|
release2.actions.forEach((action) => {
|
|
1331
|
-
if (
|
|
1332
|
-
|
|
1435
|
+
if (action.contentType !== contentTypeUid) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (locale && action.locale !== locale) {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
if (!acc[action.entryDocumentId]) {
|
|
1442
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1333
1443
|
} else {
|
|
1334
|
-
acc[action.
|
|
1444
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1335
1445
|
}
|
|
1336
1446
|
});
|
|
1337
1447
|
return acc;
|
|
1338
1448
|
},
|
|
1339
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1340
1449
|
{}
|
|
1341
1450
|
);
|
|
1342
1451
|
ctx.body = {
|
|
@@ -1381,18 +1490,18 @@ const releaseController = {
|
|
|
1381
1490
|
};
|
|
1382
1491
|
},
|
|
1383
1492
|
async publish(ctx) {
|
|
1384
|
-
const user = ctx.state.user;
|
|
1385
1493
|
const id = ctx.params.id;
|
|
1386
1494
|
const releaseService = getService("release", { strapi });
|
|
1387
|
-
const
|
|
1495
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1496
|
+
const release2 = await releaseService.publish(id);
|
|
1388
1497
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1389
|
-
|
|
1498
|
+
releaseActionService.countActions({
|
|
1390
1499
|
filters: {
|
|
1391
1500
|
release: id,
|
|
1392
1501
|
type: "publish"
|
|
1393
1502
|
}
|
|
1394
1503
|
}),
|
|
1395
|
-
|
|
1504
|
+
releaseActionService.countActions({
|
|
1396
1505
|
filters: {
|
|
1397
1506
|
release: id,
|
|
1398
1507
|
type: "unpublish"
|
|
@@ -1410,24 +1519,27 @@ const releaseController = {
|
|
|
1410
1519
|
}
|
|
1411
1520
|
};
|
|
1412
1521
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
}).required(),
|
|
1522
|
+
contentType: yup$1.string().required(),
|
|
1523
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1524
|
+
locale: yup$1.string(),
|
|
1417
1525
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1418
1526
|
});
|
|
1419
1527
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
1420
1528
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1421
1529
|
});
|
|
1530
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1531
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1532
|
+
});
|
|
1422
1533
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
1423
1534
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1535
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
1424
1536
|
const releaseActionController = {
|
|
1425
1537
|
async create(ctx) {
|
|
1426
1538
|
const releaseId = ctx.params.releaseId;
|
|
1427
1539
|
const releaseActionArgs = ctx.request.body;
|
|
1428
1540
|
await validateReleaseAction(releaseActionArgs);
|
|
1429
|
-
const
|
|
1430
|
-
const releaseAction2 = await
|
|
1541
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1542
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1431
1543
|
ctx.created({
|
|
1432
1544
|
data: releaseAction2
|
|
1433
1545
|
});
|
|
@@ -1438,12 +1550,15 @@ const releaseActionController = {
|
|
|
1438
1550
|
await Promise.all(
|
|
1439
1551
|
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1440
1552
|
);
|
|
1553
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1441
1554
|
const releaseService = getService("release", { strapi });
|
|
1442
1555
|
const releaseActions = await strapi.db.transaction(async () => {
|
|
1443
1556
|
const releaseActions2 = await Promise.all(
|
|
1444
1557
|
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1445
1558
|
try {
|
|
1446
|
-
const action = await
|
|
1559
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1560
|
+
disableUpdateReleaseStatus: true
|
|
1561
|
+
});
|
|
1447
1562
|
return action;
|
|
1448
1563
|
} catch (error) {
|
|
1449
1564
|
if (error instanceof AlreadyOnReleaseError) {
|
|
@@ -1456,6 +1571,9 @@ const releaseActionController = {
|
|
|
1456
1571
|
return releaseActions2;
|
|
1457
1572
|
});
|
|
1458
1573
|
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1574
|
+
if (newReleaseActions.length > 0) {
|
|
1575
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1576
|
+
}
|
|
1459
1577
|
ctx.created({
|
|
1460
1578
|
data: newReleaseActions,
|
|
1461
1579
|
meta: {
|
|
@@ -1470,10 +1588,17 @@ const releaseActionController = {
|
|
|
1470
1588
|
ability: ctx.state.userAbility,
|
|
1471
1589
|
model: RELEASE_ACTION_MODEL_UID
|
|
1472
1590
|
});
|
|
1591
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1592
|
+
if (ctx.query.groupBy) {
|
|
1593
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1594
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1598
|
+
delete ctx.query.groupBy;
|
|
1473
1599
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1474
|
-
const
|
|
1475
|
-
const { results, pagination } = await
|
|
1476
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1600
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1601
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1477
1602
|
...query
|
|
1478
1603
|
});
|
|
1479
1604
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
@@ -1489,10 +1614,11 @@ const releaseActionController = {
|
|
|
1489
1614
|
}, {});
|
|
1490
1615
|
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1491
1616
|
...action,
|
|
1492
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1617
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1493
1618
|
}));
|
|
1494
|
-
const groupedData = await
|
|
1495
|
-
const contentTypes2 =
|
|
1619
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1620
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1621
|
+
const releaseService = getService("release", { strapi });
|
|
1496
1622
|
const components = await releaseService.getAllComponents();
|
|
1497
1623
|
ctx.body = {
|
|
1498
1624
|
data: groupedData,
|
|
@@ -1508,8 +1634,8 @@ const releaseActionController = {
|
|
|
1508
1634
|
const releaseId = ctx.params.releaseId;
|
|
1509
1635
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
1510
1636
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
1511
|
-
const
|
|
1512
|
-
const updatedAction = await
|
|
1637
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1638
|
+
const updatedAction = await releaseActionService.update(
|
|
1513
1639
|
actionId,
|
|
1514
1640
|
releaseId,
|
|
1515
1641
|
releaseActionUpdateArgs
|
|
@@ -1521,14 +1647,36 @@ const releaseActionController = {
|
|
|
1521
1647
|
async delete(ctx) {
|
|
1522
1648
|
const actionId = ctx.params.actionId;
|
|
1523
1649
|
const releaseId = ctx.params.releaseId;
|
|
1524
|
-
const
|
|
1525
|
-
const deletedReleaseAction = await
|
|
1650
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1651
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
1526
1652
|
ctx.body = {
|
|
1527
1653
|
data: deletedReleaseAction
|
|
1528
1654
|
};
|
|
1529
1655
|
}
|
|
1530
1656
|
};
|
|
1531
|
-
const
|
|
1657
|
+
const SETTINGS_SCHEMA = yup.object().shape({
|
|
1658
|
+
defaultTimezone: yup.string().nullable().default(null)
|
|
1659
|
+
}).required().noUnknown();
|
|
1660
|
+
const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
|
|
1661
|
+
const settingsController = {
|
|
1662
|
+
async find(ctx) {
|
|
1663
|
+
const settingsService = getService("settings", { strapi });
|
|
1664
|
+
const settings2 = await settingsService.find();
|
|
1665
|
+
ctx.body = { data: settings2 };
|
|
1666
|
+
},
|
|
1667
|
+
async update(ctx) {
|
|
1668
|
+
const settingsBody = ctx.request.body;
|
|
1669
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1670
|
+
const settingsService = getService("settings", { strapi });
|
|
1671
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1672
|
+
ctx.body = { data: updatedSettings };
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
const controllers = {
|
|
1676
|
+
release: releaseController,
|
|
1677
|
+
"release-action": releaseActionController,
|
|
1678
|
+
settings: settingsController
|
|
1679
|
+
};
|
|
1532
1680
|
const release = {
|
|
1533
1681
|
type: "admin",
|
|
1534
1682
|
routes: [
|
|
@@ -1548,6 +1696,22 @@ const release = {
|
|
|
1548
1696
|
]
|
|
1549
1697
|
}
|
|
1550
1698
|
},
|
|
1699
|
+
{
|
|
1700
|
+
method: "GET",
|
|
1701
|
+
path: "/getByDocumentAttached",
|
|
1702
|
+
handler: "release.findByDocumentAttached",
|
|
1703
|
+
config: {
|
|
1704
|
+
policies: [
|
|
1705
|
+
"admin::isAuthenticatedAdmin",
|
|
1706
|
+
{
|
|
1707
|
+
name: "admin::hasPermissions",
|
|
1708
|
+
config: {
|
|
1709
|
+
actions: ["plugin::content-releases.read"]
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
]
|
|
1713
|
+
}
|
|
1714
|
+
},
|
|
1551
1715
|
{
|
|
1552
1716
|
method: "POST",
|
|
1553
1717
|
path: "/",
|
|
@@ -1567,7 +1731,7 @@ const release = {
|
|
|
1567
1731
|
{
|
|
1568
1732
|
method: "GET",
|
|
1569
1733
|
path: "/",
|
|
1570
|
-
handler: "release.
|
|
1734
|
+
handler: "release.findPage",
|
|
1571
1735
|
config: {
|
|
1572
1736
|
policies: [
|
|
1573
1737
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1731,7 +1895,45 @@ const releaseAction = {
|
|
|
1731
1895
|
}
|
|
1732
1896
|
]
|
|
1733
1897
|
};
|
|
1898
|
+
const settings = {
|
|
1899
|
+
type: "admin",
|
|
1900
|
+
routes: [
|
|
1901
|
+
{
|
|
1902
|
+
method: "GET",
|
|
1903
|
+
path: "/settings",
|
|
1904
|
+
handler: "settings.find",
|
|
1905
|
+
config: {
|
|
1906
|
+
policies: [
|
|
1907
|
+
"admin::isAuthenticatedAdmin",
|
|
1908
|
+
{
|
|
1909
|
+
name: "admin::hasPermissions",
|
|
1910
|
+
config: {
|
|
1911
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
]
|
|
1915
|
+
}
|
|
1916
|
+
},
|
|
1917
|
+
{
|
|
1918
|
+
method: "PUT",
|
|
1919
|
+
path: "/settings",
|
|
1920
|
+
handler: "settings.update",
|
|
1921
|
+
config: {
|
|
1922
|
+
policies: [
|
|
1923
|
+
"admin::isAuthenticatedAdmin",
|
|
1924
|
+
{
|
|
1925
|
+
name: "admin::hasPermissions",
|
|
1926
|
+
config: {
|
|
1927
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
]
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
]
|
|
1934
|
+
};
|
|
1734
1935
|
const routes = {
|
|
1936
|
+
settings,
|
|
1735
1937
|
release,
|
|
1736
1938
|
"release-action": releaseAction
|
|
1737
1939
|
};
|