@strapi/content-releases 0.0.0-next.60335b11eca9587b558035fe9ecca90e05db036b → 0.0.0-next.64bd4f3d1efcc9420d27c4f4d2013677ded62360
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-r9YocBH0.js → en-CmYoEnA7.js} +31 -5
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/{en-m9eTk4UF.mjs → en-D0yVZFqf.mjs} +31 -5
- 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 +1292 -525
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1292 -525
- 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 +34 -37
- package/dist/_chunks/App-BFMg2T7b.mjs +0 -1080
- package/dist/_chunks/App-BFMg2T7b.mjs.map +0 -1
- package/dist/_chunks/App-HSsvlR7P.js +0 -1102
- package/dist/_chunks/App-HSsvlR7P.js.map +0 -1
- package/dist/_chunks/en-m9eTk4UF.mjs.map +0 -1
- package/dist/_chunks/en-r9YocBH0.js.map +0 -1
- package/dist/_chunks/index-GAnmm0Zl.js +0 -957
- package/dist/_chunks/index-GAnmm0Zl.js.map +0 -1
- package/dist/_chunks/index-y1lk-7GR.mjs +0 -936
- package/dist/_chunks/index-y1lk-7GR.mjs.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 EE from "@strapi/strapi/dist/utils/ee";
|
|
5
5
|
import { scheduleJob } from "node-schedule";
|
|
6
6
|
import * as yup from "yup";
|
|
7
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
@@ -48,8 +48,83 @@ const ACTIONS = [
|
|
|
48
48
|
displayName: "Add an entry to a release",
|
|
49
49
|
uid: "create-action",
|
|
50
50
|
pluginName: "content-releases"
|
|
51
|
+
},
|
|
52
|
+
// Settings
|
|
53
|
+
{
|
|
54
|
+
uid: "settings.read",
|
|
55
|
+
section: "settings",
|
|
56
|
+
displayName: "Read",
|
|
57
|
+
category: "content releases",
|
|
58
|
+
subCategory: "options",
|
|
59
|
+
pluginName: "content-releases"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
uid: "settings.update",
|
|
63
|
+
section: "settings",
|
|
64
|
+
displayName: "Edit",
|
|
65
|
+
category: "content releases",
|
|
66
|
+
subCategory: "options",
|
|
67
|
+
pluginName: "content-releases"
|
|
51
68
|
}
|
|
52
69
|
];
|
|
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
|
+
};
|
|
53
128
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
54
129
|
oldContentTypes,
|
|
55
130
|
contentTypes: contentTypes2
|
|
@@ -71,66 +146,349 @@ async function deleteActionsOnDisableDraftAndPublish({
|
|
|
71
146
|
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
72
147
|
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
|
|
73
148
|
if (deletedContentTypes.length) {
|
|
74
|
-
await
|
|
149
|
+
await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
75
150
|
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
76
151
|
});
|
|
77
152
|
}
|
|
78
153
|
}
|
|
79
|
-
|
|
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
|
+
};
|
|
80
334
|
const register = async ({ strapi: strapi2 }) => {
|
|
81
|
-
if (features
|
|
82
|
-
await strapi2.admin
|
|
83
|
-
strapi2.
|
|
84
|
-
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);
|
|
85
443
|
}
|
|
86
444
|
};
|
|
87
|
-
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
88
445
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
89
|
-
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
|
+
);
|
|
90
450
|
strapi2.db.lifecycles.subscribe({
|
|
91
|
-
|
|
92
|
-
const { model, result } = event;
|
|
93
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
94
|
-
const { id } = result;
|
|
95
|
-
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
96
|
-
where: {
|
|
97
|
-
target_type: model.uid,
|
|
98
|
-
target_id: id
|
|
99
|
-
}
|
|
100
|
-
});
|
|
101
|
-
}
|
|
102
|
-
},
|
|
451
|
+
models: contentTypesWithDraftAndPublish,
|
|
103
452
|
/**
|
|
104
|
-
* deleteMany
|
|
105
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
106
|
-
*/
|
|
107
|
-
async beforeDeleteMany(event) {
|
|
108
|
-
const { model, params } = event;
|
|
109
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
110
|
-
const { where } = params;
|
|
111
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
112
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
113
|
-
}
|
|
114
|
-
},
|
|
115
|
-
/**
|
|
116
|
-
* We delete the release actions related to deleted entries
|
|
117
|
-
* 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
|
|
118
454
|
*/
|
|
119
455
|
async afterDeleteMany(event) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
130
469
|
});
|
|
131
470
|
}
|
|
132
471
|
}
|
|
133
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();
|
|
134
492
|
}
|
|
135
493
|
};
|
|
136
494
|
const schema$1 = {
|
|
@@ -162,6 +520,14 @@ const schema$1 = {
|
|
|
162
520
|
scheduledAt: {
|
|
163
521
|
type: "datetime"
|
|
164
522
|
},
|
|
523
|
+
timezone: {
|
|
524
|
+
type: "string"
|
|
525
|
+
},
|
|
526
|
+
status: {
|
|
527
|
+
type: "enumeration",
|
|
528
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
529
|
+
required: true
|
|
530
|
+
},
|
|
165
531
|
actions: {
|
|
166
532
|
type: "relation",
|
|
167
533
|
relation: "oneToMany",
|
|
@@ -197,15 +563,13 @@ const schema = {
|
|
|
197
563
|
enum: ["publish", "unpublish"],
|
|
198
564
|
required: true
|
|
199
565
|
},
|
|
200
|
-
entry: {
|
|
201
|
-
type: "relation",
|
|
202
|
-
relation: "morphToOne",
|
|
203
|
-
configurable: false
|
|
204
|
-
},
|
|
205
566
|
contentType: {
|
|
206
567
|
type: "string",
|
|
207
568
|
required: true
|
|
208
569
|
},
|
|
570
|
+
entryDocumentId: {
|
|
571
|
+
type: "string"
|
|
572
|
+
},
|
|
209
573
|
locale: {
|
|
210
574
|
type: "string"
|
|
211
575
|
},
|
|
@@ -214,6 +578,9 @@ const schema = {
|
|
|
214
578
|
relation: "manyToOne",
|
|
215
579
|
target: RELEASE_MODEL_UID,
|
|
216
580
|
inversedBy: "actions"
|
|
581
|
+
},
|
|
582
|
+
isEntryValid: {
|
|
583
|
+
type: "boolean"
|
|
217
584
|
}
|
|
218
585
|
}
|
|
219
586
|
};
|
|
@@ -224,233 +591,297 @@ const contentTypes = {
|
|
|
224
591
|
release: release$1,
|
|
225
592
|
"release-action": releaseAction$1
|
|
226
593
|
};
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return "contentType.displayName";
|
|
234
|
-
case "action":
|
|
235
|
-
return "type";
|
|
236
|
-
case "locale":
|
|
237
|
-
return _.getOr("No locale", "locale.name");
|
|
238
|
-
default:
|
|
239
|
-
return "contentType.displayName";
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
243
|
-
async create(releaseData, { user }) {
|
|
244
|
-
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
245
|
-
const {
|
|
246
|
-
validatePendingReleasesLimit,
|
|
247
|
-
validateUniqueNameForPendingRelease,
|
|
248
|
-
validateScheduledAtIsLaterThanNow
|
|
249
|
-
} = getService("release-validation", { strapi: strapi2 });
|
|
250
|
-
await Promise.all([
|
|
251
|
-
validatePendingReleasesLimit(),
|
|
252
|
-
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
253
|
-
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
254
|
-
]);
|
|
255
|
-
const release2 = await strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
256
|
-
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
|
|
257
600
|
});
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
async findOne(id, query = {}) {
|
|
265
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
266
|
-
...query
|
|
267
|
-
});
|
|
268
|
-
return release2;
|
|
269
|
-
},
|
|
270
|
-
findPage(query) {
|
|
271
|
-
return strapi2.entityService.findPage(RELEASE_MODEL_UID, {
|
|
272
|
-
...query,
|
|
273
|
-
populate: {
|
|
274
|
-
actions: {
|
|
275
|
-
// @ts-expect-error Ignore missing properties
|
|
276
|
-
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
|
|
277
607
|
}
|
|
278
608
|
}
|
|
279
609
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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"
|
|
290
646
|
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
|
298
670
|
}
|
|
299
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}`);
|
|
300
695
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if (release2.actions?.length) {
|
|
304
|
-
const [actionForEntry] = release2.actions;
|
|
305
|
-
delete release2.actions;
|
|
306
|
-
return {
|
|
307
|
-
...release2,
|
|
308
|
-
action: actionForEntry
|
|
309
|
-
};
|
|
696
|
+
if (release2.releasedAt) {
|
|
697
|
+
throw new errors.ValidationError("Release already published");
|
|
310
698
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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;
|
|
319
720
|
},
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
+
}
|
|
323
732
|
}
|
|
733
|
+
});
|
|
734
|
+
if (!release2) {
|
|
735
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
324
736
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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: {
|
|
330
743
|
id: {
|
|
331
|
-
$
|
|
744
|
+
$in: release2.actions.map((action) => action.id)
|
|
332
745
|
}
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
actions: null
|
|
336
746
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if (release2.
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
return {
|
|
348
|
-
...release2,
|
|
349
|
-
action: actionForEntry
|
|
350
|
-
};
|
|
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);
|
|
351
757
|
}
|
|
758
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
352
759
|
return release2;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
throw new errors.ValidationError("Release already published");
|
|
363
|
-
}
|
|
364
|
-
const updatedRelease = await strapi2.entityService.update(RELEASE_MODEL_UID, id, {
|
|
365
|
-
/*
|
|
366
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
367
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
368
|
-
*/
|
|
369
|
-
// @ts-expect-error see above
|
|
370
|
-
data: releaseWithCreatorFields
|
|
371
|
-
});
|
|
372
|
-
return updatedRelease;
|
|
373
|
-
},
|
|
374
|
-
async createAction(releaseId, action) {
|
|
375
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
376
|
-
strapi: strapi2
|
|
377
|
-
});
|
|
378
|
-
await Promise.all([
|
|
379
|
-
validateEntryContentType(action.entry.contentType),
|
|
380
|
-
validateUniqueEntry(releaseId, action)
|
|
381
|
-
]);
|
|
382
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
383
|
-
if (!release2) {
|
|
384
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
385
|
-
}
|
|
386
|
-
if (release2.releasedAt) {
|
|
387
|
-
throw new errors.ValidationError("Release already published");
|
|
388
|
-
}
|
|
389
|
-
const { entry, type } = action;
|
|
390
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
391
|
-
data: {
|
|
392
|
-
type,
|
|
393
|
-
contentType: entry.contentType,
|
|
394
|
-
locale: entry.locale,
|
|
395
|
-
entry: {
|
|
396
|
-
id: entry.id,
|
|
397
|
-
__type: entry.contentType,
|
|
398
|
-
__pivot: { field: "entry" }
|
|
399
|
-
},
|
|
400
|
-
release: releaseId
|
|
401
|
-
},
|
|
402
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
403
|
-
});
|
|
404
|
-
},
|
|
405
|
-
async findActions(releaseId, query) {
|
|
406
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
407
|
-
fields: ["id"]
|
|
408
|
-
});
|
|
409
|
-
if (!release2) {
|
|
410
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
411
|
-
}
|
|
412
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
413
|
-
...query,
|
|
414
|
-
populate: {
|
|
415
|
-
entry: {
|
|
416
|
-
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}`);
|
|
417
769
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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;
|
|
421
822
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
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
|
+
});
|
|
431
859
|
}
|
|
432
|
-
return
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
const formattedData = actions.map((action) => {
|
|
439
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
440
|
-
return {
|
|
441
|
-
...action,
|
|
442
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
443
|
-
contentType: {
|
|
444
|
-
displayName,
|
|
445
|
-
mainFieldValue: action.entry[mainField],
|
|
446
|
-
uid: action.contentType
|
|
860
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
861
|
+
where: {
|
|
862
|
+
id: releaseId
|
|
863
|
+
},
|
|
864
|
+
data: {
|
|
865
|
+
status: "empty"
|
|
447
866
|
}
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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 () => {
|
|
454
885
|
if (!strapi2.plugin("i18n")) {
|
|
455
886
|
return {};
|
|
456
887
|
}
|
|
@@ -459,8 +890,8 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
459
890
|
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
460
891
|
return acc;
|
|
461
892
|
}, {});
|
|
462
|
-
}
|
|
463
|
-
async
|
|
893
|
+
};
|
|
894
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
464
895
|
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
465
896
|
const contentTypesData = {};
|
|
466
897
|
for (const contentTypeUid of contentTypesUids) {
|
|
@@ -473,247 +904,249 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
473
904
|
};
|
|
474
905
|
}
|
|
475
906
|
return contentTypesData;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
return contentTypeModelsMap;
|
|
492
|
-
},
|
|
493
|
-
async getAllComponents() {
|
|
494
|
-
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
495
|
-
const components = await contentManagerComponentsService.findAllComponents();
|
|
496
|
-
const componentsMap = components.reduce(
|
|
497
|
-
(acc, component) => {
|
|
498
|
-
acc[component.uid] = component;
|
|
499
|
-
return acc;
|
|
500
|
-
},
|
|
501
|
-
{}
|
|
502
|
-
);
|
|
503
|
-
return componentsMap;
|
|
504
|
-
},
|
|
505
|
-
async delete(releaseId) {
|
|
506
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
507
|
-
populate: {
|
|
508
|
-
actions: {
|
|
509
|
-
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}`);
|
|
510
922
|
}
|
|
923
|
+
action.entryDocumentId = document.documentId;
|
|
511
924
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
|
525
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"] } }
|
|
526
949
|
});
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return release2;
|
|
530
|
-
},
|
|
531
|
-
async publish(releaseId) {
|
|
532
|
-
const releaseWithPopulatedActionEntries = await strapi2.entityService.findOne(
|
|
533
|
-
RELEASE_MODEL_UID,
|
|
534
|
-
releaseId,
|
|
535
|
-
{
|
|
536
|
-
populate: {
|
|
537
|
-
actions: {
|
|
538
|
-
populate: {
|
|
539
|
-
entry: {
|
|
540
|
-
fields: ["id"]
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
}
|
|
950
|
+
if (!disableUpdateReleaseStatus) {
|
|
951
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
545
952
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
if (!collectionTypeActions[contentTypeUid]) {
|
|
562
|
-
collectionTypeActions[contentTypeUid] = {
|
|
563
|
-
entriestoPublishIds: [],
|
|
564
|
-
entriesToUnpublishIds: []
|
|
565
|
-
};
|
|
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}`);
|
|
962
|
+
}
|
|
963
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
964
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
965
|
+
...dbQuery,
|
|
966
|
+
where: {
|
|
967
|
+
release: releaseId
|
|
566
968
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
969
|
+
});
|
|
970
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
971
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
972
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
973
|
+
const entry = await getEntry(
|
|
974
|
+
{
|
|
975
|
+
contentType: action.contentType,
|
|
976
|
+
documentId: action.entryDocumentId,
|
|
977
|
+
locale: action.locale,
|
|
978
|
+
populate,
|
|
979
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
980
|
+
},
|
|
981
|
+
{ strapi: strapi2 }
|
|
982
|
+
);
|
|
983
|
+
return {
|
|
984
|
+
...action,
|
|
985
|
+
entry,
|
|
986
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
987
|
+
};
|
|
988
|
+
});
|
|
989
|
+
return {
|
|
990
|
+
results: actionsWithEntry,
|
|
991
|
+
pagination
|
|
992
|
+
};
|
|
993
|
+
},
|
|
994
|
+
async groupActions(actions, groupBy) {
|
|
995
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
996
|
+
if (!acc.includes(action.contentType)) {
|
|
997
|
+
acc.push(action.contentType);
|
|
571
998
|
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
const entry = await strapi2.entityService.findOne(uid, id, { populate });
|
|
586
|
-
try {
|
|
587
|
-
if (action === "publish") {
|
|
588
|
-
await entityManagerService.publish(entry, uid);
|
|
589
|
-
} else {
|
|
590
|
-
await entityManagerService.unpublish(entry, uid);
|
|
591
|
-
}
|
|
592
|
-
} catch (error) {
|
|
593
|
-
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft"))
|
|
594
|
-
;
|
|
595
|
-
else {
|
|
596
|
-
throw error;
|
|
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
|
|
597
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);
|
|
598
1022
|
}
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
+
}
|
|
623
1047
|
}
|
|
624
|
-
);
|
|
625
|
-
if (entriesToPublish.length > 0) {
|
|
626
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
627
1048
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
const release2 = await strapi2.entityService.update(RELEASE_MODEL_UID, releaseId, {
|
|
634
|
-
data: {
|
|
635
|
-
/*
|
|
636
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">> looks like it's wrong
|
|
637
|
-
*/
|
|
638
|
-
// @ts-expect-error see above
|
|
639
|
-
releasedAt: /* @__PURE__ */ new Date()
|
|
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
|
+
);
|
|
640
1054
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
+
}
|
|
652
1073
|
}
|
|
1074
|
+
},
|
|
1075
|
+
data: {
|
|
1076
|
+
...update,
|
|
1077
|
+
isEntryValid: actionStatus
|
|
653
1078
|
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
id: actionId,
|
|
668
|
-
release: {
|
|
669
|
-
id: releaseId,
|
|
670
|
-
releasedAt: {
|
|
671
|
-
$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
|
+
}
|
|
672
1092
|
}
|
|
673
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
|
+
);
|
|
674
1099
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
throw new errors.NotFoundError(
|
|
678
|
-
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
679
|
-
);
|
|
1100
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1101
|
+
return deletedAction;
|
|
680
1102
|
}
|
|
681
|
-
|
|
1103
|
+
};
|
|
1104
|
+
};
|
|
1105
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1106
|
+
constructor(message) {
|
|
1107
|
+
super(message);
|
|
1108
|
+
this.name = "AlreadyOnReleaseError";
|
|
682
1109
|
}
|
|
683
|
-
}
|
|
1110
|
+
}
|
|
684
1111
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
685
1112
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
686
|
-
const release2 = await strapi2.
|
|
687
|
-
|
|
1113
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1114
|
+
where: {
|
|
1115
|
+
id: releaseId
|
|
1116
|
+
},
|
|
1117
|
+
populate: {
|
|
1118
|
+
actions: true
|
|
1119
|
+
}
|
|
688
1120
|
});
|
|
689
1121
|
if (!release2) {
|
|
690
1122
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
691
1123
|
}
|
|
692
1124
|
const isEntryInRelease = release2.actions.some(
|
|
693
|
-
(action) =>
|
|
1125
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
694
1126
|
);
|
|
695
1127
|
if (isEntryInRelease) {
|
|
696
|
-
throw new
|
|
697
|
-
`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}`
|
|
698
1130
|
);
|
|
699
1131
|
}
|
|
700
1132
|
},
|
|
701
|
-
|
|
1133
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
702
1134
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
703
1135
|
if (!contentType) {
|
|
704
1136
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
705
1137
|
}
|
|
706
|
-
if (!contentType
|
|
1138
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
707
1139
|
throw new errors.ValidationError(
|
|
708
1140
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
709
1141
|
);
|
|
710
1142
|
}
|
|
1143
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1144
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1145
|
+
}
|
|
711
1146
|
},
|
|
712
1147
|
async validatePendingReleasesLimit() {
|
|
713
|
-
const
|
|
714
|
-
|
|
715
|
-
EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
716
|
-
);
|
|
1148
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1149
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
717
1150
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
718
1151
|
filters: {
|
|
719
1152
|
releasedAt: {
|
|
@@ -725,13 +1158,14 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
725
1158
|
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
726
1159
|
}
|
|
727
1160
|
},
|
|
728
|
-
async validateUniqueNameForPendingRelease(name) {
|
|
729
|
-
const pendingReleases = await strapi2.
|
|
730
|
-
|
|
1161
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1162
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1163
|
+
where: {
|
|
731
1164
|
releasedAt: {
|
|
732
1165
|
$null: true
|
|
733
1166
|
},
|
|
734
|
-
name
|
|
1167
|
+
name,
|
|
1168
|
+
...id && { id: { $ne: id } }
|
|
735
1169
|
}
|
|
736
1170
|
});
|
|
737
1171
|
const isNameUnique = pendingReleases.length === 0;
|
|
@@ -755,7 +1189,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
755
1189
|
}
|
|
756
1190
|
const job = scheduleJob(scheduleDate, async () => {
|
|
757
1191
|
try {
|
|
758
|
-
await getService("release").publish(releaseId);
|
|
1192
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
759
1193
|
} catch (error) {
|
|
760
1194
|
}
|
|
761
1195
|
this.cancel(releaseId);
|
|
@@ -772,68 +1206,197 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
772
1206
|
scheduledJobs.delete(releaseId);
|
|
773
1207
|
}
|
|
774
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
|
+
};
|
|
775
1252
|
}
|
|
776
1253
|
};
|
|
777
1254
|
};
|
|
778
1255
|
const services = {
|
|
779
1256
|
release: createReleaseService,
|
|
1257
|
+
"release-action": createReleaseActionService,
|
|
780
1258
|
"release-validation": createReleaseValidationService,
|
|
781
|
-
|
|
1259
|
+
scheduling: createSchedulingService,
|
|
1260
|
+
settings: createSettingsService
|
|
782
1261
|
};
|
|
783
|
-
const RELEASE_SCHEMA = yup.object().shape({
|
|
784
|
-
name: yup.string().trim().required(),
|
|
785
|
-
|
|
786
|
-
|
|
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()
|
|
787
1276
|
}).required().noUnknown();
|
|
788
1277
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1278
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1279
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1280
|
+
);
|
|
789
1281
|
const releaseController = {
|
|
790
|
-
|
|
791
|
-
|
|
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({
|
|
792
1289
|
ability: ctx.state.userAbility,
|
|
793
1290
|
model: RELEASE_MODEL_UID
|
|
794
1291
|
});
|
|
795
1292
|
await permissionsManager.validateQuery(ctx.query);
|
|
796
1293
|
const releaseService = getService("release", { strapi });
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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: {
|
|
812
1317
|
actions: {
|
|
813
|
-
|
|
814
|
-
|
|
1318
|
+
fields: ["type"],
|
|
1319
|
+
filters: {
|
|
1320
|
+
contentType,
|
|
1321
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1322
|
+
locale: locale ?? null
|
|
815
1323
|
}
|
|
816
1324
|
}
|
|
817
|
-
}
|
|
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
|
+
}
|
|
818
1353
|
});
|
|
819
|
-
ctx.body = { data
|
|
1354
|
+
ctx.body = { data: releases };
|
|
820
1355
|
}
|
|
821
1356
|
},
|
|
1357
|
+
async findPage(ctx) {
|
|
1358
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1359
|
+
ability: ctx.state.userAbility,
|
|
1360
|
+
model: RELEASE_MODEL_UID
|
|
1361
|
+
});
|
|
1362
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1363
|
+
const releaseService = getService("release", { strapi });
|
|
1364
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1365
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1366
|
+
const data = results.map((release2) => {
|
|
1367
|
+
const { actions, ...releaseData } = release2;
|
|
1368
|
+
return {
|
|
1369
|
+
...releaseData,
|
|
1370
|
+
actions: {
|
|
1371
|
+
meta: {
|
|
1372
|
+
count: actions.count
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
});
|
|
1377
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1378
|
+
where: {
|
|
1379
|
+
releasedAt: null
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1383
|
+
},
|
|
822
1384
|
async findOne(ctx) {
|
|
823
1385
|
const id = ctx.params.id;
|
|
824
1386
|
const releaseService = getService("release", { strapi });
|
|
1387
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
825
1388
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
826
1389
|
if (!release2) {
|
|
827
1390
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
828
1391
|
}
|
|
829
|
-
const count = await
|
|
1392
|
+
const count = await releaseActionService.countActions({
|
|
830
1393
|
filters: {
|
|
831
1394
|
release: id
|
|
832
1395
|
}
|
|
833
1396
|
});
|
|
834
1397
|
const sanitizedRelease = {
|
|
835
1398
|
...release2,
|
|
836
|
-
createdBy: release2.createdBy ? strapi.admin
|
|
1399
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
837
1400
|
};
|
|
838
1401
|
const data = {
|
|
839
1402
|
...sanitizedRelease,
|
|
@@ -845,19 +1408,63 @@ const releaseController = {
|
|
|
845
1408
|
};
|
|
846
1409
|
ctx.body = { data };
|
|
847
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
|
+
},
|
|
848
1455
|
async create(ctx) {
|
|
849
1456
|
const user = ctx.state.user;
|
|
850
1457
|
const releaseArgs = ctx.request.body;
|
|
851
1458
|
await validateRelease(releaseArgs);
|
|
852
1459
|
const releaseService = getService("release", { strapi });
|
|
853
1460
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
854
|
-
const permissionsManager = strapi.admin
|
|
1461
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
855
1462
|
ability: ctx.state.userAbility,
|
|
856
1463
|
model: RELEASE_MODEL_UID
|
|
857
1464
|
});
|
|
858
|
-
ctx.
|
|
1465
|
+
ctx.created({
|
|
859
1466
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
860
|
-
};
|
|
1467
|
+
});
|
|
861
1468
|
},
|
|
862
1469
|
async update(ctx) {
|
|
863
1470
|
const user = ctx.state.user;
|
|
@@ -866,7 +1473,7 @@ const releaseController = {
|
|
|
866
1473
|
await validateRelease(releaseArgs);
|
|
867
1474
|
const releaseService = getService("release", { strapi });
|
|
868
1475
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
869
|
-
const permissionsManager = strapi.admin
|
|
1476
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
870
1477
|
ability: ctx.state.userAbility,
|
|
871
1478
|
model: RELEASE_MODEL_UID
|
|
872
1479
|
});
|
|
@@ -883,18 +1490,18 @@ const releaseController = {
|
|
|
883
1490
|
};
|
|
884
1491
|
},
|
|
885
1492
|
async publish(ctx) {
|
|
886
|
-
const user = ctx.state.user;
|
|
887
1493
|
const id = ctx.params.id;
|
|
888
1494
|
const releaseService = getService("release", { strapi });
|
|
889
|
-
const
|
|
1495
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1496
|
+
const release2 = await releaseService.publish(id);
|
|
890
1497
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
891
|
-
|
|
1498
|
+
releaseActionService.countActions({
|
|
892
1499
|
filters: {
|
|
893
1500
|
release: id,
|
|
894
1501
|
type: "publish"
|
|
895
1502
|
}
|
|
896
1503
|
}),
|
|
897
|
-
|
|
1504
|
+
releaseActionService.countActions({
|
|
898
1505
|
filters: {
|
|
899
1506
|
release: id,
|
|
900
1507
|
type: "unpublish"
|
|
@@ -912,57 +1519,106 @@ const releaseController = {
|
|
|
912
1519
|
}
|
|
913
1520
|
};
|
|
914
1521
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
}).required(),
|
|
1522
|
+
contentType: yup$1.string().required(),
|
|
1523
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1524
|
+
locale: yup$1.string(),
|
|
919
1525
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
920
1526
|
});
|
|
921
1527
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
922
1528
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
923
1529
|
});
|
|
1530
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1531
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1532
|
+
});
|
|
924
1533
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
925
1534
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1535
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
926
1536
|
const releaseActionController = {
|
|
927
1537
|
async create(ctx) {
|
|
928
1538
|
const releaseId = ctx.params.releaseId;
|
|
929
1539
|
const releaseActionArgs = ctx.request.body;
|
|
930
1540
|
await validateReleaseAction(releaseActionArgs);
|
|
931
|
-
const
|
|
932
|
-
const releaseAction2 = await
|
|
933
|
-
ctx.
|
|
1541
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1542
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1543
|
+
ctx.created({
|
|
934
1544
|
data: releaseAction2
|
|
935
|
-
};
|
|
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
|
+
});
|
|
936
1584
|
},
|
|
937
1585
|
async findMany(ctx) {
|
|
938
1586
|
const releaseId = ctx.params.releaseId;
|
|
939
|
-
const permissionsManager = strapi.admin
|
|
1587
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
940
1588
|
ability: ctx.state.userAbility,
|
|
941
1589
|
model: RELEASE_ACTION_MODEL_UID
|
|
942
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;
|
|
943
1599
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
944
|
-
const
|
|
945
|
-
const { results, pagination } = await
|
|
946
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1600
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1601
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
947
1602
|
...query
|
|
948
1603
|
});
|
|
949
1604
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
950
1605
|
if (acc[action.contentType]) {
|
|
951
1606
|
return acc;
|
|
952
1607
|
}
|
|
953
|
-
const contentTypePermissionsManager = strapi.admin
|
|
1608
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
954
1609
|
ability: ctx.state.userAbility,
|
|
955
1610
|
model: action.contentType
|
|
956
1611
|
});
|
|
957
1612
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
958
1613
|
return acc;
|
|
959
1614
|
}, {});
|
|
960
|
-
const sanitizedResults = await
|
|
1615
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
961
1616
|
...action,
|
|
962
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1617
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
963
1618
|
}));
|
|
964
|
-
const groupedData = await
|
|
965
|
-
const contentTypes2 =
|
|
1619
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1620
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1621
|
+
const releaseService = getService("release", { strapi });
|
|
966
1622
|
const components = await releaseService.getAllComponents();
|
|
967
1623
|
ctx.body = {
|
|
968
1624
|
data: groupedData,
|
|
@@ -978,8 +1634,8 @@ const releaseActionController = {
|
|
|
978
1634
|
const releaseId = ctx.params.releaseId;
|
|
979
1635
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
980
1636
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
981
|
-
const
|
|
982
|
-
const updatedAction = await
|
|
1637
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1638
|
+
const updatedAction = await releaseActionService.update(
|
|
983
1639
|
actionId,
|
|
984
1640
|
releaseId,
|
|
985
1641
|
releaseActionUpdateArgs
|
|
@@ -991,17 +1647,71 @@ const releaseActionController = {
|
|
|
991
1647
|
async delete(ctx) {
|
|
992
1648
|
const actionId = ctx.params.actionId;
|
|
993
1649
|
const releaseId = ctx.params.releaseId;
|
|
994
|
-
const
|
|
995
|
-
const deletedReleaseAction = await
|
|
1650
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1651
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
996
1652
|
ctx.body = {
|
|
997
1653
|
data: deletedReleaseAction
|
|
998
1654
|
};
|
|
999
1655
|
}
|
|
1000
1656
|
};
|
|
1001
|
-
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
|
+
};
|
|
1002
1680
|
const release = {
|
|
1003
1681
|
type: "admin",
|
|
1004
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
|
+
},
|
|
1005
1715
|
{
|
|
1006
1716
|
method: "POST",
|
|
1007
1717
|
path: "/",
|
|
@@ -1021,7 +1731,7 @@ const release = {
|
|
|
1021
1731
|
{
|
|
1022
1732
|
method: "GET",
|
|
1023
1733
|
path: "/",
|
|
1024
|
-
handler: "release.
|
|
1734
|
+
handler: "release.findPage",
|
|
1025
1735
|
config: {
|
|
1026
1736
|
policies: [
|
|
1027
1737
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1119,6 +1829,22 @@ const releaseAction = {
|
|
|
1119
1829
|
]
|
|
1120
1830
|
}
|
|
1121
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
|
+
},
|
|
1122
1848
|
{
|
|
1123
1849
|
method: "GET",
|
|
1124
1850
|
path: "/:releaseId/actions",
|
|
@@ -1169,16 +1895,54 @@ const releaseAction = {
|
|
|
1169
1895
|
}
|
|
1170
1896
|
]
|
|
1171
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
|
+
};
|
|
1172
1935
|
const routes = {
|
|
1936
|
+
settings,
|
|
1173
1937
|
release,
|
|
1174
1938
|
"release-action": releaseAction
|
|
1175
1939
|
};
|
|
1176
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1177
1940
|
const getPlugin = () => {
|
|
1178
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
1941
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1179
1942
|
return {
|
|
1180
1943
|
register,
|
|
1181
1944
|
bootstrap,
|
|
1945
|
+
destroy,
|
|
1182
1946
|
contentTypes,
|
|
1183
1947
|
services,
|
|
1184
1948
|
controllers,
|
|
@@ -1186,6 +1950,9 @@ const getPlugin = () => {
|
|
|
1186
1950
|
};
|
|
1187
1951
|
}
|
|
1188
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
|
|
1189
1956
|
contentTypes
|
|
1190
1957
|
};
|
|
1191
1958
|
};
|