@strapi/content-releases 0.0.0-experimental.d23c1d5b0e45dd06ef09977f526c85468be05403 → 0.0.0-experimental.d3243594aea3e6fa0ef08580192bb0df29c9162d
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-Cmn2Mkn7.mjs → App-BA2xDdy0.mjs} +435 -404
- package/dist/_chunks/App-BA2xDdy0.mjs.map +1 -0
- package/dist/_chunks/{App-FVorrIzF.js → App-D4Wira1X.js} +440 -411
- package/dist/_chunks/App-D4Wira1X.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-sD6ADHk2.js → PurchaseContentReleases-Be3acS2L.js} +7 -6
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-C8djn9fP.mjs → PurchaseContentReleases-_MxP6-Dt.mjs} +8 -7
- 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-DDohgTaQ.mjs → index-CCFFG3Zs.mjs} +724 -604
- package/dist/_chunks/index-CCFFG3Zs.mjs.map +1 -0
- package/dist/_chunks/{index-BfJLth9Z.js → index-DxkQGp4N.js} +712 -594
- 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-Cmn2Mkn7.mjs.map +0 -1
- package/dist/_chunks/App-FVorrIzF.js.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-C8djn9fP.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-sD6ADHk2.js.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-BfJLth9Z.js.map +0 -1
- package/dist/_chunks/index-DDohgTaQ.mjs.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.js
CHANGED
|
@@ -71,6 +71,23 @@ const ACTIONS = [
|
|
|
71
71
|
displayName: "Add an entry to a release",
|
|
72
72
|
uid: "create-action",
|
|
73
73
|
pluginName: "content-releases"
|
|
74
|
+
},
|
|
75
|
+
// Settings
|
|
76
|
+
{
|
|
77
|
+
uid: "settings.read",
|
|
78
|
+
section: "settings",
|
|
79
|
+
displayName: "Read",
|
|
80
|
+
category: "content releases",
|
|
81
|
+
subCategory: "options",
|
|
82
|
+
pluginName: "content-releases"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
uid: "settings.update",
|
|
86
|
+
section: "settings",
|
|
87
|
+
displayName: "Edit",
|
|
88
|
+
category: "content releases",
|
|
89
|
+
subCategory: "options",
|
|
90
|
+
pluginName: "content-releases"
|
|
74
91
|
}
|
|
75
92
|
];
|
|
76
93
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
@@ -79,16 +96,13 @@ const ALLOWED_WEBHOOK_EVENTS = {
|
|
|
79
96
|
const getService = (name, { strapi: strapi2 }) => {
|
|
80
97
|
return strapi2.plugin("content-releases").service(name);
|
|
81
98
|
};
|
|
82
|
-
const
|
|
99
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
83
100
|
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
84
|
-
const populate = await populateBuilderService(
|
|
85
|
-
const entry = await strapi2
|
|
86
|
-
|
|
87
|
-
populate
|
|
88
|
-
});
|
|
89
|
-
return entry;
|
|
101
|
+
const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
|
|
102
|
+
const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
|
|
103
|
+
return isEntryValid(contentType, entry, { strapi: strapi2 });
|
|
90
104
|
};
|
|
91
|
-
const
|
|
105
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
92
106
|
try {
|
|
93
107
|
await strapi2.entityValidator.validateEntityCreation(
|
|
94
108
|
strapi2.getModel(contentTypeUid),
|
|
@@ -102,6 +116,38 @@ const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 }) =
|
|
|
102
116
|
return false;
|
|
103
117
|
}
|
|
104
118
|
};
|
|
119
|
+
const getEntry = async ({
|
|
120
|
+
contentType,
|
|
121
|
+
documentId,
|
|
122
|
+
locale,
|
|
123
|
+
populate,
|
|
124
|
+
status = "draft"
|
|
125
|
+
}, { strapi: strapi2 }) => {
|
|
126
|
+
if (documentId) {
|
|
127
|
+
return strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
|
|
128
|
+
}
|
|
129
|
+
return strapi2.documents(contentType).findFirst({ locale, populate, status });
|
|
130
|
+
};
|
|
131
|
+
const getEntryStatus = async (contentType, entry) => {
|
|
132
|
+
if (entry.publishedAt) {
|
|
133
|
+
return "published";
|
|
134
|
+
}
|
|
135
|
+
const publishedEntry = await strapi.documents(contentType).findOne({
|
|
136
|
+
documentId: entry.documentId,
|
|
137
|
+
locale: entry.locale,
|
|
138
|
+
status: "published",
|
|
139
|
+
fields: ["updatedAt"]
|
|
140
|
+
});
|
|
141
|
+
if (!publishedEntry) {
|
|
142
|
+
return "draft";
|
|
143
|
+
}
|
|
144
|
+
const entryUpdatedAt = new Date(entry.updatedAt).getTime();
|
|
145
|
+
const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
|
|
146
|
+
if (entryUpdatedAt > publishedEntryUpdatedAt) {
|
|
147
|
+
return "modified";
|
|
148
|
+
}
|
|
149
|
+
return "published";
|
|
150
|
+
};
|
|
105
151
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
106
152
|
oldContentTypes,
|
|
107
153
|
contentTypes: contentTypes2
|
|
@@ -147,20 +193,22 @@ async function migrateIsValidAndStatusReleases() {
|
|
|
147
193
|
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
148
194
|
for (const action of notValidatedActions) {
|
|
149
195
|
if (action.entry) {
|
|
150
|
-
const
|
|
151
|
-
|
|
196
|
+
const isEntryValid2 = getDraftEntryValidStatus(
|
|
197
|
+
{
|
|
198
|
+
contentType: action.contentType,
|
|
199
|
+
documentId: action.entryDocumentId,
|
|
200
|
+
locale: action.locale
|
|
201
|
+
},
|
|
202
|
+
{ strapi }
|
|
203
|
+
);
|
|
204
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
205
|
+
where: {
|
|
206
|
+
id: action.id
|
|
207
|
+
},
|
|
208
|
+
data: {
|
|
209
|
+
isEntryValid: isEntryValid2
|
|
210
|
+
}
|
|
152
211
|
});
|
|
153
|
-
if (populatedEntry) {
|
|
154
|
-
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
155
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
156
|
-
where: {
|
|
157
|
-
id: action.id
|
|
158
|
-
},
|
|
159
|
-
data: {
|
|
160
|
-
isEntryValid
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
212
|
}
|
|
165
213
|
}
|
|
166
214
|
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
@@ -204,24 +252,24 @@ async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: co
|
|
|
204
252
|
}
|
|
205
253
|
});
|
|
206
254
|
await utils.async.map(actions, async (action) => {
|
|
207
|
-
if (action.entry && action.release) {
|
|
208
|
-
const
|
|
209
|
-
|
|
255
|
+
if (action.entry && action.release && action.type === "publish") {
|
|
256
|
+
const isEntryValid2 = await getDraftEntryValidStatus(
|
|
257
|
+
{
|
|
258
|
+
contentType: contentTypeUID,
|
|
259
|
+
documentId: action.entryDocumentId,
|
|
260
|
+
locale: action.locale
|
|
261
|
+
},
|
|
262
|
+
{ strapi }
|
|
263
|
+
);
|
|
264
|
+
releasesAffected.add(action.release.id);
|
|
265
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
266
|
+
where: {
|
|
267
|
+
id: action.id
|
|
268
|
+
},
|
|
269
|
+
data: {
|
|
270
|
+
isEntryValid: isEntryValid2
|
|
271
|
+
}
|
|
210
272
|
});
|
|
211
|
-
if (populatedEntry) {
|
|
212
|
-
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
213
|
-
strapi
|
|
214
|
-
});
|
|
215
|
-
releasesAffected.add(action.release.id);
|
|
216
|
-
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
217
|
-
where: {
|
|
218
|
-
id: action.id
|
|
219
|
-
},
|
|
220
|
-
data: {
|
|
221
|
-
isEntryValid
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
273
|
}
|
|
226
274
|
});
|
|
227
275
|
}
|
|
@@ -278,9 +326,38 @@ async function enableContentTypeLocalized({ oldContentTypes, contentTypes: conte
|
|
|
278
326
|
}
|
|
279
327
|
}
|
|
280
328
|
}
|
|
329
|
+
const addEntryDocumentToReleaseActions = {
|
|
330
|
+
name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
|
|
331
|
+
async up(trx, db) {
|
|
332
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
333
|
+
if (hasPolymorphicColumn) {
|
|
334
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
335
|
+
"strapi_release_actions",
|
|
336
|
+
"entry_document_id"
|
|
337
|
+
);
|
|
338
|
+
if (!hasEntryDocumentIdColumn) {
|
|
339
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
340
|
+
table.string("entry_document_id");
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
344
|
+
utils.async.map(releaseActions, async (action) => {
|
|
345
|
+
const { target_type, target_id } = action;
|
|
346
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
347
|
+
if (entry) {
|
|
348
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
},
|
|
353
|
+
async down() {
|
|
354
|
+
throw new Error("not implemented");
|
|
355
|
+
}
|
|
356
|
+
};
|
|
281
357
|
const register = async ({ strapi: strapi2 }) => {
|
|
282
358
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
283
359
|
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
360
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
284
361
|
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
285
362
|
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
286
363
|
}
|
|
@@ -290,6 +367,104 @@ const register = async ({ strapi: strapi2 }) => {
|
|
|
290
367
|
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
291
368
|
}
|
|
292
369
|
};
|
|
370
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
371
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
372
|
+
where: {
|
|
373
|
+
actions: {
|
|
374
|
+
contentType,
|
|
375
|
+
entryDocumentId: entry.documentId,
|
|
376
|
+
locale: entry.locale
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
381
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
382
|
+
where: {
|
|
383
|
+
contentType,
|
|
384
|
+
entryDocumentId: entry.documentId,
|
|
385
|
+
locale: entry.locale
|
|
386
|
+
},
|
|
387
|
+
data: {
|
|
388
|
+
isEntryValid: entryStatus
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
for (const release2 of releases) {
|
|
392
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
396
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
397
|
+
where: {
|
|
398
|
+
actions: params
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
402
|
+
where: params
|
|
403
|
+
});
|
|
404
|
+
for (const release2 of releases) {
|
|
405
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
409
|
+
if (ctx.action !== "delete") {
|
|
410
|
+
return next();
|
|
411
|
+
}
|
|
412
|
+
if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
|
|
413
|
+
return next();
|
|
414
|
+
}
|
|
415
|
+
const contentType = ctx.contentType.uid;
|
|
416
|
+
const { documentId, locale } = ctx.params;
|
|
417
|
+
const result = await next();
|
|
418
|
+
if (!result) {
|
|
419
|
+
return result;
|
|
420
|
+
}
|
|
421
|
+
try {
|
|
422
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
423
|
+
contentType,
|
|
424
|
+
entryDocumentId: documentId,
|
|
425
|
+
...locale !== "*" && { locale }
|
|
426
|
+
});
|
|
427
|
+
} catch (error) {
|
|
428
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
429
|
+
error
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
return result;
|
|
433
|
+
};
|
|
434
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
435
|
+
if (ctx.action !== "update") {
|
|
436
|
+
return next();
|
|
437
|
+
}
|
|
438
|
+
if (!utils.contentTypes.hasDraftAndPublish(ctx.contentType)) {
|
|
439
|
+
return next();
|
|
440
|
+
}
|
|
441
|
+
const contentType = ctx.contentType.uid;
|
|
442
|
+
const result = await next();
|
|
443
|
+
if (!result) {
|
|
444
|
+
return result;
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
448
|
+
} catch (error) {
|
|
449
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
450
|
+
error
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
return result;
|
|
454
|
+
};
|
|
455
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
456
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
457
|
+
where: {
|
|
458
|
+
actions: params
|
|
459
|
+
}
|
|
460
|
+
});
|
|
461
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
462
|
+
where: params
|
|
463
|
+
});
|
|
464
|
+
for (const release2 of releases) {
|
|
465
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
293
468
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
294
469
|
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
295
470
|
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
@@ -297,115 +472,29 @@ const bootstrap = async ({ strapi: strapi2 }) => {
|
|
|
297
472
|
);
|
|
298
473
|
strapi2.db.lifecycles.subscribe({
|
|
299
474
|
models: contentTypesWithDraftAndPublish,
|
|
300
|
-
async afterDelete(event) {
|
|
301
|
-
try {
|
|
302
|
-
const { model, result } = event;
|
|
303
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
304
|
-
const { id } = result;
|
|
305
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
306
|
-
where: {
|
|
307
|
-
actions: {
|
|
308
|
-
target_type: model.uid,
|
|
309
|
-
target_id: id
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
});
|
|
313
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
314
|
-
where: {
|
|
315
|
-
target_type: model.uid,
|
|
316
|
-
target_id: id
|
|
317
|
-
}
|
|
318
|
-
});
|
|
319
|
-
for (const release2 of releases) {
|
|
320
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
} catch (error) {
|
|
324
|
-
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
325
|
-
}
|
|
326
|
-
},
|
|
327
475
|
/**
|
|
328
|
-
* deleteMany
|
|
329
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
330
|
-
*/
|
|
331
|
-
async beforeDeleteMany(event) {
|
|
332
|
-
const { model, params } = event;
|
|
333
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
334
|
-
const { where } = params;
|
|
335
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
336
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
337
|
-
}
|
|
338
|
-
},
|
|
339
|
-
/**
|
|
340
|
-
* We delete the release actions related to deleted entries
|
|
341
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
476
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
342
477
|
*/
|
|
343
478
|
async afterDeleteMany(event) {
|
|
344
479
|
try {
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
target_id: {
|
|
353
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
359
|
-
where: {
|
|
360
|
-
target_type: model.uid,
|
|
361
|
-
target_id: {
|
|
362
|
-
$in: entriesToDelete.map((entry) => entry.id)
|
|
363
|
-
}
|
|
364
|
-
}
|
|
480
|
+
const model = strapi2.getModel(event.model.uid);
|
|
481
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
482
|
+
const { where } = event.params;
|
|
483
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
484
|
+
contentType: model.uid,
|
|
485
|
+
locale: where.locale ?? null,
|
|
486
|
+
...where.documentId && { entryDocumentId: where.documentId }
|
|
365
487
|
});
|
|
366
|
-
for (const release2 of releases) {
|
|
367
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
368
|
-
}
|
|
369
488
|
}
|
|
370
489
|
} catch (error) {
|
|
371
490
|
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
372
491
|
error
|
|
373
492
|
});
|
|
374
493
|
}
|
|
375
|
-
},
|
|
376
|
-
async afterUpdate(event) {
|
|
377
|
-
try {
|
|
378
|
-
const { model, result } = event;
|
|
379
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
380
|
-
const isEntryValid = await getEntryValidStatus(model.uid, result, {
|
|
381
|
-
strapi: strapi2
|
|
382
|
-
});
|
|
383
|
-
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
384
|
-
where: {
|
|
385
|
-
target_type: model.uid,
|
|
386
|
-
target_id: result.id
|
|
387
|
-
},
|
|
388
|
-
data: {
|
|
389
|
-
isEntryValid
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
393
|
-
where: {
|
|
394
|
-
actions: {
|
|
395
|
-
target_type: model.uid,
|
|
396
|
-
target_id: result.id
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
});
|
|
400
|
-
for (const release2 of releases) {
|
|
401
|
-
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
} catch (error) {
|
|
405
|
-
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
406
|
-
}
|
|
407
494
|
}
|
|
408
495
|
});
|
|
496
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
497
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
409
498
|
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
410
499
|
strapi2.log.error(
|
|
411
500
|
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
@@ -497,15 +586,13 @@ const schema = {
|
|
|
497
586
|
enum: ["publish", "unpublish"],
|
|
498
587
|
required: true
|
|
499
588
|
},
|
|
500
|
-
entry: {
|
|
501
|
-
type: "relation",
|
|
502
|
-
relation: "morphToOne",
|
|
503
|
-
configurable: false
|
|
504
|
-
},
|
|
505
589
|
contentType: {
|
|
506
590
|
type: "string",
|
|
507
591
|
required: true
|
|
508
592
|
},
|
|
593
|
+
entryDocumentId: {
|
|
594
|
+
type: "string"
|
|
595
|
+
},
|
|
509
596
|
locale: {
|
|
510
597
|
type: "string"
|
|
511
598
|
},
|
|
@@ -527,18 +614,6 @@ const contentTypes = {
|
|
|
527
614
|
release: release$1,
|
|
528
615
|
"release-action": releaseAction$1
|
|
529
616
|
};
|
|
530
|
-
const getGroupName = (queryValue) => {
|
|
531
|
-
switch (queryValue) {
|
|
532
|
-
case "contentType":
|
|
533
|
-
return "contentType.displayName";
|
|
534
|
-
case "action":
|
|
535
|
-
return "type";
|
|
536
|
-
case "locale":
|
|
537
|
-
return ___default.default.getOr("No locale", "locale.name");
|
|
538
|
-
default:
|
|
539
|
-
return "contentType.displayName";
|
|
540
|
-
}
|
|
541
|
-
};
|
|
542
617
|
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
543
618
|
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
544
619
|
strapi2.eventHub.emit(event, {
|
|
@@ -547,93 +622,32 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
547
622
|
release: release2
|
|
548
623
|
});
|
|
549
624
|
};
|
|
550
|
-
const publishSingleTypeAction = async (uid, actionType, entryId) => {
|
|
551
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
552
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
553
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
554
|
-
const entry = await strapi2.entityService.findOne(uid, entryId, { populate });
|
|
555
|
-
try {
|
|
556
|
-
if (actionType === "publish") {
|
|
557
|
-
await entityManagerService.publish(entry, uid);
|
|
558
|
-
} else {
|
|
559
|
-
await entityManagerService.unpublish(entry, uid);
|
|
560
|
-
}
|
|
561
|
-
} catch (error) {
|
|
562
|
-
if (error instanceof utils.errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
563
|
-
;
|
|
564
|
-
else {
|
|
565
|
-
throw error;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
const publishCollectionTypeAction = async (uid, entriesToPublishIds, entriestoUnpublishIds) => {
|
|
570
|
-
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
571
|
-
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
572
|
-
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
573
|
-
const entriesToPublish = await strapi2.entityService.findMany(uid, {
|
|
574
|
-
filters: {
|
|
575
|
-
id: {
|
|
576
|
-
$in: entriesToPublishIds
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
populate
|
|
580
|
-
});
|
|
581
|
-
const entriesToUnpublish = await strapi2.entityService.findMany(uid, {
|
|
582
|
-
filters: {
|
|
583
|
-
id: {
|
|
584
|
-
$in: entriestoUnpublishIds
|
|
585
|
-
}
|
|
586
|
-
},
|
|
587
|
-
populate
|
|
588
|
-
});
|
|
589
|
-
if (entriesToPublish.length > 0) {
|
|
590
|
-
await entityManagerService.publishMany(entriesToPublish, uid);
|
|
591
|
-
}
|
|
592
|
-
if (entriesToUnpublish.length > 0) {
|
|
593
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, uid);
|
|
594
|
-
}
|
|
595
|
-
};
|
|
596
625
|
const getFormattedActions = async (releaseId) => {
|
|
597
626
|
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
598
627
|
where: {
|
|
599
628
|
release: {
|
|
600
629
|
id: releaseId
|
|
601
630
|
}
|
|
602
|
-
},
|
|
603
|
-
populate: {
|
|
604
|
-
entry: {
|
|
605
|
-
fields: ["id"]
|
|
606
|
-
}
|
|
607
631
|
}
|
|
608
632
|
});
|
|
609
633
|
if (actions.length === 0) {
|
|
610
634
|
throw new utils.errors.ValidationError("No entries to publish");
|
|
611
635
|
}
|
|
612
|
-
const
|
|
613
|
-
const singleTypeActions = [];
|
|
636
|
+
const formattedActions = {};
|
|
614
637
|
for (const action of actions) {
|
|
615
638
|
const contentTypeUid = action.contentType;
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
};
|
|
622
|
-
}
|
|
623
|
-
if (action.type === "publish") {
|
|
624
|
-
collectionTypeActions[contentTypeUid].entriesToPublishIds.push(action.entry.id);
|
|
625
|
-
} else {
|
|
626
|
-
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
627
|
-
}
|
|
628
|
-
} else {
|
|
629
|
-
singleTypeActions.push({
|
|
630
|
-
uid: contentTypeUid,
|
|
631
|
-
action: action.type,
|
|
632
|
-
id: action.entry.id
|
|
633
|
-
});
|
|
639
|
+
if (!formattedActions[contentTypeUid]) {
|
|
640
|
+
formattedActions[contentTypeUid] = {
|
|
641
|
+
publish: [],
|
|
642
|
+
unpublish: []
|
|
643
|
+
};
|
|
634
644
|
}
|
|
645
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
646
|
+
documentId: action.entryDocumentId,
|
|
647
|
+
locale: action.locale
|
|
648
|
+
});
|
|
635
649
|
}
|
|
636
|
-
return
|
|
650
|
+
return formattedActions;
|
|
637
651
|
};
|
|
638
652
|
return {
|
|
639
653
|
async create(releaseData, { user }) {
|
|
@@ -680,91 +694,10 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
680
694
|
}
|
|
681
695
|
});
|
|
682
696
|
},
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
}
|
|
688
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
689
|
-
where: {
|
|
690
|
-
actions: {
|
|
691
|
-
target_type: contentTypeUid,
|
|
692
|
-
target_id: {
|
|
693
|
-
$in: entries
|
|
694
|
-
}
|
|
695
|
-
},
|
|
696
|
-
releasedAt: {
|
|
697
|
-
$null: true
|
|
698
|
-
}
|
|
699
|
-
},
|
|
700
|
-
populate: {
|
|
701
|
-
// Filter the action to get only the content type entry
|
|
702
|
-
actions: {
|
|
703
|
-
where: {
|
|
704
|
-
target_type: contentTypeUid,
|
|
705
|
-
target_id: {
|
|
706
|
-
$in: entries
|
|
707
|
-
}
|
|
708
|
-
},
|
|
709
|
-
populate: {
|
|
710
|
-
entry: {
|
|
711
|
-
select: ["id"]
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
});
|
|
717
|
-
return releases.map((release2) => {
|
|
718
|
-
if (release2.actions?.length) {
|
|
719
|
-
const actionsForEntry = release2.actions;
|
|
720
|
-
delete release2.actions;
|
|
721
|
-
return {
|
|
722
|
-
...release2,
|
|
723
|
-
actions: actionsForEntry
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
return release2;
|
|
727
|
-
});
|
|
728
|
-
},
|
|
729
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
730
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
731
|
-
where: {
|
|
732
|
-
releasedAt: {
|
|
733
|
-
$null: true
|
|
734
|
-
},
|
|
735
|
-
actions: {
|
|
736
|
-
target_type: contentTypeUid,
|
|
737
|
-
target_id: entryId
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
});
|
|
741
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
742
|
-
where: {
|
|
743
|
-
$or: [
|
|
744
|
-
{
|
|
745
|
-
id: {
|
|
746
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
747
|
-
}
|
|
748
|
-
},
|
|
749
|
-
{
|
|
750
|
-
actions: null
|
|
751
|
-
}
|
|
752
|
-
],
|
|
753
|
-
releasedAt: {
|
|
754
|
-
$null: true
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
});
|
|
758
|
-
return releases.map((release2) => {
|
|
759
|
-
if (release2.actions?.length) {
|
|
760
|
-
const [actionForEntry] = release2.actions;
|
|
761
|
-
delete release2.actions;
|
|
762
|
-
return {
|
|
763
|
-
...release2,
|
|
764
|
-
action: actionForEntry
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
return release2;
|
|
697
|
+
findMany(query) {
|
|
698
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
699
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
700
|
+
...dbQuery
|
|
768
701
|
});
|
|
769
702
|
},
|
|
770
703
|
async update(id, releaseData, { user }) {
|
|
@@ -800,131 +733,6 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
800
733
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
801
734
|
return updatedRelease;
|
|
802
735
|
},
|
|
803
|
-
async createAction(releaseId, action) {
|
|
804
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
805
|
-
strapi: strapi2
|
|
806
|
-
});
|
|
807
|
-
await Promise.all([
|
|
808
|
-
validateEntryContentType(action.entry.contentType),
|
|
809
|
-
validateUniqueEntry(releaseId, action)
|
|
810
|
-
]);
|
|
811
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
812
|
-
if (!release2) {
|
|
813
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
814
|
-
}
|
|
815
|
-
if (release2.releasedAt) {
|
|
816
|
-
throw new utils.errors.ValidationError("Release already published");
|
|
817
|
-
}
|
|
818
|
-
const { entry, type } = action;
|
|
819
|
-
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
820
|
-
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
821
|
-
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
822
|
-
data: {
|
|
823
|
-
type,
|
|
824
|
-
contentType: entry.contentType,
|
|
825
|
-
locale: entry.locale,
|
|
826
|
-
isEntryValid,
|
|
827
|
-
entry: {
|
|
828
|
-
id: entry.id,
|
|
829
|
-
__type: entry.contentType,
|
|
830
|
-
__pivot: { field: "entry" }
|
|
831
|
-
},
|
|
832
|
-
release: releaseId
|
|
833
|
-
},
|
|
834
|
-
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
835
|
-
});
|
|
836
|
-
this.updateReleaseStatus(releaseId);
|
|
837
|
-
return releaseAction2;
|
|
838
|
-
},
|
|
839
|
-
async findActions(releaseId, query) {
|
|
840
|
-
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
841
|
-
where: { id: releaseId },
|
|
842
|
-
select: ["id"]
|
|
843
|
-
});
|
|
844
|
-
if (!release2) {
|
|
845
|
-
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
846
|
-
}
|
|
847
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
848
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
849
|
-
...dbQuery,
|
|
850
|
-
populate: {
|
|
851
|
-
entry: {
|
|
852
|
-
populate: "*"
|
|
853
|
-
}
|
|
854
|
-
},
|
|
855
|
-
where: {
|
|
856
|
-
release: releaseId
|
|
857
|
-
}
|
|
858
|
-
});
|
|
859
|
-
},
|
|
860
|
-
async countActions(query) {
|
|
861
|
-
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
862
|
-
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
863
|
-
},
|
|
864
|
-
async groupActions(actions, groupBy) {
|
|
865
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
866
|
-
if (!acc.includes(action.contentType)) {
|
|
867
|
-
acc.push(action.contentType);
|
|
868
|
-
}
|
|
869
|
-
return acc;
|
|
870
|
-
}, []);
|
|
871
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(contentTypeUids);
|
|
872
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
873
|
-
const formattedData = actions.map((action) => {
|
|
874
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
875
|
-
return {
|
|
876
|
-
...action,
|
|
877
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
878
|
-
contentType: {
|
|
879
|
-
displayName,
|
|
880
|
-
mainFieldValue: action.entry[mainField],
|
|
881
|
-
uid: action.contentType
|
|
882
|
-
}
|
|
883
|
-
};
|
|
884
|
-
});
|
|
885
|
-
const groupName = getGroupName(groupBy);
|
|
886
|
-
return ___default.default.groupBy(groupName)(formattedData);
|
|
887
|
-
},
|
|
888
|
-
async getLocalesDataForActions() {
|
|
889
|
-
if (!strapi2.plugin("i18n")) {
|
|
890
|
-
return {};
|
|
891
|
-
}
|
|
892
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
893
|
-
return allLocales.reduce((acc, locale) => {
|
|
894
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
895
|
-
return acc;
|
|
896
|
-
}, {});
|
|
897
|
-
},
|
|
898
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
899
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
900
|
-
const contentTypesData = {};
|
|
901
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
902
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
903
|
-
uid: contentTypeUid
|
|
904
|
-
});
|
|
905
|
-
contentTypesData[contentTypeUid] = {
|
|
906
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
907
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
908
|
-
};
|
|
909
|
-
}
|
|
910
|
-
return contentTypesData;
|
|
911
|
-
},
|
|
912
|
-
getContentTypeModelsFromActions(actions) {
|
|
913
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
914
|
-
if (!acc.includes(action.contentType)) {
|
|
915
|
-
acc.push(action.contentType);
|
|
916
|
-
}
|
|
917
|
-
return acc;
|
|
918
|
-
}, []);
|
|
919
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
920
|
-
(acc, contentTypeUid) => {
|
|
921
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
922
|
-
return acc;
|
|
923
|
-
},
|
|
924
|
-
{}
|
|
925
|
-
);
|
|
926
|
-
return contentTypeModelsMap;
|
|
927
|
-
},
|
|
928
736
|
async getAllComponents() {
|
|
929
737
|
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
930
738
|
const components = await contentManagerComponentsService.findAllComponents();
|
|
@@ -990,20 +798,19 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
990
798
|
}
|
|
991
799
|
try {
|
|
992
800
|
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
993
|
-
const
|
|
994
|
-
await strapi2.db.transaction(
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
});
|
|
801
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
802
|
+
await strapi2.db.transaction(
|
|
803
|
+
async () => Promise.all(
|
|
804
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
805
|
+
const contentType = contentTypeUid;
|
|
806
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
807
|
+
return Promise.all([
|
|
808
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
809
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
810
|
+
]);
|
|
811
|
+
})
|
|
812
|
+
)
|
|
813
|
+
);
|
|
1007
814
|
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1008
815
|
where: {
|
|
1009
816
|
id: releaseId
|
|
@@ -1033,13 +840,226 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1033
840
|
};
|
|
1034
841
|
}
|
|
1035
842
|
});
|
|
1036
|
-
if (error instanceof Error) {
|
|
1037
|
-
throw error;
|
|
843
|
+
if (error instanceof Error) {
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
return release2;
|
|
847
|
+
},
|
|
848
|
+
async updateReleaseStatus(releaseId) {
|
|
849
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
850
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
851
|
+
releaseActionService.countActions({
|
|
852
|
+
filters: {
|
|
853
|
+
release: releaseId
|
|
854
|
+
}
|
|
855
|
+
}),
|
|
856
|
+
releaseActionService.countActions({
|
|
857
|
+
filters: {
|
|
858
|
+
release: releaseId,
|
|
859
|
+
isEntryValid: false
|
|
860
|
+
}
|
|
861
|
+
})
|
|
862
|
+
]);
|
|
863
|
+
if (totalActions > 0) {
|
|
864
|
+
if (invalidActions > 0) {
|
|
865
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
866
|
+
where: {
|
|
867
|
+
id: releaseId
|
|
868
|
+
},
|
|
869
|
+
data: {
|
|
870
|
+
status: "blocked"
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
875
|
+
where: {
|
|
876
|
+
id: releaseId
|
|
877
|
+
},
|
|
878
|
+
data: {
|
|
879
|
+
status: "ready"
|
|
880
|
+
}
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
884
|
+
where: {
|
|
885
|
+
id: releaseId
|
|
886
|
+
},
|
|
887
|
+
data: {
|
|
888
|
+
status: "empty"
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
};
|
|
894
|
+
const getGroupName = (queryValue) => {
|
|
895
|
+
switch (queryValue) {
|
|
896
|
+
case "contentType":
|
|
897
|
+
return "contentType.displayName";
|
|
898
|
+
case "type":
|
|
899
|
+
return "type";
|
|
900
|
+
case "locale":
|
|
901
|
+
return ___default.default.getOr("No locale", "locale.name");
|
|
902
|
+
default:
|
|
903
|
+
return "contentType.displayName";
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
907
|
+
const getLocalesDataForActions = async () => {
|
|
908
|
+
if (!strapi2.plugin("i18n")) {
|
|
909
|
+
return {};
|
|
910
|
+
}
|
|
911
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
912
|
+
return allLocales.reduce((acc, locale) => {
|
|
913
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
914
|
+
return acc;
|
|
915
|
+
}, {});
|
|
916
|
+
};
|
|
917
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
918
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
919
|
+
const contentTypesData = {};
|
|
920
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
921
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
922
|
+
uid: contentTypeUid
|
|
923
|
+
});
|
|
924
|
+
contentTypesData[contentTypeUid] = {
|
|
925
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
926
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
return contentTypesData;
|
|
930
|
+
};
|
|
931
|
+
return {
|
|
932
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
933
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
934
|
+
strapi: strapi2
|
|
935
|
+
});
|
|
936
|
+
await Promise.all([
|
|
937
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
938
|
+
validateUniqueEntry(releaseId, action)
|
|
939
|
+
]);
|
|
940
|
+
const model = strapi2.contentType(action.contentType);
|
|
941
|
+
if (model.kind === "singleType") {
|
|
942
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
943
|
+
if (!document) {
|
|
944
|
+
throw new utils.errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
945
|
+
}
|
|
946
|
+
action.entryDocumentId = document.documentId;
|
|
947
|
+
}
|
|
948
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
949
|
+
if (!release2) {
|
|
950
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
951
|
+
}
|
|
952
|
+
if (release2.releasedAt) {
|
|
953
|
+
throw new utils.errors.ValidationError("Release already published");
|
|
954
|
+
}
|
|
955
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
956
|
+
{
|
|
957
|
+
contentType: action.contentType,
|
|
958
|
+
documentId: action.entryDocumentId,
|
|
959
|
+
locale: action.locale
|
|
960
|
+
},
|
|
961
|
+
{
|
|
962
|
+
strapi: strapi2
|
|
963
|
+
}
|
|
964
|
+
) : true;
|
|
965
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
966
|
+
data: {
|
|
967
|
+
...action,
|
|
968
|
+
release: release2.id,
|
|
969
|
+
isEntryValid: actionStatus
|
|
970
|
+
},
|
|
971
|
+
populate: { release: { select: ["id"] } }
|
|
972
|
+
});
|
|
973
|
+
if (!disableUpdateReleaseStatus) {
|
|
974
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
1038
975
|
}
|
|
1039
|
-
return
|
|
976
|
+
return releaseAction2;
|
|
1040
977
|
},
|
|
1041
|
-
async
|
|
1042
|
-
const
|
|
978
|
+
async findPage(releaseId, query) {
|
|
979
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
980
|
+
where: { id: releaseId },
|
|
981
|
+
select: ["id"]
|
|
982
|
+
});
|
|
983
|
+
if (!release2) {
|
|
984
|
+
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
985
|
+
}
|
|
986
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
987
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
988
|
+
...dbQuery,
|
|
989
|
+
where: {
|
|
990
|
+
release: releaseId
|
|
991
|
+
}
|
|
992
|
+
});
|
|
993
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
994
|
+
const actionsWithEntry = await utils.async.map(actions, async (action) => {
|
|
995
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
996
|
+
const entry = await getEntry(
|
|
997
|
+
{
|
|
998
|
+
contentType: action.contentType,
|
|
999
|
+
documentId: action.entryDocumentId,
|
|
1000
|
+
locale: action.locale,
|
|
1001
|
+
populate,
|
|
1002
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
1003
|
+
},
|
|
1004
|
+
{ strapi: strapi2 }
|
|
1005
|
+
);
|
|
1006
|
+
return {
|
|
1007
|
+
...action,
|
|
1008
|
+
entry,
|
|
1009
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
1010
|
+
};
|
|
1011
|
+
});
|
|
1012
|
+
return {
|
|
1013
|
+
results: actionsWithEntry,
|
|
1014
|
+
pagination
|
|
1015
|
+
};
|
|
1016
|
+
},
|
|
1017
|
+
async groupActions(actions, groupBy) {
|
|
1018
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1019
|
+
if (!acc.includes(action.contentType)) {
|
|
1020
|
+
acc.push(action.contentType);
|
|
1021
|
+
}
|
|
1022
|
+
return acc;
|
|
1023
|
+
}, []);
|
|
1024
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1025
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1026
|
+
const formattedData = actions.map((action) => {
|
|
1027
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1028
|
+
return {
|
|
1029
|
+
...action,
|
|
1030
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1031
|
+
contentType: {
|
|
1032
|
+
displayName,
|
|
1033
|
+
mainFieldValue: action.entry[mainField],
|
|
1034
|
+
uid: action.contentType
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
});
|
|
1038
|
+
const groupName = getGroupName(groupBy);
|
|
1039
|
+
return ___default.default.groupBy(groupName)(formattedData);
|
|
1040
|
+
},
|
|
1041
|
+
getContentTypeModelsFromActions(actions) {
|
|
1042
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1043
|
+
if (!acc.includes(action.contentType)) {
|
|
1044
|
+
acc.push(action.contentType);
|
|
1045
|
+
}
|
|
1046
|
+
return acc;
|
|
1047
|
+
}, []);
|
|
1048
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
1049
|
+
(acc, contentTypeUid) => {
|
|
1050
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
1051
|
+
return acc;
|
|
1052
|
+
},
|
|
1053
|
+
{}
|
|
1054
|
+
);
|
|
1055
|
+
return contentTypeModelsMap;
|
|
1056
|
+
},
|
|
1057
|
+
async countActions(query) {
|
|
1058
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1059
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1060
|
+
},
|
|
1061
|
+
async update(actionId, releaseId, update) {
|
|
1062
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1043
1063
|
where: {
|
|
1044
1064
|
id: actionId,
|
|
1045
1065
|
release: {
|
|
@@ -1048,17 +1068,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1048
1068
|
$null: true
|
|
1049
1069
|
}
|
|
1050
1070
|
}
|
|
1051
|
-
}
|
|
1052
|
-
data: update
|
|
1071
|
+
}
|
|
1053
1072
|
});
|
|
1054
|
-
if (!
|
|
1073
|
+
if (!action) {
|
|
1055
1074
|
throw new utils.errors.NotFoundError(
|
|
1056
1075
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1057
1076
|
);
|
|
1058
1077
|
}
|
|
1078
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1079
|
+
{
|
|
1080
|
+
contentType: action.contentType,
|
|
1081
|
+
documentId: action.entryDocumentId,
|
|
1082
|
+
locale: action.locale
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
strapi: strapi2
|
|
1086
|
+
}
|
|
1087
|
+
) : true;
|
|
1088
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1089
|
+
where: {
|
|
1090
|
+
id: actionId,
|
|
1091
|
+
release: {
|
|
1092
|
+
id: releaseId,
|
|
1093
|
+
releasedAt: {
|
|
1094
|
+
$null: true
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
data: {
|
|
1099
|
+
...update,
|
|
1100
|
+
isEntryValid: actionStatus
|
|
1101
|
+
}
|
|
1102
|
+
});
|
|
1103
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1059
1104
|
return updatedAction;
|
|
1060
1105
|
},
|
|
1061
|
-
async
|
|
1106
|
+
async delete(actionId, releaseId) {
|
|
1062
1107
|
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1063
1108
|
where: {
|
|
1064
1109
|
id: actionId,
|
|
@@ -1075,51 +1120,8 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
1075
1120
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1076
1121
|
);
|
|
1077
1122
|
}
|
|
1078
|
-
|
|
1123
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1079
1124
|
return deletedAction;
|
|
1080
|
-
},
|
|
1081
|
-
async updateReleaseStatus(releaseId) {
|
|
1082
|
-
const [totalActions, invalidActions] = await Promise.all([
|
|
1083
|
-
this.countActions({
|
|
1084
|
-
filters: {
|
|
1085
|
-
release: releaseId
|
|
1086
|
-
}
|
|
1087
|
-
}),
|
|
1088
|
-
this.countActions({
|
|
1089
|
-
filters: {
|
|
1090
|
-
release: releaseId,
|
|
1091
|
-
isEntryValid: false
|
|
1092
|
-
}
|
|
1093
|
-
})
|
|
1094
|
-
]);
|
|
1095
|
-
if (totalActions > 0) {
|
|
1096
|
-
if (invalidActions > 0) {
|
|
1097
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1098
|
-
where: {
|
|
1099
|
-
id: releaseId
|
|
1100
|
-
},
|
|
1101
|
-
data: {
|
|
1102
|
-
status: "blocked"
|
|
1103
|
-
}
|
|
1104
|
-
});
|
|
1105
|
-
}
|
|
1106
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1107
|
-
where: {
|
|
1108
|
-
id: releaseId
|
|
1109
|
-
},
|
|
1110
|
-
data: {
|
|
1111
|
-
status: "ready"
|
|
1112
|
-
}
|
|
1113
|
-
});
|
|
1114
|
-
}
|
|
1115
|
-
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1116
|
-
where: {
|
|
1117
|
-
id: releaseId
|
|
1118
|
-
},
|
|
1119
|
-
data: {
|
|
1120
|
-
status: "empty"
|
|
1121
|
-
}
|
|
1122
|
-
});
|
|
1123
1125
|
}
|
|
1124
1126
|
};
|
|
1125
1127
|
};
|
|
@@ -1135,30 +1137,35 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
1135
1137
|
where: {
|
|
1136
1138
|
id: releaseId
|
|
1137
1139
|
},
|
|
1138
|
-
populate: {
|
|
1140
|
+
populate: {
|
|
1141
|
+
actions: true
|
|
1142
|
+
}
|
|
1139
1143
|
});
|
|
1140
1144
|
if (!release2) {
|
|
1141
1145
|
throw new utils.errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1142
1146
|
}
|
|
1143
1147
|
const isEntryInRelease = release2.actions.some(
|
|
1144
|
-
(action) =>
|
|
1148
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
1145
1149
|
);
|
|
1146
1150
|
if (isEntryInRelease) {
|
|
1147
1151
|
throw new AlreadyOnReleaseError(
|
|
1148
|
-
`Entry with
|
|
1152
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
1149
1153
|
);
|
|
1150
1154
|
}
|
|
1151
1155
|
},
|
|
1152
|
-
|
|
1156
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
1153
1157
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
1154
1158
|
if (!contentType) {
|
|
1155
1159
|
throw new utils.errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
1156
1160
|
}
|
|
1157
|
-
if (!contentType
|
|
1161
|
+
if (!utils.contentTypes.hasDraftAndPublish(contentType)) {
|
|
1158
1162
|
throw new utils.errors.ValidationError(
|
|
1159
1163
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
1160
1164
|
);
|
|
1161
1165
|
}
|
|
1166
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1167
|
+
throw new utils.errors.ValidationError("Document id is required for collection type");
|
|
1168
|
+
}
|
|
1162
1169
|
},
|
|
1163
1170
|
async validatePendingReleasesLimit() {
|
|
1164
1171
|
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
@@ -1247,78 +1254,165 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
1247
1254
|
}
|
|
1248
1255
|
};
|
|
1249
1256
|
};
|
|
1257
|
+
const DEFAULT_SETTINGS = {
|
|
1258
|
+
defaultTimezone: null
|
|
1259
|
+
};
|
|
1260
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1261
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1262
|
+
return {
|
|
1263
|
+
async update({ settings: settings2 }) {
|
|
1264
|
+
const store = await getStore();
|
|
1265
|
+
store.set({ key: "settings", value: settings2 });
|
|
1266
|
+
return settings2;
|
|
1267
|
+
},
|
|
1268
|
+
async find() {
|
|
1269
|
+
const store = await getStore();
|
|
1270
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1271
|
+
return {
|
|
1272
|
+
...DEFAULT_SETTINGS,
|
|
1273
|
+
...settings2 || {}
|
|
1274
|
+
};
|
|
1275
|
+
}
|
|
1276
|
+
};
|
|
1277
|
+
};
|
|
1250
1278
|
const services = {
|
|
1251
1279
|
release: createReleaseService,
|
|
1280
|
+
"release-action": createReleaseActionService,
|
|
1252
1281
|
"release-validation": createReleaseValidationService,
|
|
1253
|
-
scheduling: createSchedulingService
|
|
1282
|
+
scheduling: createSchedulingService,
|
|
1283
|
+
settings: createSettingsService
|
|
1254
1284
|
};
|
|
1255
|
-
const RELEASE_SCHEMA =
|
|
1256
|
-
name:
|
|
1257
|
-
scheduledAt:
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
otherwise: yup__namespace.string().nullable()
|
|
1263
|
-
}),
|
|
1264
|
-
timezone: yup__namespace.string().when("isScheduled", {
|
|
1265
|
-
is: true,
|
|
1266
|
-
then: yup__namespace.string().required().nullable(),
|
|
1267
|
-
otherwise: yup__namespace.string().nullable()
|
|
1268
|
-
}),
|
|
1269
|
-
date: yup__namespace.string().when("isScheduled", {
|
|
1270
|
-
is: true,
|
|
1271
|
-
then: yup__namespace.string().required().nullable(),
|
|
1272
|
-
otherwise: yup__namespace.string().nullable()
|
|
1285
|
+
const RELEASE_SCHEMA = utils.yup.object().shape({
|
|
1286
|
+
name: utils.yup.string().trim().required(),
|
|
1287
|
+
scheduledAt: utils.yup.string().nullable(),
|
|
1288
|
+
timezone: utils.yup.string().when("scheduledAt", {
|
|
1289
|
+
is: (value) => value !== null && value !== void 0,
|
|
1290
|
+
then: utils.yup.string().required(),
|
|
1291
|
+
otherwise: utils.yup.string().nullable()
|
|
1273
1292
|
})
|
|
1274
1293
|
}).required().noUnknown();
|
|
1294
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = utils.yup.object().shape({
|
|
1295
|
+
contentType: utils.yup.string().required(),
|
|
1296
|
+
entryDocumentId: utils.yup.string().nullable(),
|
|
1297
|
+
hasEntryAttached: utils.yup.string().nullable(),
|
|
1298
|
+
locale: utils.yup.string().nullable()
|
|
1299
|
+
}).required().noUnknown();
|
|
1275
1300
|
const validateRelease = utils.validateYupSchema(RELEASE_SCHEMA);
|
|
1301
|
+
const validatefindByDocumentAttachedParams = utils.validateYupSchema(
|
|
1302
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1303
|
+
);
|
|
1276
1304
|
const releaseController = {
|
|
1277
|
-
|
|
1305
|
+
/**
|
|
1306
|
+
* Find releases based on documents attached or not to the release.
|
|
1307
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1308
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1309
|
+
*/
|
|
1310
|
+
async findByDocumentAttached(ctx) {
|
|
1278
1311
|
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1279
1312
|
ability: ctx.state.userAbility,
|
|
1280
1313
|
model: RELEASE_MODEL_UID
|
|
1281
1314
|
});
|
|
1282
1315
|
await permissionsManager.validateQuery(ctx.query);
|
|
1283
1316
|
const releaseService = getService("release", { strapi });
|
|
1284
|
-
const
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1317
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1318
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1319
|
+
const model = strapi.getModel(query.contentType);
|
|
1320
|
+
if (model.kind && model.kind === "singleType") {
|
|
1321
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1322
|
+
if (!document) {
|
|
1323
|
+
throw new utils.errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1324
|
+
}
|
|
1325
|
+
query.entryDocumentId = document.documentId;
|
|
1326
|
+
}
|
|
1327
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1328
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1329
|
+
if (isEntryAttached) {
|
|
1330
|
+
const releases = await releaseService.findMany({
|
|
1331
|
+
where: {
|
|
1332
|
+
releasedAt: null,
|
|
1333
|
+
actions: {
|
|
1334
|
+
contentType,
|
|
1335
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1336
|
+
locale: locale ?? null
|
|
1337
|
+
}
|
|
1338
|
+
},
|
|
1339
|
+
populate: {
|
|
1299
1340
|
actions: {
|
|
1300
|
-
|
|
1301
|
-
|
|
1341
|
+
fields: ["type"],
|
|
1342
|
+
filters: {
|
|
1343
|
+
contentType,
|
|
1344
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1345
|
+
locale: locale ?? null
|
|
1302
1346
|
}
|
|
1303
1347
|
}
|
|
1304
|
-
}
|
|
1348
|
+
}
|
|
1349
|
+
});
|
|
1350
|
+
ctx.body = { data: releases };
|
|
1351
|
+
} else {
|
|
1352
|
+
const relatedReleases = await releaseService.findMany({
|
|
1353
|
+
where: {
|
|
1354
|
+
releasedAt: null,
|
|
1355
|
+
actions: {
|
|
1356
|
+
contentType,
|
|
1357
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1358
|
+
locale: locale ?? null
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1305
1361
|
});
|
|
1306
|
-
const
|
|
1362
|
+
const releases = await releaseService.findMany({
|
|
1307
1363
|
where: {
|
|
1364
|
+
$or: [
|
|
1365
|
+
{
|
|
1366
|
+
id: {
|
|
1367
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
actions: null
|
|
1372
|
+
}
|
|
1373
|
+
],
|
|
1308
1374
|
releasedAt: null
|
|
1309
1375
|
}
|
|
1310
1376
|
});
|
|
1311
|
-
ctx.body = { data
|
|
1377
|
+
ctx.body = { data: releases };
|
|
1312
1378
|
}
|
|
1313
1379
|
},
|
|
1380
|
+
async findPage(ctx) {
|
|
1381
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1382
|
+
ability: ctx.state.userAbility,
|
|
1383
|
+
model: RELEASE_MODEL_UID
|
|
1384
|
+
});
|
|
1385
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1386
|
+
const releaseService = getService("release", { strapi });
|
|
1387
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1388
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1389
|
+
const data = results.map((release2) => {
|
|
1390
|
+
const { actions, ...releaseData } = release2;
|
|
1391
|
+
return {
|
|
1392
|
+
...releaseData,
|
|
1393
|
+
actions: {
|
|
1394
|
+
meta: {
|
|
1395
|
+
count: actions.count
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
};
|
|
1399
|
+
});
|
|
1400
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1401
|
+
where: {
|
|
1402
|
+
releasedAt: null
|
|
1403
|
+
}
|
|
1404
|
+
});
|
|
1405
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1406
|
+
},
|
|
1314
1407
|
async findOne(ctx) {
|
|
1315
1408
|
const id = ctx.params.id;
|
|
1316
1409
|
const releaseService = getService("release", { strapi });
|
|
1410
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1317
1411
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
1318
1412
|
if (!release2) {
|
|
1319
1413
|
throw new utils.errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1320
1414
|
}
|
|
1321
|
-
const count = await
|
|
1415
|
+
const count = await releaseActionService.countActions({
|
|
1322
1416
|
filters: {
|
|
1323
1417
|
release: id
|
|
1324
1418
|
}
|
|
@@ -1338,28 +1432,43 @@ const releaseController = {
|
|
|
1338
1432
|
ctx.body = { data };
|
|
1339
1433
|
},
|
|
1340
1434
|
async mapEntriesToReleases(ctx) {
|
|
1341
|
-
const { contentTypeUid,
|
|
1342
|
-
if (!contentTypeUid || !
|
|
1435
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1436
|
+
if (!contentTypeUid || !documentIds) {
|
|
1343
1437
|
throw new utils.errors.ValidationError("Missing required query parameters");
|
|
1344
1438
|
}
|
|
1345
1439
|
const releaseService = getService("release", { strapi });
|
|
1346
|
-
const releasesWithActions = await releaseService.
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1440
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1441
|
+
where: {
|
|
1442
|
+
releasedAt: null,
|
|
1443
|
+
actions: {
|
|
1444
|
+
contentType: contentTypeUid,
|
|
1445
|
+
entryDocumentId: {
|
|
1446
|
+
$in: documentIds
|
|
1447
|
+
},
|
|
1448
|
+
locale
|
|
1449
|
+
}
|
|
1450
|
+
},
|
|
1451
|
+
populate: {
|
|
1452
|
+
actions: true
|
|
1453
|
+
}
|
|
1454
|
+
});
|
|
1350
1455
|
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1351
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1352
1456
|
(acc, release2) => {
|
|
1353
1457
|
release2.actions.forEach((action) => {
|
|
1354
|
-
if (
|
|
1355
|
-
|
|
1458
|
+
if (action.contentType !== contentTypeUid) {
|
|
1459
|
+
return;
|
|
1460
|
+
}
|
|
1461
|
+
if (locale && action.locale !== locale) {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
if (!acc[action.entryDocumentId]) {
|
|
1465
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1356
1466
|
} else {
|
|
1357
|
-
acc[action.
|
|
1467
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1358
1468
|
}
|
|
1359
1469
|
});
|
|
1360
1470
|
return acc;
|
|
1361
1471
|
},
|
|
1362
|
-
// TODO: Fix for v5 removed mappedEntriedToRelease
|
|
1363
1472
|
{}
|
|
1364
1473
|
);
|
|
1365
1474
|
ctx.body = {
|
|
@@ -1404,18 +1513,18 @@ const releaseController = {
|
|
|
1404
1513
|
};
|
|
1405
1514
|
},
|
|
1406
1515
|
async publish(ctx) {
|
|
1407
|
-
const user = ctx.state.user;
|
|
1408
1516
|
const id = ctx.params.id;
|
|
1409
1517
|
const releaseService = getService("release", { strapi });
|
|
1410
|
-
const
|
|
1518
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1519
|
+
const release2 = await releaseService.publish(id);
|
|
1411
1520
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1412
|
-
|
|
1521
|
+
releaseActionService.countActions({
|
|
1413
1522
|
filters: {
|
|
1414
1523
|
release: id,
|
|
1415
1524
|
type: "publish"
|
|
1416
1525
|
}
|
|
1417
1526
|
}),
|
|
1418
|
-
|
|
1527
|
+
releaseActionService.countActions({
|
|
1419
1528
|
filters: {
|
|
1420
1529
|
release: id,
|
|
1421
1530
|
type: "unpublish"
|
|
@@ -1433,24 +1542,27 @@ const releaseController = {
|
|
|
1433
1542
|
}
|
|
1434
1543
|
};
|
|
1435
1544
|
const RELEASE_ACTION_SCHEMA = utils.yup.object().shape({
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
}).required(),
|
|
1545
|
+
contentType: utils.yup.string().required(),
|
|
1546
|
+
entryDocumentId: utils.yup.strapiID(),
|
|
1547
|
+
locale: utils.yup.string(),
|
|
1440
1548
|
type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
|
|
1441
1549
|
});
|
|
1442
1550
|
const RELEASE_ACTION_UPDATE_SCHEMA = utils.yup.object().shape({
|
|
1443
1551
|
type: utils.yup.string().oneOf(["publish", "unpublish"]).required()
|
|
1444
1552
|
});
|
|
1553
|
+
const FIND_MANY_ACTIONS_PARAMS = utils.yup.object().shape({
|
|
1554
|
+
groupBy: utils.yup.string().oneOf(["action", "contentType", "locale"])
|
|
1555
|
+
});
|
|
1445
1556
|
const validateReleaseAction = utils.validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
1446
1557
|
const validateReleaseActionUpdateSchema = utils.validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1558
|
+
const validateFindManyActionsParams = utils.validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
1447
1559
|
const releaseActionController = {
|
|
1448
1560
|
async create(ctx) {
|
|
1449
1561
|
const releaseId = ctx.params.releaseId;
|
|
1450
1562
|
const releaseActionArgs = ctx.request.body;
|
|
1451
1563
|
await validateReleaseAction(releaseActionArgs);
|
|
1452
|
-
const
|
|
1453
|
-
const releaseAction2 = await
|
|
1564
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1565
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1454
1566
|
ctx.created({
|
|
1455
1567
|
data: releaseAction2
|
|
1456
1568
|
});
|
|
@@ -1461,12 +1573,15 @@ const releaseActionController = {
|
|
|
1461
1573
|
await Promise.all(
|
|
1462
1574
|
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1463
1575
|
);
|
|
1576
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1464
1577
|
const releaseService = getService("release", { strapi });
|
|
1465
1578
|
const releaseActions = await strapi.db.transaction(async () => {
|
|
1466
1579
|
const releaseActions2 = await Promise.all(
|
|
1467
1580
|
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1468
1581
|
try {
|
|
1469
|
-
const action = await
|
|
1582
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1583
|
+
disableUpdateReleaseStatus: true
|
|
1584
|
+
});
|
|
1470
1585
|
return action;
|
|
1471
1586
|
} catch (error) {
|
|
1472
1587
|
if (error instanceof AlreadyOnReleaseError) {
|
|
@@ -1479,6 +1594,9 @@ const releaseActionController = {
|
|
|
1479
1594
|
return releaseActions2;
|
|
1480
1595
|
});
|
|
1481
1596
|
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1597
|
+
if (newReleaseActions.length > 0) {
|
|
1598
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1599
|
+
}
|
|
1482
1600
|
ctx.created({
|
|
1483
1601
|
data: newReleaseActions,
|
|
1484
1602
|
meta: {
|
|
@@ -1493,10 +1611,17 @@ const releaseActionController = {
|
|
|
1493
1611
|
ability: ctx.state.userAbility,
|
|
1494
1612
|
model: RELEASE_ACTION_MODEL_UID
|
|
1495
1613
|
});
|
|
1614
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1615
|
+
if (ctx.query.groupBy) {
|
|
1616
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1617
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1618
|
+
}
|
|
1619
|
+
}
|
|
1620
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1621
|
+
delete ctx.query.groupBy;
|
|
1496
1622
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1497
|
-
const
|
|
1498
|
-
const { results, pagination } = await
|
|
1499
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1623
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1624
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1500
1625
|
...query
|
|
1501
1626
|
});
|
|
1502
1627
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
@@ -1512,10 +1637,11 @@ const releaseActionController = {
|
|
|
1512
1637
|
}, {});
|
|
1513
1638
|
const sanitizedResults = await utils.async.map(results, async (action) => ({
|
|
1514
1639
|
...action,
|
|
1515
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1640
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1516
1641
|
}));
|
|
1517
|
-
const groupedData = await
|
|
1518
|
-
const contentTypes2 =
|
|
1642
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1643
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1644
|
+
const releaseService = getService("release", { strapi });
|
|
1519
1645
|
const components = await releaseService.getAllComponents();
|
|
1520
1646
|
ctx.body = {
|
|
1521
1647
|
data: groupedData,
|
|
@@ -1531,8 +1657,8 @@ const releaseActionController = {
|
|
|
1531
1657
|
const releaseId = ctx.params.releaseId;
|
|
1532
1658
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
1533
1659
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
1534
|
-
const
|
|
1535
|
-
const updatedAction = await
|
|
1660
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1661
|
+
const updatedAction = await releaseActionService.update(
|
|
1536
1662
|
actionId,
|
|
1537
1663
|
releaseId,
|
|
1538
1664
|
releaseActionUpdateArgs
|
|
@@ -1544,14 +1670,36 @@ const releaseActionController = {
|
|
|
1544
1670
|
async delete(ctx) {
|
|
1545
1671
|
const actionId = ctx.params.actionId;
|
|
1546
1672
|
const releaseId = ctx.params.releaseId;
|
|
1547
|
-
const
|
|
1548
|
-
const deletedReleaseAction = await
|
|
1673
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1674
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
1549
1675
|
ctx.body = {
|
|
1550
1676
|
data: deletedReleaseAction
|
|
1551
1677
|
};
|
|
1552
1678
|
}
|
|
1553
1679
|
};
|
|
1554
|
-
const
|
|
1680
|
+
const SETTINGS_SCHEMA = yup__namespace.object().shape({
|
|
1681
|
+
defaultTimezone: yup__namespace.string().nullable().default(null)
|
|
1682
|
+
}).required().noUnknown();
|
|
1683
|
+
const validateSettings = utils.validateYupSchema(SETTINGS_SCHEMA);
|
|
1684
|
+
const settingsController = {
|
|
1685
|
+
async find(ctx) {
|
|
1686
|
+
const settingsService = getService("settings", { strapi });
|
|
1687
|
+
const settings2 = await settingsService.find();
|
|
1688
|
+
ctx.body = { data: settings2 };
|
|
1689
|
+
},
|
|
1690
|
+
async update(ctx) {
|
|
1691
|
+
const settingsBody = ctx.request.body;
|
|
1692
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1693
|
+
const settingsService = getService("settings", { strapi });
|
|
1694
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1695
|
+
ctx.body = { data: updatedSettings };
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
const controllers = {
|
|
1699
|
+
release: releaseController,
|
|
1700
|
+
"release-action": releaseActionController,
|
|
1701
|
+
settings: settingsController
|
|
1702
|
+
};
|
|
1555
1703
|
const release = {
|
|
1556
1704
|
type: "admin",
|
|
1557
1705
|
routes: [
|
|
@@ -1571,6 +1719,22 @@ const release = {
|
|
|
1571
1719
|
]
|
|
1572
1720
|
}
|
|
1573
1721
|
},
|
|
1722
|
+
{
|
|
1723
|
+
method: "GET",
|
|
1724
|
+
path: "/getByDocumentAttached",
|
|
1725
|
+
handler: "release.findByDocumentAttached",
|
|
1726
|
+
config: {
|
|
1727
|
+
policies: [
|
|
1728
|
+
"admin::isAuthenticatedAdmin",
|
|
1729
|
+
{
|
|
1730
|
+
name: "admin::hasPermissions",
|
|
1731
|
+
config: {
|
|
1732
|
+
actions: ["plugin::content-releases.read"]
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
]
|
|
1736
|
+
}
|
|
1737
|
+
},
|
|
1574
1738
|
{
|
|
1575
1739
|
method: "POST",
|
|
1576
1740
|
path: "/",
|
|
@@ -1590,7 +1754,7 @@ const release = {
|
|
|
1590
1754
|
{
|
|
1591
1755
|
method: "GET",
|
|
1592
1756
|
path: "/",
|
|
1593
|
-
handler: "release.
|
|
1757
|
+
handler: "release.findPage",
|
|
1594
1758
|
config: {
|
|
1595
1759
|
policies: [
|
|
1596
1760
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1754,7 +1918,45 @@ const releaseAction = {
|
|
|
1754
1918
|
}
|
|
1755
1919
|
]
|
|
1756
1920
|
};
|
|
1921
|
+
const settings = {
|
|
1922
|
+
type: "admin",
|
|
1923
|
+
routes: [
|
|
1924
|
+
{
|
|
1925
|
+
method: "GET",
|
|
1926
|
+
path: "/settings",
|
|
1927
|
+
handler: "settings.find",
|
|
1928
|
+
config: {
|
|
1929
|
+
policies: [
|
|
1930
|
+
"admin::isAuthenticatedAdmin",
|
|
1931
|
+
{
|
|
1932
|
+
name: "admin::hasPermissions",
|
|
1933
|
+
config: {
|
|
1934
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
]
|
|
1938
|
+
}
|
|
1939
|
+
},
|
|
1940
|
+
{
|
|
1941
|
+
method: "PUT",
|
|
1942
|
+
path: "/settings",
|
|
1943
|
+
handler: "settings.update",
|
|
1944
|
+
config: {
|
|
1945
|
+
policies: [
|
|
1946
|
+
"admin::isAuthenticatedAdmin",
|
|
1947
|
+
{
|
|
1948
|
+
name: "admin::hasPermissions",
|
|
1949
|
+
config: {
|
|
1950
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
]
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
]
|
|
1957
|
+
};
|
|
1757
1958
|
const routes = {
|
|
1959
|
+
settings,
|
|
1758
1960
|
release,
|
|
1759
1961
|
"release-action": releaseAction
|
|
1760
1962
|
};
|