@strapi/content-releases 0.0.0-next.6a58621932ad3d83bf9d6928c1871e7906adcd59 → 0.0.0-next.6d1431fe2a53b80b47ad9f00187cd7bae9d5f46b
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-BKB1esYS.js +1395 -0
- package/dist/_chunks/App-BKB1esYS.js.map +1 -0
- package/dist/_chunks/App-Cne--1Z8.mjs +1374 -0
- package/dist/_chunks/App-Cne--1Z8.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js +52 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs +52 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-C1WwGWIH.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-kuXIwpWp.js.map +1 -0
- package/dist/_chunks/{en-gYDqKYFd.js → en-CmYoEnA7.js} +37 -7
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/{en-MyLPoISH.mjs → en-D0yVZFqf.mjs} +37 -7
- package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
- package/dist/_chunks/index-5Odi61vw.js +1381 -0
- package/dist/_chunks/index-5Odi61vw.js.map +1 -0
- package/dist/_chunks/index-Cy7qwpaU.mjs +1362 -0
- package/dist/_chunks/index-Cy7qwpaU.mjs.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 -15
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +2 -16
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionModal.d.ts +24 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +17 -0
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +76 -0
- package/dist/admin/src/index.d.ts +3 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -0
- package/dist/admin/src/pages/App.d.ts +1 -0
- package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
- package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
- package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
- package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
- package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
- package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/services/release.d.ts +112 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/time.d.ts +10 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +1359 -495
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1360 -496
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/constants.d.ts +21 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +97 -0
- package/dist/server/src/content-types/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/index.d.ts +48 -0
- package/dist/server/src/content-types/release/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/schema.d.ts +47 -0
- package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/index.d.ts +48 -0
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/schema.d.ts +47 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +25 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +10 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +18 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- 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 +14 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +4 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
- 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/destroy.d.ts +5 -0
- package/dist/server/src/destroy.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +2115 -0
- package/dist/server/src/index.d.ts.map +1 -0
- 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 +13 -0
- package/dist/server/src/migrations/index.d.ts.map +1 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/register.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts +51 -0
- package/dist/server/src/routes/index.d.ts.map +1 -0
- package/dist/server/src/routes/release-action.d.ts +18 -0
- package/dist/server/src/routes/release-action.d.ts.map +1 -0
- package/dist/server/src/routes/release.d.ts +18 -0
- package/dist/server/src/routes/release.d.ts.map +1 -0
- 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 +1828 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- 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 +31 -0
- package/dist/server/src/services/release.d.ts.map +1 -0
- package/dist/server/src/services/scheduling.d.ts +18 -0
- package/dist/server/src/services/scheduling.d.ts.map +1 -0
- 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 +18 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +35 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +130 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +184 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +35 -37
- package/dist/_chunks/App-L1jSxCiL.mjs +0 -1015
- package/dist/_chunks/App-L1jSxCiL.mjs.map +0 -1
- package/dist/_chunks/App-_20W9dYa.js +0 -1037
- package/dist/_chunks/App-_20W9dYa.js.map +0 -1
- package/dist/_chunks/en-MyLPoISH.mjs.map +0 -1
- package/dist/_chunks/en-gYDqKYFd.js.map +0 -1
- package/dist/_chunks/index-KJa1Rb5F.js +0 -908
- package/dist/_chunks/index-KJa1Rb5F.js.map +0 -1
- package/dist/_chunks/index-c4zRX_sg.mjs +0 -887
- package/dist/_chunks/index-c4zRX_sg.mjs.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { contentTypes as contentTypes$1,
|
|
1
|
+
import { contentTypes as contentTypes$1, async, setCreatorFields, errors, yup as yup$1, validateYupSchema } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
2
3
|
import { difference, keys } from "lodash";
|
|
3
4
|
import _ from "lodash/fp";
|
|
4
|
-
import
|
|
5
|
+
import { scheduleJob } from "node-schedule";
|
|
5
6
|
import * as yup from "yup";
|
|
6
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
7
8
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -47,8 +48,83 @@ const ACTIONS = [
|
|
|
47
48
|
displayName: "Add an entry to a release",
|
|
48
49
|
uid: "create-action",
|
|
49
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"
|
|
50
68
|
}
|
|
51
69
|
];
|
|
70
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
71
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
72
|
+
};
|
|
73
|
+
const getService = (name, { strapi: strapi2 }) => {
|
|
74
|
+
return strapi2.plugin("content-releases").service(name);
|
|
75
|
+
};
|
|
76
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
77
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
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 });
|
|
81
|
+
};
|
|
82
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
83
|
+
try {
|
|
84
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
85
|
+
strapi2.getModel(contentTypeUid),
|
|
86
|
+
entry,
|
|
87
|
+
void 0,
|
|
88
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
89
|
+
entry
|
|
90
|
+
);
|
|
91
|
+
return true;
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
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
|
+
};
|
|
52
128
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
53
129
|
oldContentTypes,
|
|
54
130
|
contentTypes: contentTypes2
|
|
@@ -70,66 +146,353 @@ async function deleteActionsOnDisableDraftAndPublish({
|
|
|
70
146
|
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
71
147
|
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
|
|
72
148
|
if (deletedContentTypes.length) {
|
|
73
|
-
await
|
|
149
|
+
await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
74
150
|
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
75
151
|
});
|
|
76
152
|
}
|
|
77
153
|
}
|
|
78
|
-
|
|
154
|
+
async function migrateIsValidAndStatusReleases() {
|
|
155
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
156
|
+
where: {
|
|
157
|
+
status: null,
|
|
158
|
+
releasedAt: null
|
|
159
|
+
},
|
|
160
|
+
populate: {
|
|
161
|
+
actions: {
|
|
162
|
+
populate: {
|
|
163
|
+
entry: true
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
async.map(releasesWithoutStatus, async (release2) => {
|
|
169
|
+
const actions = release2.actions;
|
|
170
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
171
|
+
for (const action of notValidatedActions) {
|
|
172
|
+
if (action.entry) {
|
|
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
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
192
|
+
});
|
|
193
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
194
|
+
where: {
|
|
195
|
+
status: null,
|
|
196
|
+
releasedAt: {
|
|
197
|
+
$notNull: true
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
async.map(publishedReleases, async (release2) => {
|
|
202
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
203
|
+
where: {
|
|
204
|
+
id: release2.id
|
|
205
|
+
},
|
|
206
|
+
data: {
|
|
207
|
+
status: "done"
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
213
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
214
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
215
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
216
|
+
);
|
|
217
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
218
|
+
async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
219
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
220
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
221
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
222
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
223
|
+
where: {
|
|
224
|
+
contentType: contentTypeUID
|
|
225
|
+
},
|
|
226
|
+
populate: {
|
|
227
|
+
entry: true,
|
|
228
|
+
release: true
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
await async.map(actions, async (action) => {
|
|
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
|
+
}
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}).then(() => {
|
|
254
|
+
async.map(releasesAffected, async (releaseId) => {
|
|
255
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
261
|
+
if (!oldContentTypes) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
265
|
+
if (!i18nPlugin) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
for (const uid in contentTypes2) {
|
|
269
|
+
if (!oldContentTypes[uid]) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
const oldContentType = oldContentTypes[uid];
|
|
273
|
+
const contentType = contentTypes2[uid];
|
|
274
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
275
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
276
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
277
|
+
locale: null
|
|
278
|
+
}).where({ contentType: uid }).execute();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
283
|
+
if (!oldContentTypes) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
287
|
+
if (!i18nPlugin) {
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
for (const uid in contentTypes2) {
|
|
291
|
+
if (!oldContentTypes[uid]) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
const oldContentType = oldContentTypes[uid];
|
|
295
|
+
const contentType = contentTypes2[uid];
|
|
296
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
297
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
298
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
299
|
+
const defaultLocale = await getDefaultLocale();
|
|
300
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
301
|
+
locale: defaultLocale
|
|
302
|
+
}).where({ contentType: uid }).execute();
|
|
303
|
+
}
|
|
304
|
+
}
|
|
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 hasTable = await trx.schema.hasTable("strapi_release_actions");
|
|
310
|
+
if (!hasTable) {
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
314
|
+
if (hasPolymorphicColumn) {
|
|
315
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
316
|
+
"strapi_release_actions",
|
|
317
|
+
"entry_document_id"
|
|
318
|
+
);
|
|
319
|
+
if (!hasEntryDocumentIdColumn) {
|
|
320
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
321
|
+
table.string("entry_document_id");
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
325
|
+
async.map(releaseActions, async (action) => {
|
|
326
|
+
const { target_type, target_id } = action;
|
|
327
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
328
|
+
if (entry) {
|
|
329
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
},
|
|
334
|
+
async down() {
|
|
335
|
+
throw new Error("not implemented");
|
|
336
|
+
}
|
|
337
|
+
};
|
|
79
338
|
const register = async ({ strapi: strapi2 }) => {
|
|
80
|
-
if (features
|
|
81
|
-
await strapi2.admin
|
|
82
|
-
strapi2.
|
|
83
|
-
strapi2.hook("strapi::content-types.
|
|
339
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
340
|
+
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
341
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
342
|
+
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
343
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
344
|
+
}
|
|
345
|
+
if (strapi2.plugin("graphql")) {
|
|
346
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
347
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
348
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
352
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
353
|
+
where: {
|
|
354
|
+
actions: {
|
|
355
|
+
contentType,
|
|
356
|
+
entryDocumentId: entry.documentId,
|
|
357
|
+
locale: entry.locale
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
362
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
363
|
+
where: {
|
|
364
|
+
contentType,
|
|
365
|
+
entryDocumentId: entry.documentId,
|
|
366
|
+
locale: entry.locale
|
|
367
|
+
},
|
|
368
|
+
data: {
|
|
369
|
+
isEntryValid: entryStatus
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
for (const release2 of releases) {
|
|
373
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
377
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
378
|
+
where: {
|
|
379
|
+
actions: params
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
383
|
+
where: params
|
|
384
|
+
});
|
|
385
|
+
for (const release2 of releases) {
|
|
386
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
390
|
+
if (ctx.action !== "delete") {
|
|
391
|
+
return next();
|
|
392
|
+
}
|
|
393
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
394
|
+
return next();
|
|
395
|
+
}
|
|
396
|
+
const contentType = ctx.contentType.uid;
|
|
397
|
+
const { documentId, locale } = ctx.params;
|
|
398
|
+
const result = await next();
|
|
399
|
+
if (!result) {
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
try {
|
|
403
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
404
|
+
contentType,
|
|
405
|
+
entryDocumentId: documentId,
|
|
406
|
+
...locale !== "*" && { locale }
|
|
407
|
+
});
|
|
408
|
+
} catch (error) {
|
|
409
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
410
|
+
error
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
return result;
|
|
414
|
+
};
|
|
415
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
416
|
+
if (ctx.action !== "update") {
|
|
417
|
+
return next();
|
|
418
|
+
}
|
|
419
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
420
|
+
return next();
|
|
421
|
+
}
|
|
422
|
+
const contentType = ctx.contentType.uid;
|
|
423
|
+
const result = await next();
|
|
424
|
+
if (!result) {
|
|
425
|
+
return result;
|
|
426
|
+
}
|
|
427
|
+
try {
|
|
428
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
431
|
+
error
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
};
|
|
436
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
437
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
438
|
+
where: {
|
|
439
|
+
actions: params
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
443
|
+
where: params
|
|
444
|
+
});
|
|
445
|
+
for (const release2 of releases) {
|
|
446
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
84
447
|
}
|
|
85
448
|
};
|
|
86
|
-
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
87
449
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
88
|
-
if (features
|
|
450
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
451
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
452
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
453
|
+
);
|
|
89
454
|
strapi2.db.lifecycles.subscribe({
|
|
90
|
-
|
|
91
|
-
const { model, result } = event;
|
|
92
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
93
|
-
const { id } = result;
|
|
94
|
-
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
95
|
-
where: {
|
|
96
|
-
target_type: model.uid,
|
|
97
|
-
target_id: id
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
},
|
|
102
|
-
/**
|
|
103
|
-
* deleteMany hook doesn't return the deleted entries ids
|
|
104
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
105
|
-
*/
|
|
106
|
-
async beforeDeleteMany(event) {
|
|
107
|
-
const { model, params } = event;
|
|
108
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
109
|
-
const { where } = params;
|
|
110
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
111
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
112
|
-
}
|
|
113
|
-
},
|
|
455
|
+
models: contentTypesWithDraftAndPublish,
|
|
114
456
|
/**
|
|
115
|
-
*
|
|
116
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
457
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
117
458
|
*/
|
|
118
459
|
async afterDeleteMany(event) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
460
|
+
try {
|
|
461
|
+
const model = strapi2.getModel(event.model.uid);
|
|
462
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
463
|
+
const { where } = event.params;
|
|
464
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
465
|
+
contentType: model.uid,
|
|
466
|
+
locale: where.locale ?? null,
|
|
467
|
+
...where.documentId && { entryDocumentId: where.documentId }
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
} catch (error) {
|
|
471
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
472
|
+
error
|
|
129
473
|
});
|
|
130
474
|
}
|
|
131
475
|
}
|
|
132
476
|
});
|
|
477
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
478
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
479
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
480
|
+
strapi2.log.error(
|
|
481
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
482
|
+
);
|
|
483
|
+
throw err;
|
|
484
|
+
});
|
|
485
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
486
|
+
strapi2.get("webhookStore").addAllowedEvent(key, value);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
};
|
|
490
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
491
|
+
const scheduledJobs = getService("scheduling", {
|
|
492
|
+
strapi: strapi2
|
|
493
|
+
}).getAll();
|
|
494
|
+
for (const [, job] of scheduledJobs) {
|
|
495
|
+
job.cancel();
|
|
133
496
|
}
|
|
134
497
|
};
|
|
135
498
|
const schema$1 = {
|
|
@@ -158,6 +521,17 @@ const schema$1 = {
|
|
|
158
521
|
releasedAt: {
|
|
159
522
|
type: "datetime"
|
|
160
523
|
},
|
|
524
|
+
scheduledAt: {
|
|
525
|
+
type: "datetime"
|
|
526
|
+
},
|
|
527
|
+
timezone: {
|
|
528
|
+
type: "string"
|
|
529
|
+
},
|
|
530
|
+
status: {
|
|
531
|
+
type: "enumeration",
|
|
532
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
533
|
+
required: true
|
|
534
|
+
},
|
|
161
535
|
actions: {
|
|
162
536
|
type: "relation",
|
|
163
537
|
relation: "oneToMany",
|
|
@@ -193,15 +567,13 @@ const schema = {
|
|
|
193
567
|
enum: ["publish", "unpublish"],
|
|
194
568
|
required: true
|
|
195
569
|
},
|
|
196
|
-
entry: {
|
|
197
|
-
type: "relation",
|
|
198
|
-
relation: "morphToOne",
|
|
199
|
-
configurable: false
|
|
200
|
-
},
|
|
201
570
|
contentType: {
|
|
202
571
|
type: "string",
|
|
203
572
|
required: true
|
|
204
573
|
},
|
|
574
|
+
entryDocumentId: {
|
|
575
|
+
type: "string"
|
|
576
|
+
},
|
|
205
577
|
locale: {
|
|
206
578
|
type: "string"
|
|
207
579
|
},
|
|
@@ -210,6 +582,9 @@ const schema = {
|
|
|
210
582
|
relation: "manyToOne",
|
|
211
583
|
target: RELEASE_MODEL_UID,
|
|
212
584
|
inversedBy: "actions"
|
|
585
|
+
},
|
|
586
|
+
isEntryValid: {
|
|
587
|
+
type: "boolean"
|
|
213
588
|
}
|
|
214
589
|
}
|
|
215
590
|
};
|
|
@@ -220,226 +595,297 @@ const contentTypes = {
|
|
|
220
595
|
release: release$1,
|
|
221
596
|
"release-action": releaseAction$1
|
|
222
597
|
};
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
return "contentType.displayName";
|
|
230
|
-
case "action":
|
|
231
|
-
return "type";
|
|
232
|
-
case "locale":
|
|
233
|
-
return _.getOr("No locale", "locale.name");
|
|
234
|
-
default:
|
|
235
|
-
return "contentType.displayName";
|
|
236
|
-
}
|
|
237
|
-
};
|
|
238
|
-
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
239
|
-
async create(releaseData, { user }) {
|
|
240
|
-
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
241
|
-
const { validatePendingReleasesLimit, validateUniqueNameForPendingRelease } = getService(
|
|
242
|
-
"release-validation",
|
|
243
|
-
{ strapi: strapi2 }
|
|
244
|
-
);
|
|
245
|
-
await Promise.all([
|
|
246
|
-
validatePendingReleasesLimit(),
|
|
247
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name)
|
|
248
|
-
]);
|
|
249
|
-
return strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
250
|
-
data: releaseWithCreatorFields
|
|
598
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
599
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
600
|
+
strapi2.eventHub.emit(event, {
|
|
601
|
+
isPublished,
|
|
602
|
+
error,
|
|
603
|
+
release: release2
|
|
251
604
|
});
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
},
|
|
259
|
-
findPage(query) {
|
|
260
|
-
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
261
|
-
...query,
|
|
262
|
-
populate: {
|
|
263
|
-
actions: {
|
|
264
|
-
// @ts-expect-error Ignore missing properties
|
|
265
|
-
count: true
|
|
605
|
+
};
|
|
606
|
+
const getFormattedActions = async (releaseId) => {
|
|
607
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
608
|
+
where: {
|
|
609
|
+
release: {
|
|
610
|
+
id: releaseId
|
|
266
611
|
}
|
|
267
612
|
}
|
|
268
613
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
614
|
+
if (actions.length === 0) {
|
|
615
|
+
throw new errors.ValidationError("No entries to publish");
|
|
616
|
+
}
|
|
617
|
+
const formattedActions = {};
|
|
618
|
+
for (const action of actions) {
|
|
619
|
+
const contentTypeUid = action.contentType;
|
|
620
|
+
if (!formattedActions[contentTypeUid]) {
|
|
621
|
+
formattedActions[contentTypeUid] = {
|
|
622
|
+
publish: [],
|
|
623
|
+
unpublish: []
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
627
|
+
documentId: action.entryDocumentId,
|
|
628
|
+
locale: action.locale
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return formattedActions;
|
|
632
|
+
};
|
|
633
|
+
return {
|
|
634
|
+
async create(releaseData, { user }) {
|
|
635
|
+
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
636
|
+
const {
|
|
637
|
+
validatePendingReleasesLimit,
|
|
638
|
+
validateUniqueNameForPendingRelease,
|
|
639
|
+
validateScheduledAtIsLaterThanNow
|
|
640
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
641
|
+
await Promise.all([
|
|
642
|
+
validatePendingReleasesLimit(),
|
|
643
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
644
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
645
|
+
]);
|
|
646
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
647
|
+
data: {
|
|
648
|
+
...releaseWithCreatorFields,
|
|
649
|
+
status: "empty"
|
|
279
650
|
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
651
|
+
});
|
|
652
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
653
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
654
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
655
|
+
}
|
|
656
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
657
|
+
return release2;
|
|
658
|
+
},
|
|
659
|
+
async findOne(id, query = {}) {
|
|
660
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
|
|
661
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
662
|
+
...dbQuery,
|
|
663
|
+
where: { id }
|
|
664
|
+
});
|
|
665
|
+
return release2;
|
|
666
|
+
},
|
|
667
|
+
findPage(query) {
|
|
668
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
669
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
670
|
+
...dbQuery,
|
|
671
|
+
populate: {
|
|
672
|
+
actions: {
|
|
673
|
+
count: true
|
|
287
674
|
}
|
|
288
675
|
}
|
|
676
|
+
});
|
|
677
|
+
},
|
|
678
|
+
findMany(query) {
|
|
679
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
680
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
681
|
+
...dbQuery
|
|
682
|
+
});
|
|
683
|
+
},
|
|
684
|
+
async update(id, releaseData, { user }) {
|
|
685
|
+
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
|
|
686
|
+
releaseData
|
|
687
|
+
);
|
|
688
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
689
|
+
"release-validation",
|
|
690
|
+
{ strapi: strapi2 }
|
|
691
|
+
);
|
|
692
|
+
await Promise.all([
|
|
693
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
694
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
695
|
+
]);
|
|
696
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
697
|
+
if (!release2) {
|
|
698
|
+
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
289
699
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
if (release2.actions?.length) {
|
|
293
|
-
const [actionForEntry] = release2.actions;
|
|
294
|
-
delete release2.actions;
|
|
295
|
-
return {
|
|
296
|
-
...release2,
|
|
297
|
-
action: actionForEntry
|
|
298
|
-
};
|
|
700
|
+
if (release2.releasedAt) {
|
|
701
|
+
throw new errors.ValidationError("Release already published");
|
|
299
702
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
703
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
704
|
+
where: { id },
|
|
705
|
+
data: releaseWithCreatorFields
|
|
706
|
+
});
|
|
707
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
708
|
+
if (releaseData.scheduledAt) {
|
|
709
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
710
|
+
} else if (release2.scheduledAt) {
|
|
711
|
+
schedulingService.cancel(id);
|
|
712
|
+
}
|
|
713
|
+
this.updateReleaseStatus(id);
|
|
714
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
715
|
+
return updatedRelease;
|
|
716
|
+
},
|
|
717
|
+
async getAllComponents() {
|
|
718
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
719
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
720
|
+
const componentsMap = components.reduce(
|
|
721
|
+
(acc, component) => {
|
|
722
|
+
acc[component.uid] = component;
|
|
723
|
+
return acc;
|
|
308
724
|
},
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
725
|
+
{}
|
|
726
|
+
);
|
|
727
|
+
return componentsMap;
|
|
728
|
+
},
|
|
729
|
+
async delete(releaseId) {
|
|
730
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
731
|
+
where: { id: releaseId },
|
|
732
|
+
populate: {
|
|
733
|
+
actions: {
|
|
734
|
+
select: ["id"]
|
|
735
|
+
}
|
|
312
736
|
}
|
|
737
|
+
});
|
|
738
|
+
if (!release2) {
|
|
739
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
313
740
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
741
|
+
if (release2.releasedAt) {
|
|
742
|
+
throw new errors.ValidationError("Release already published");
|
|
743
|
+
}
|
|
744
|
+
await strapi2.db.transaction(async () => {
|
|
745
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
746
|
+
where: {
|
|
319
747
|
id: {
|
|
320
|
-
$
|
|
748
|
+
$in: release2.actions.map((action) => action.id)
|
|
321
749
|
}
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
actions: null
|
|
325
750
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (release2.
|
|
334
|
-
const
|
|
335
|
-
|
|
336
|
-
return {
|
|
337
|
-
...release2,
|
|
338
|
-
action: actionForEntry
|
|
339
|
-
};
|
|
751
|
+
});
|
|
752
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
753
|
+
where: {
|
|
754
|
+
id: releaseId
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
});
|
|
758
|
+
if (release2.scheduledAt) {
|
|
759
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
760
|
+
await schedulingService.cancel(release2.id);
|
|
340
761
|
}
|
|
762
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
341
763
|
return release2;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
throw new errors.ValidationError("Release already published");
|
|
352
|
-
}
|
|
353
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
354
|
-
/*
|
|
355
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
356
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
357
|
-
*/
|
|
358
|
-
// @ts-expect-error see above
|
|
359
|
-
data: releaseWithCreatorFields
|
|
360
|
-
});
|
|
361
|
-
return updatedRelease;
|
|
362
|
-
},
|
|
363
|
-
async createAction(releaseId, action) {
|
|
364
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
365
|
-
strapi: strapi2
|
|
366
|
-
});
|
|
367
|
-
await Promise.all([
|
|
368
|
-
validateEntryContentType(action.entry.contentType),
|
|
369
|
-
validateUniqueEntry(releaseId, action)
|
|
370
|
-
]);
|
|
371
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
372
|
-
if (!release2) {
|
|
373
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
374
|
-
}
|
|
375
|
-
if (release2.releasedAt) {
|
|
376
|
-
throw new errors.ValidationError("Release already published");
|
|
377
|
-
}
|
|
378
|
-
const { entry, type } = action;
|
|
379
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
380
|
-
data: {
|
|
381
|
-
type,
|
|
382
|
-
contentType: entry.contentType,
|
|
383
|
-
locale: entry.locale,
|
|
384
|
-
entry: {
|
|
385
|
-
id: entry.id,
|
|
386
|
-
__type: entry.contentType,
|
|
387
|
-
__pivot: { field: "entry" }
|
|
388
|
-
},
|
|
389
|
-
release: releaseId
|
|
390
|
-
},
|
|
391
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
392
|
-
});
|
|
393
|
-
},
|
|
394
|
-
async findActions(releaseId, query) {
|
|
395
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
396
|
-
fields: ["id"]
|
|
397
|
-
});
|
|
398
|
-
if (!release2) {
|
|
399
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
400
|
-
}
|
|
401
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
402
|
-
...query,
|
|
403
|
-
populate: {
|
|
404
|
-
entry: {
|
|
405
|
-
populate: "*"
|
|
764
|
+
},
|
|
765
|
+
async publish(releaseId) {
|
|
766
|
+
const {
|
|
767
|
+
release: release2,
|
|
768
|
+
error
|
|
769
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
770
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
771
|
+
if (!lockedRelease) {
|
|
772
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
406
773
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
774
|
+
if (lockedRelease.releasedAt) {
|
|
775
|
+
throw new errors.ValidationError("Release already published");
|
|
776
|
+
}
|
|
777
|
+
if (lockedRelease.status === "failed") {
|
|
778
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
779
|
+
}
|
|
780
|
+
try {
|
|
781
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
782
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
783
|
+
await strapi2.db.transaction(
|
|
784
|
+
async () => Promise.all(
|
|
785
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
786
|
+
const contentType = contentTypeUid;
|
|
787
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
788
|
+
return Promise.all([
|
|
789
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
790
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
791
|
+
]);
|
|
792
|
+
})
|
|
793
|
+
)
|
|
794
|
+
);
|
|
795
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
796
|
+
where: {
|
|
797
|
+
id: releaseId
|
|
798
|
+
},
|
|
799
|
+
data: {
|
|
800
|
+
status: "done",
|
|
801
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
802
|
+
}
|
|
803
|
+
});
|
|
804
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
805
|
+
isPublished: true,
|
|
806
|
+
release: release22
|
|
807
|
+
});
|
|
808
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
809
|
+
return { release: release22, error: null };
|
|
810
|
+
} catch (error2) {
|
|
811
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
812
|
+
isPublished: false,
|
|
813
|
+
error: error2
|
|
814
|
+
});
|
|
815
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
816
|
+
status: "failed"
|
|
817
|
+
}).transacting(trx).execute();
|
|
818
|
+
return {
|
|
819
|
+
release: null,
|
|
820
|
+
error: error2
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
});
|
|
824
|
+
if (error instanceof Error) {
|
|
825
|
+
throw error;
|
|
410
826
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
827
|
+
return release2;
|
|
828
|
+
},
|
|
829
|
+
async updateReleaseStatus(releaseId) {
|
|
830
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
831
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
832
|
+
releaseActionService.countActions({
|
|
833
|
+
filters: {
|
|
834
|
+
release: releaseId
|
|
835
|
+
}
|
|
836
|
+
}),
|
|
837
|
+
releaseActionService.countActions({
|
|
838
|
+
filters: {
|
|
839
|
+
release: releaseId,
|
|
840
|
+
isEntryValid: false
|
|
841
|
+
}
|
|
842
|
+
})
|
|
843
|
+
]);
|
|
844
|
+
if (totalActions > 0) {
|
|
845
|
+
if (invalidActions > 0) {
|
|
846
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
847
|
+
where: {
|
|
848
|
+
id: releaseId
|
|
849
|
+
},
|
|
850
|
+
data: {
|
|
851
|
+
status: "blocked"
|
|
852
|
+
}
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
856
|
+
where: {
|
|
857
|
+
id: releaseId
|
|
858
|
+
},
|
|
859
|
+
data: {
|
|
860
|
+
status: "ready"
|
|
861
|
+
}
|
|
862
|
+
});
|
|
420
863
|
}
|
|
421
|
-
return
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
const formattedData = actions.map((action) => {
|
|
428
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
429
|
-
return {
|
|
430
|
-
...action,
|
|
431
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
432
|
-
contentType: {
|
|
433
|
-
displayName,
|
|
434
|
-
mainFieldValue: action.entry[mainField],
|
|
435
|
-
uid: action.contentType
|
|
864
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
865
|
+
where: {
|
|
866
|
+
id: releaseId
|
|
867
|
+
},
|
|
868
|
+
data: {
|
|
869
|
+
status: "empty"
|
|
436
870
|
}
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
};
|
|
874
|
+
};
|
|
875
|
+
const getGroupName = (queryValue) => {
|
|
876
|
+
switch (queryValue) {
|
|
877
|
+
case "contentType":
|
|
878
|
+
return "contentType.displayName";
|
|
879
|
+
case "type":
|
|
880
|
+
return "type";
|
|
881
|
+
case "locale":
|
|
882
|
+
return _.getOr("No locale", "locale.name");
|
|
883
|
+
default:
|
|
884
|
+
return "contentType.displayName";
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
888
|
+
const getLocalesDataForActions = async () => {
|
|
443
889
|
if (!strapi2.plugin("i18n")) {
|
|
444
890
|
return {};
|
|
445
891
|
}
|
|
@@ -448,8 +894,8 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
448
894
|
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
449
895
|
return acc;
|
|
450
896
|
}, {});
|
|
451
|
-
}
|
|
452
|
-
async
|
|
897
|
+
};
|
|
898
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
453
899
|
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
454
900
|
const contentTypesData = {};
|
|
455
901
|
for (const contentTypeUid of contentTypesUids) {
|
|
@@ -462,221 +908,249 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
462
908
|
};
|
|
463
909
|
}
|
|
464
910
|
return contentTypesData;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
return contentTypeModelsMap;
|
|
481
|
-
},
|
|
482
|
-
async getAllComponents() {
|
|
483
|
-
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
484
|
-
const components = await contentManagerComponentsService.findAllComponents();
|
|
485
|
-
const componentsMap = components.reduce(
|
|
486
|
-
(acc, component) => {
|
|
487
|
-
acc[component.uid] = component;
|
|
488
|
-
return acc;
|
|
489
|
-
},
|
|
490
|
-
{}
|
|
491
|
-
);
|
|
492
|
-
return componentsMap;
|
|
493
|
-
},
|
|
494
|
-
async delete(releaseId) {
|
|
495
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
496
|
-
populate: {
|
|
497
|
-
actions: {
|
|
498
|
-
fields: ["id"]
|
|
911
|
+
};
|
|
912
|
+
return {
|
|
913
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
914
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
915
|
+
strapi: strapi2
|
|
916
|
+
});
|
|
917
|
+
await Promise.all([
|
|
918
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
919
|
+
validateUniqueEntry(releaseId, action)
|
|
920
|
+
]);
|
|
921
|
+
const model = strapi2.contentType(action.contentType);
|
|
922
|
+
if (model.kind === "singleType") {
|
|
923
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
924
|
+
if (!document) {
|
|
925
|
+
throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
499
926
|
}
|
|
927
|
+
action.entryDocumentId = document.documentId;
|
|
500
928
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
929
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
930
|
+
if (!release2) {
|
|
931
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
932
|
+
}
|
|
933
|
+
if (release2.releasedAt) {
|
|
934
|
+
throw new errors.ValidationError("Release already published");
|
|
935
|
+
}
|
|
936
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
937
|
+
{
|
|
938
|
+
contentType: action.contentType,
|
|
939
|
+
documentId: action.entryDocumentId,
|
|
940
|
+
locale: action.locale
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
strapi: strapi2
|
|
514
944
|
}
|
|
945
|
+
) : true;
|
|
946
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
947
|
+
data: {
|
|
948
|
+
...action,
|
|
949
|
+
release: release2.id,
|
|
950
|
+
isEntryValid: actionStatus
|
|
951
|
+
},
|
|
952
|
+
populate: { release: { select: ["id"] } }
|
|
515
953
|
});
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
return release2;
|
|
519
|
-
},
|
|
520
|
-
async publish(releaseId) {
|
|
521
|
-
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
522
|
-
RELEASE_MODEL_UID,
|
|
523
|
-
releaseId,
|
|
524
|
-
{
|
|
525
|
-
populate: {
|
|
526
|
-
actions: {
|
|
527
|
-
populate: {
|
|
528
|
-
entry: {
|
|
529
|
-
fields: ["id"]
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
}
|
|
954
|
+
if (!disableUpdateReleaseStatus) {
|
|
955
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
534
956
|
}
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}
|
|
545
|
-
const actions = {};
|
|
546
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
547
|
-
const contentTypeUid = action.contentType;
|
|
548
|
-
if (!actions[contentTypeUid]) {
|
|
549
|
-
actions[contentTypeUid] = {
|
|
550
|
-
entriestoPublishIds: [],
|
|
551
|
-
entriesToUnpublishIds: []
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
if (action.type === "publish") {
|
|
555
|
-
actions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
556
|
-
} else {
|
|
557
|
-
actions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
957
|
+
return releaseAction2;
|
|
958
|
+
},
|
|
959
|
+
async findPage(releaseId, query) {
|
|
960
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
961
|
+
where: { id: releaseId },
|
|
962
|
+
select: ["id"]
|
|
963
|
+
});
|
|
964
|
+
if (!release2) {
|
|
965
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
558
966
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
967
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
968
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
969
|
+
...dbQuery,
|
|
970
|
+
where: {
|
|
971
|
+
release: releaseId
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
975
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
976
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
977
|
+
const entry = await getEntry(
|
|
568
978
|
{
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
979
|
+
contentType: action.contentType,
|
|
980
|
+
documentId: action.entryDocumentId,
|
|
981
|
+
locale: action.locale,
|
|
982
|
+
populate,
|
|
983
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
984
|
+
},
|
|
985
|
+
{ strapi: strapi2 }
|
|
576
986
|
);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
987
|
+
return {
|
|
988
|
+
...action,
|
|
989
|
+
entry,
|
|
990
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
991
|
+
};
|
|
992
|
+
});
|
|
993
|
+
return {
|
|
994
|
+
results: actionsWithEntry,
|
|
995
|
+
pagination
|
|
996
|
+
};
|
|
997
|
+
},
|
|
998
|
+
async groupActions(actions, groupBy) {
|
|
999
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1000
|
+
if (!acc.includes(action.contentType)) {
|
|
1001
|
+
acc.push(action.contentType);
|
|
1002
|
+
}
|
|
1003
|
+
return acc;
|
|
1004
|
+
}, []);
|
|
1005
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1006
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1007
|
+
const formattedData = actions.map((action) => {
|
|
1008
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1009
|
+
return {
|
|
1010
|
+
...action,
|
|
1011
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1012
|
+
contentType: {
|
|
1013
|
+
displayName,
|
|
1014
|
+
mainFieldValue: action.entry[mainField],
|
|
1015
|
+
uid: action.contentType
|
|
1016
|
+
}
|
|
1017
|
+
};
|
|
1018
|
+
});
|
|
1019
|
+
const groupName = getGroupName(groupBy);
|
|
1020
|
+
return _.groupBy(groupName)(formattedData);
|
|
1021
|
+
},
|
|
1022
|
+
getContentTypeModelsFromActions(actions) {
|
|
1023
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1024
|
+
if (!acc.includes(action.contentType)) {
|
|
1025
|
+
acc.push(action.contentType);
|
|
1026
|
+
}
|
|
1027
|
+
return acc;
|
|
1028
|
+
}, []);
|
|
1029
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
1030
|
+
(acc, contentTypeUid) => {
|
|
1031
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
1032
|
+
return acc;
|
|
1033
|
+
},
|
|
1034
|
+
{}
|
|
1035
|
+
);
|
|
1036
|
+
return contentTypeModelsMap;
|
|
1037
|
+
},
|
|
1038
|
+
async countActions(query) {
|
|
1039
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1040
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1041
|
+
},
|
|
1042
|
+
async update(actionId, releaseId, update) {
|
|
1043
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1044
|
+
where: {
|
|
1045
|
+
id: actionId,
|
|
1046
|
+
release: {
|
|
1047
|
+
id: releaseId,
|
|
1048
|
+
releasedAt: {
|
|
1049
|
+
$null: true
|
|
1050
|
+
}
|
|
586
1051
|
}
|
|
587
|
-
);
|
|
588
|
-
if (entriesToPublish.length > 0) {
|
|
589
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
590
|
-
}
|
|
591
|
-
if (entriesToUnpublish.length > 0) {
|
|
592
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
593
1052
|
}
|
|
1053
|
+
});
|
|
1054
|
+
if (!action) {
|
|
1055
|
+
throw new errors.NotFoundError(
|
|
1056
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1057
|
+
);
|
|
594
1058
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
releasedAt: {
|
|
614
|
-
$null: true
|
|
1059
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1060
|
+
{
|
|
1061
|
+
contentType: action.contentType,
|
|
1062
|
+
documentId: action.entryDocumentId,
|
|
1063
|
+
locale: action.locale
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
strapi: strapi2
|
|
1067
|
+
}
|
|
1068
|
+
) : true;
|
|
1069
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1070
|
+
where: {
|
|
1071
|
+
id: actionId,
|
|
1072
|
+
release: {
|
|
1073
|
+
id: releaseId,
|
|
1074
|
+
releasedAt: {
|
|
1075
|
+
$null: true
|
|
1076
|
+
}
|
|
615
1077
|
}
|
|
1078
|
+
},
|
|
1079
|
+
data: {
|
|
1080
|
+
...update,
|
|
1081
|
+
isEntryValid: actionStatus
|
|
616
1082
|
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
id: actionId,
|
|
631
|
-
release: {
|
|
632
|
-
id: releaseId,
|
|
633
|
-
releasedAt: {
|
|
634
|
-
$null: true
|
|
1083
|
+
});
|
|
1084
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1085
|
+
return updatedAction;
|
|
1086
|
+
},
|
|
1087
|
+
async delete(actionId, releaseId) {
|
|
1088
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1089
|
+
where: {
|
|
1090
|
+
id: actionId,
|
|
1091
|
+
release: {
|
|
1092
|
+
id: releaseId,
|
|
1093
|
+
releasedAt: {
|
|
1094
|
+
$null: true
|
|
1095
|
+
}
|
|
635
1096
|
}
|
|
636
1097
|
}
|
|
1098
|
+
});
|
|
1099
|
+
if (!deletedAction) {
|
|
1100
|
+
throw new errors.NotFoundError(
|
|
1101
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1102
|
+
);
|
|
637
1103
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
throw new errors.NotFoundError(
|
|
641
|
-
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
642
|
-
);
|
|
1104
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1105
|
+
return deletedAction;
|
|
643
1106
|
}
|
|
644
|
-
|
|
1107
|
+
};
|
|
1108
|
+
};
|
|
1109
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1110
|
+
constructor(message) {
|
|
1111
|
+
super(message);
|
|
1112
|
+
this.name = "AlreadyOnReleaseError";
|
|
645
1113
|
}
|
|
646
|
-
}
|
|
1114
|
+
}
|
|
647
1115
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
648
1116
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
649
|
-
const release2 = await strapi2.
|
|
650
|
-
|
|
1117
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1118
|
+
where: {
|
|
1119
|
+
id: releaseId
|
|
1120
|
+
},
|
|
1121
|
+
populate: {
|
|
1122
|
+
actions: true
|
|
1123
|
+
}
|
|
651
1124
|
});
|
|
652
1125
|
if (!release2) {
|
|
653
1126
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
654
1127
|
}
|
|
655
1128
|
const isEntryInRelease = release2.actions.some(
|
|
656
|
-
(action) =>
|
|
1129
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
657
1130
|
);
|
|
658
1131
|
if (isEntryInRelease) {
|
|
659
|
-
throw new
|
|
660
|
-
`Entry with
|
|
1132
|
+
throw new AlreadyOnReleaseError(
|
|
1133
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
661
1134
|
);
|
|
662
1135
|
}
|
|
663
1136
|
},
|
|
664
|
-
|
|
1137
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
665
1138
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
666
1139
|
if (!contentType) {
|
|
667
1140
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
668
1141
|
}
|
|
669
|
-
if (!contentType
|
|
1142
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
670
1143
|
throw new errors.ValidationError(
|
|
671
1144
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
672
1145
|
);
|
|
673
1146
|
}
|
|
1147
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1148
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1149
|
+
}
|
|
674
1150
|
},
|
|
675
1151
|
async validatePendingReleasesLimit() {
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
679
|
-
);
|
|
1152
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1153
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
680
1154
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
681
1155
|
filters: {
|
|
682
1156
|
releasedAt: {
|
|
@@ -688,79 +1162,246 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
688
1162
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
689
1163
|
}
|
|
690
1164
|
},
|
|
691
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
692
|
-
const pendingReleases = await strapi2.
|
|
693
|
-
|
|
1165
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1166
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1167
|
+
where: {
|
|
694
1168
|
releasedAt: {
|
|
695
1169
|
$null: true
|
|
696
1170
|
},
|
|
697
|
-
name
|
|
1171
|
+
name,
|
|
1172
|
+
...id && { id: { $ne: id } }
|
|
698
1173
|
}
|
|
699
1174
|
});
|
|
700
1175
|
const isNameUnique = pendingReleases.length === 0;
|
|
701
1176
|
if (!isNameUnique) {
|
|
702
1177
|
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
703
1178
|
}
|
|
1179
|
+
},
|
|
1180
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1181
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1182
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
1183
|
+
}
|
|
704
1184
|
}
|
|
705
1185
|
});
|
|
1186
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1187
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1188
|
+
return {
|
|
1189
|
+
async set(releaseId, scheduleDate) {
|
|
1190
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1191
|
+
if (!release2) {
|
|
1192
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1193
|
+
}
|
|
1194
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
1195
|
+
try {
|
|
1196
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
}
|
|
1199
|
+
this.cancel(releaseId);
|
|
1200
|
+
});
|
|
1201
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1202
|
+
this.cancel(releaseId);
|
|
1203
|
+
}
|
|
1204
|
+
scheduledJobs.set(releaseId, job);
|
|
1205
|
+
return scheduledJobs;
|
|
1206
|
+
},
|
|
1207
|
+
cancel(releaseId) {
|
|
1208
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1209
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1210
|
+
scheduledJobs.delete(releaseId);
|
|
1211
|
+
}
|
|
1212
|
+
return scheduledJobs;
|
|
1213
|
+
},
|
|
1214
|
+
getAll() {
|
|
1215
|
+
return scheduledJobs;
|
|
1216
|
+
},
|
|
1217
|
+
/**
|
|
1218
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1219
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1220
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1221
|
+
*/
|
|
1222
|
+
async syncFromDatabase() {
|
|
1223
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1224
|
+
where: {
|
|
1225
|
+
scheduledAt: {
|
|
1226
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1227
|
+
},
|
|
1228
|
+
releasedAt: null
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
for (const release2 of releases) {
|
|
1232
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1233
|
+
}
|
|
1234
|
+
return scheduledJobs;
|
|
1235
|
+
}
|
|
1236
|
+
};
|
|
1237
|
+
};
|
|
1238
|
+
const DEFAULT_SETTINGS = {
|
|
1239
|
+
defaultTimezone: null
|
|
1240
|
+
};
|
|
1241
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1242
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1243
|
+
return {
|
|
1244
|
+
async update({ settings: settings2 }) {
|
|
1245
|
+
const store = await getStore();
|
|
1246
|
+
store.set({ key: "settings", value: settings2 });
|
|
1247
|
+
return settings2;
|
|
1248
|
+
},
|
|
1249
|
+
async find() {
|
|
1250
|
+
const store = await getStore();
|
|
1251
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1252
|
+
return {
|
|
1253
|
+
...DEFAULT_SETTINGS,
|
|
1254
|
+
...settings2 || {}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
1258
|
+
};
|
|
706
1259
|
const services = {
|
|
707
1260
|
release: createReleaseService,
|
|
708
|
-
"release-
|
|
1261
|
+
"release-action": createReleaseActionService,
|
|
1262
|
+
"release-validation": createReleaseValidationService,
|
|
1263
|
+
scheduling: createSchedulingService,
|
|
1264
|
+
settings: createSettingsService
|
|
709
1265
|
};
|
|
710
|
-
const RELEASE_SCHEMA = yup.object().shape({
|
|
711
|
-
name: yup.string().trim().required()
|
|
1266
|
+
const RELEASE_SCHEMA = yup$1.object().shape({
|
|
1267
|
+
name: yup$1.string().trim().required(),
|
|
1268
|
+
scheduledAt: yup$1.string().nullable(),
|
|
1269
|
+
timezone: yup$1.string().when("scheduledAt", {
|
|
1270
|
+
is: (value) => value !== null && value !== void 0,
|
|
1271
|
+
then: yup$1.string().required(),
|
|
1272
|
+
otherwise: yup$1.string().nullable()
|
|
1273
|
+
})
|
|
1274
|
+
}).required().noUnknown();
|
|
1275
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
|
|
1276
|
+
contentType: yup$1.string().required(),
|
|
1277
|
+
entryDocumentId: yup$1.string().nullable(),
|
|
1278
|
+
hasEntryAttached: yup$1.string().nullable(),
|
|
1279
|
+
locale: yup$1.string().nullable()
|
|
712
1280
|
}).required().noUnknown();
|
|
713
1281
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1282
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1283
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1284
|
+
);
|
|
714
1285
|
const releaseController = {
|
|
715
|
-
|
|
716
|
-
|
|
1286
|
+
/**
|
|
1287
|
+
* Find releases based on documents attached or not to the release.
|
|
1288
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1289
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1290
|
+
*/
|
|
1291
|
+
async findByDocumentAttached(ctx) {
|
|
1292
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
717
1293
|
ability: ctx.state.userAbility,
|
|
718
1294
|
model: RELEASE_MODEL_UID
|
|
719
1295
|
});
|
|
720
1296
|
await permissionsManager.validateQuery(ctx.query);
|
|
721
1297
|
const releaseService = getService("release", { strapi });
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1298
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1299
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1300
|
+
const model = strapi.getModel(query.contentType);
|
|
1301
|
+
if (model.kind && model.kind === "singleType") {
|
|
1302
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1303
|
+
if (!document) {
|
|
1304
|
+
throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1305
|
+
}
|
|
1306
|
+
query.entryDocumentId = document.documentId;
|
|
1307
|
+
}
|
|
1308
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1309
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1310
|
+
if (isEntryAttached) {
|
|
1311
|
+
const releases = await releaseService.findMany({
|
|
1312
|
+
where: {
|
|
1313
|
+
releasedAt: null,
|
|
1314
|
+
actions: {
|
|
1315
|
+
contentType,
|
|
1316
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1317
|
+
locale: locale ?? null
|
|
1318
|
+
}
|
|
1319
|
+
},
|
|
1320
|
+
populate: {
|
|
737
1321
|
actions: {
|
|
738
|
-
|
|
739
|
-
|
|
1322
|
+
fields: ["type"],
|
|
1323
|
+
filters: {
|
|
1324
|
+
contentType,
|
|
1325
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1326
|
+
locale: locale ?? null
|
|
740
1327
|
}
|
|
741
1328
|
}
|
|
742
|
-
}
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
ctx.body = { data: releases };
|
|
1332
|
+
} else {
|
|
1333
|
+
const relatedReleases = await releaseService.findMany({
|
|
1334
|
+
where: {
|
|
1335
|
+
releasedAt: null,
|
|
1336
|
+
actions: {
|
|
1337
|
+
contentType,
|
|
1338
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1339
|
+
locale: locale ?? null
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
const releases = await releaseService.findMany({
|
|
1344
|
+
where: {
|
|
1345
|
+
$or: [
|
|
1346
|
+
{
|
|
1347
|
+
id: {
|
|
1348
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1349
|
+
}
|
|
1350
|
+
},
|
|
1351
|
+
{
|
|
1352
|
+
actions: null
|
|
1353
|
+
}
|
|
1354
|
+
],
|
|
1355
|
+
releasedAt: null
|
|
1356
|
+
}
|
|
743
1357
|
});
|
|
744
|
-
ctx.body = { data
|
|
1358
|
+
ctx.body = { data: releases };
|
|
745
1359
|
}
|
|
746
1360
|
},
|
|
747
|
-
async
|
|
748
|
-
const
|
|
749
|
-
const releaseService = getService("release", { strapi });
|
|
750
|
-
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
751
|
-
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1361
|
+
async findPage(ctx) {
|
|
1362
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
752
1363
|
ability: ctx.state.userAbility,
|
|
753
1364
|
model: RELEASE_MODEL_UID
|
|
754
1365
|
});
|
|
755
|
-
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
1366
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1367
|
+
const releaseService = getService("release", { strapi });
|
|
1368
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1369
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1370
|
+
const data = results.map((release2) => {
|
|
1371
|
+
const { actions, ...releaseData } = release2;
|
|
1372
|
+
return {
|
|
1373
|
+
...releaseData,
|
|
1374
|
+
actions: {
|
|
1375
|
+
meta: {
|
|
1376
|
+
count: actions.count
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
};
|
|
1380
|
+
});
|
|
1381
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1382
|
+
where: {
|
|
1383
|
+
releasedAt: null
|
|
759
1384
|
}
|
|
760
1385
|
});
|
|
1386
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1387
|
+
},
|
|
1388
|
+
async findOne(ctx) {
|
|
1389
|
+
const id = ctx.params.id;
|
|
1390
|
+
const releaseService = getService("release", { strapi });
|
|
1391
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1392
|
+
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
761
1393
|
if (!release2) {
|
|
762
1394
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
763
1395
|
}
|
|
1396
|
+
const count = await releaseActionService.countActions({
|
|
1397
|
+
filters: {
|
|
1398
|
+
release: id
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
const sanitizedRelease = {
|
|
1402
|
+
...release2,
|
|
1403
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
1404
|
+
};
|
|
764
1405
|
const data = {
|
|
765
1406
|
...sanitizedRelease,
|
|
766
1407
|
actions: {
|
|
@@ -771,19 +1412,63 @@ const releaseController = {
|
|
|
771
1412
|
};
|
|
772
1413
|
ctx.body = { data };
|
|
773
1414
|
},
|
|
1415
|
+
async mapEntriesToReleases(ctx) {
|
|
1416
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1417
|
+
if (!contentTypeUid || !documentIds) {
|
|
1418
|
+
throw new errors.ValidationError("Missing required query parameters");
|
|
1419
|
+
}
|
|
1420
|
+
const releaseService = getService("release", { strapi });
|
|
1421
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1422
|
+
where: {
|
|
1423
|
+
releasedAt: null,
|
|
1424
|
+
actions: {
|
|
1425
|
+
contentType: contentTypeUid,
|
|
1426
|
+
entryDocumentId: {
|
|
1427
|
+
$in: documentIds
|
|
1428
|
+
},
|
|
1429
|
+
locale
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
1432
|
+
populate: {
|
|
1433
|
+
actions: true
|
|
1434
|
+
}
|
|
1435
|
+
});
|
|
1436
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1437
|
+
(acc, release2) => {
|
|
1438
|
+
release2.actions.forEach((action) => {
|
|
1439
|
+
if (action.contentType !== contentTypeUid) {
|
|
1440
|
+
return;
|
|
1441
|
+
}
|
|
1442
|
+
if (locale && action.locale !== locale) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1445
|
+
if (!acc[action.entryDocumentId]) {
|
|
1446
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1447
|
+
} else {
|
|
1448
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
return acc;
|
|
1452
|
+
},
|
|
1453
|
+
{}
|
|
1454
|
+
);
|
|
1455
|
+
ctx.body = {
|
|
1456
|
+
data: mappedEntriesInReleases
|
|
1457
|
+
};
|
|
1458
|
+
},
|
|
774
1459
|
async create(ctx) {
|
|
775
1460
|
const user = ctx.state.user;
|
|
776
1461
|
const releaseArgs = ctx.request.body;
|
|
777
1462
|
await validateRelease(releaseArgs);
|
|
778
1463
|
const releaseService = getService("release", { strapi });
|
|
779
1464
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
780
|
-
const permissionsManager = strapi.admin
|
|
1465
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
781
1466
|
ability: ctx.state.userAbility,
|
|
782
1467
|
model: RELEASE_MODEL_UID
|
|
783
1468
|
});
|
|
784
|
-
ctx.
|
|
1469
|
+
ctx.created({
|
|
785
1470
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
786
|
-
};
|
|
1471
|
+
});
|
|
787
1472
|
},
|
|
788
1473
|
async update(ctx) {
|
|
789
1474
|
const user = ctx.state.user;
|
|
@@ -792,7 +1477,7 @@ const releaseController = {
|
|
|
792
1477
|
await validateRelease(releaseArgs);
|
|
793
1478
|
const releaseService = getService("release", { strapi });
|
|
794
1479
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
795
|
-
const permissionsManager = strapi.admin
|
|
1480
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
796
1481
|
ability: ctx.state.userAbility,
|
|
797
1482
|
model: RELEASE_MODEL_UID
|
|
798
1483
|
});
|
|
@@ -809,67 +1494,135 @@ const releaseController = {
|
|
|
809
1494
|
};
|
|
810
1495
|
},
|
|
811
1496
|
async publish(ctx) {
|
|
812
|
-
const user = ctx.state.user;
|
|
813
1497
|
const id = ctx.params.id;
|
|
814
1498
|
const releaseService = getService("release", { strapi });
|
|
815
|
-
const
|
|
1499
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1500
|
+
const release2 = await releaseService.publish(id);
|
|
1501
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1502
|
+
releaseActionService.countActions({
|
|
1503
|
+
filters: {
|
|
1504
|
+
release: id,
|
|
1505
|
+
type: "publish"
|
|
1506
|
+
}
|
|
1507
|
+
}),
|
|
1508
|
+
releaseActionService.countActions({
|
|
1509
|
+
filters: {
|
|
1510
|
+
release: id,
|
|
1511
|
+
type: "unpublish"
|
|
1512
|
+
}
|
|
1513
|
+
})
|
|
1514
|
+
]);
|
|
816
1515
|
ctx.body = {
|
|
817
|
-
data: release2
|
|
1516
|
+
data: release2,
|
|
1517
|
+
meta: {
|
|
1518
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1519
|
+
totalPublishedEntries: countPublishActions,
|
|
1520
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1521
|
+
}
|
|
818
1522
|
};
|
|
819
1523
|
}
|
|
820
1524
|
};
|
|
821
1525
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}).required(),
|
|
1526
|
+
contentType: yup$1.string().required(),
|
|
1527
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1528
|
+
locale: yup$1.string(),
|
|
826
1529
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
827
1530
|
});
|
|
828
1531
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
829
1532
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
830
1533
|
});
|
|
1534
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1535
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1536
|
+
});
|
|
831
1537
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
832
1538
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1539
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
833
1540
|
const releaseActionController = {
|
|
834
1541
|
async create(ctx) {
|
|
835
1542
|
const releaseId = ctx.params.releaseId;
|
|
836
1543
|
const releaseActionArgs = ctx.request.body;
|
|
837
1544
|
await validateReleaseAction(releaseActionArgs);
|
|
838
|
-
const
|
|
839
|
-
const releaseAction2 = await
|
|
840
|
-
ctx.
|
|
1545
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1546
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1547
|
+
ctx.created({
|
|
841
1548
|
data: releaseAction2
|
|
842
|
-
};
|
|
1549
|
+
});
|
|
1550
|
+
},
|
|
1551
|
+
async createMany(ctx) {
|
|
1552
|
+
const releaseId = ctx.params.releaseId;
|
|
1553
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1554
|
+
await Promise.all(
|
|
1555
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1556
|
+
);
|
|
1557
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1558
|
+
const releaseService = getService("release", { strapi });
|
|
1559
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1560
|
+
const releaseActions2 = await Promise.all(
|
|
1561
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1562
|
+
try {
|
|
1563
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1564
|
+
disableUpdateReleaseStatus: true
|
|
1565
|
+
});
|
|
1566
|
+
return action;
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1569
|
+
return null;
|
|
1570
|
+
}
|
|
1571
|
+
throw error;
|
|
1572
|
+
}
|
|
1573
|
+
})
|
|
1574
|
+
);
|
|
1575
|
+
return releaseActions2;
|
|
1576
|
+
});
|
|
1577
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1578
|
+
if (newReleaseActions.length > 0) {
|
|
1579
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1580
|
+
}
|
|
1581
|
+
ctx.created({
|
|
1582
|
+
data: newReleaseActions,
|
|
1583
|
+
meta: {
|
|
1584
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1585
|
+
totalEntries: releaseActions.length
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
843
1588
|
},
|
|
844
1589
|
async findMany(ctx) {
|
|
845
1590
|
const releaseId = ctx.params.releaseId;
|
|
846
|
-
const permissionsManager = strapi.admin
|
|
1591
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
847
1592
|
ability: ctx.state.userAbility,
|
|
848
1593
|
model: RELEASE_ACTION_MODEL_UID
|
|
849
1594
|
});
|
|
1595
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1596
|
+
if (ctx.query.groupBy) {
|
|
1597
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1598
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1602
|
+
delete ctx.query.groupBy;
|
|
850
1603
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
851
|
-
const
|
|
852
|
-
const { results, pagination } = await
|
|
853
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1604
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1605
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
854
1606
|
...query
|
|
855
1607
|
});
|
|
856
1608
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
857
1609
|
if (acc[action.contentType]) {
|
|
858
1610
|
return acc;
|
|
859
1611
|
}
|
|
860
|
-
const contentTypePermissionsManager = strapi.admin
|
|
1612
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
861
1613
|
ability: ctx.state.userAbility,
|
|
862
1614
|
model: action.contentType
|
|
863
1615
|
});
|
|
864
1616
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
865
1617
|
return acc;
|
|
866
1618
|
}, {});
|
|
867
|
-
const sanitizedResults = await
|
|
1619
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
868
1620
|
...action,
|
|
869
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1621
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
870
1622
|
}));
|
|
871
|
-
const groupedData = await
|
|
872
|
-
const contentTypes2 =
|
|
1623
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1624
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1625
|
+
const releaseService = getService("release", { strapi });
|
|
873
1626
|
const components = await releaseService.getAllComponents();
|
|
874
1627
|
ctx.body = {
|
|
875
1628
|
data: groupedData,
|
|
@@ -885,8 +1638,8 @@ const releaseActionController = {
|
|
|
885
1638
|
const releaseId = ctx.params.releaseId;
|
|
886
1639
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
887
1640
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
888
|
-
const
|
|
889
|
-
const updatedAction = await
|
|
1641
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1642
|
+
const updatedAction = await releaseActionService.update(
|
|
890
1643
|
actionId,
|
|
891
1644
|
releaseId,
|
|
892
1645
|
releaseActionUpdateArgs
|
|
@@ -898,17 +1651,71 @@ const releaseActionController = {
|
|
|
898
1651
|
async delete(ctx) {
|
|
899
1652
|
const actionId = ctx.params.actionId;
|
|
900
1653
|
const releaseId = ctx.params.releaseId;
|
|
901
|
-
const
|
|
902
|
-
const deletedReleaseAction = await
|
|
1654
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1655
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
903
1656
|
ctx.body = {
|
|
904
1657
|
data: deletedReleaseAction
|
|
905
1658
|
};
|
|
906
1659
|
}
|
|
907
1660
|
};
|
|
908
|
-
const
|
|
1661
|
+
const SETTINGS_SCHEMA = yup.object().shape({
|
|
1662
|
+
defaultTimezone: yup.string().nullable().default(null)
|
|
1663
|
+
}).required().noUnknown();
|
|
1664
|
+
const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
|
|
1665
|
+
const settingsController = {
|
|
1666
|
+
async find(ctx) {
|
|
1667
|
+
const settingsService = getService("settings", { strapi });
|
|
1668
|
+
const settings2 = await settingsService.find();
|
|
1669
|
+
ctx.body = { data: settings2 };
|
|
1670
|
+
},
|
|
1671
|
+
async update(ctx) {
|
|
1672
|
+
const settingsBody = ctx.request.body;
|
|
1673
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1674
|
+
const settingsService = getService("settings", { strapi });
|
|
1675
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1676
|
+
ctx.body = { data: updatedSettings };
|
|
1677
|
+
}
|
|
1678
|
+
};
|
|
1679
|
+
const controllers = {
|
|
1680
|
+
release: releaseController,
|
|
1681
|
+
"release-action": releaseActionController,
|
|
1682
|
+
settings: settingsController
|
|
1683
|
+
};
|
|
909
1684
|
const release = {
|
|
910
1685
|
type: "admin",
|
|
911
1686
|
routes: [
|
|
1687
|
+
{
|
|
1688
|
+
method: "GET",
|
|
1689
|
+
path: "/mapEntriesToReleases",
|
|
1690
|
+
handler: "release.mapEntriesToReleases",
|
|
1691
|
+
config: {
|
|
1692
|
+
policies: [
|
|
1693
|
+
"admin::isAuthenticatedAdmin",
|
|
1694
|
+
{
|
|
1695
|
+
name: "admin::hasPermissions",
|
|
1696
|
+
config: {
|
|
1697
|
+
actions: ["plugin::content-releases.read"]
|
|
1698
|
+
}
|
|
1699
|
+
}
|
|
1700
|
+
]
|
|
1701
|
+
}
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
method: "GET",
|
|
1705
|
+
path: "/getByDocumentAttached",
|
|
1706
|
+
handler: "release.findByDocumentAttached",
|
|
1707
|
+
config: {
|
|
1708
|
+
policies: [
|
|
1709
|
+
"admin::isAuthenticatedAdmin",
|
|
1710
|
+
{
|
|
1711
|
+
name: "admin::hasPermissions",
|
|
1712
|
+
config: {
|
|
1713
|
+
actions: ["plugin::content-releases.read"]
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
]
|
|
1717
|
+
}
|
|
1718
|
+
},
|
|
912
1719
|
{
|
|
913
1720
|
method: "POST",
|
|
914
1721
|
path: "/",
|
|
@@ -928,7 +1735,7 @@ const release = {
|
|
|
928
1735
|
{
|
|
929
1736
|
method: "GET",
|
|
930
1737
|
path: "/",
|
|
931
|
-
handler: "release.
|
|
1738
|
+
handler: "release.findPage",
|
|
932
1739
|
config: {
|
|
933
1740
|
policies: [
|
|
934
1741
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1026,6 +1833,22 @@ const releaseAction = {
|
|
|
1026
1833
|
]
|
|
1027
1834
|
}
|
|
1028
1835
|
},
|
|
1836
|
+
{
|
|
1837
|
+
method: "POST",
|
|
1838
|
+
path: "/:releaseId/actions/bulk",
|
|
1839
|
+
handler: "release-action.createMany",
|
|
1840
|
+
config: {
|
|
1841
|
+
policies: [
|
|
1842
|
+
"admin::isAuthenticatedAdmin",
|
|
1843
|
+
{
|
|
1844
|
+
name: "admin::hasPermissions",
|
|
1845
|
+
config: {
|
|
1846
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
]
|
|
1850
|
+
}
|
|
1851
|
+
},
|
|
1029
1852
|
{
|
|
1030
1853
|
method: "GET",
|
|
1031
1854
|
path: "/:releaseId/actions",
|
|
@@ -1076,16 +1899,54 @@ const releaseAction = {
|
|
|
1076
1899
|
}
|
|
1077
1900
|
]
|
|
1078
1901
|
};
|
|
1902
|
+
const settings = {
|
|
1903
|
+
type: "admin",
|
|
1904
|
+
routes: [
|
|
1905
|
+
{
|
|
1906
|
+
method: "GET",
|
|
1907
|
+
path: "/settings",
|
|
1908
|
+
handler: "settings.find",
|
|
1909
|
+
config: {
|
|
1910
|
+
policies: [
|
|
1911
|
+
"admin::isAuthenticatedAdmin",
|
|
1912
|
+
{
|
|
1913
|
+
name: "admin::hasPermissions",
|
|
1914
|
+
config: {
|
|
1915
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1916
|
+
}
|
|
1917
|
+
}
|
|
1918
|
+
]
|
|
1919
|
+
}
|
|
1920
|
+
},
|
|
1921
|
+
{
|
|
1922
|
+
method: "PUT",
|
|
1923
|
+
path: "/settings",
|
|
1924
|
+
handler: "settings.update",
|
|
1925
|
+
config: {
|
|
1926
|
+
policies: [
|
|
1927
|
+
"admin::isAuthenticatedAdmin",
|
|
1928
|
+
{
|
|
1929
|
+
name: "admin::hasPermissions",
|
|
1930
|
+
config: {
|
|
1931
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
]
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
]
|
|
1938
|
+
};
|
|
1079
1939
|
const routes = {
|
|
1940
|
+
settings,
|
|
1080
1941
|
release,
|
|
1081
1942
|
"release-action": releaseAction
|
|
1082
1943
|
};
|
|
1083
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1084
1944
|
const getPlugin = () => {
|
|
1085
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
1945
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1086
1946
|
return {
|
|
1087
1947
|
register,
|
|
1088
1948
|
bootstrap,
|
|
1949
|
+
destroy,
|
|
1089
1950
|
contentTypes,
|
|
1090
1951
|
services,
|
|
1091
1952
|
controllers,
|
|
@@ -1093,6 +1954,9 @@ const getPlugin = () => {
|
|
|
1093
1954
|
};
|
|
1094
1955
|
}
|
|
1095
1956
|
return {
|
|
1957
|
+
// Always return register, it handles its own feature check
|
|
1958
|
+
register,
|
|
1959
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1096
1960
|
contentTypes
|
|
1097
1961
|
};
|
|
1098
1962
|
};
|