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