@strapi/content-releases 0.0.0-next.f8af92b375dc730ba47ed2117f25df893aae696c → 0.0.0-next.fc1775f7731f8999840e56e298a216b9a6c5c4ad
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-CiZCkScI.mjs +1558 -0
- package/dist/_chunks/App-CiZCkScI.mjs.map +1 -0
- package/dist/_chunks/App-SGjO5UPV.js +1578 -0
- package/dist/_chunks/App-SGjO5UPV.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-YhAPgpG9.js → PurchaseContentReleases--qQepXpP.js} +9 -8
- package/dist/_chunks/PurchaseContentReleases--qQepXpP.js.map +1 -0
- package/dist/_chunks/{PurchaseContentReleases-Clm0iACO.mjs → PurchaseContentReleases-D-n-w-st.mjs} +10 -9
- package/dist/_chunks/PurchaseContentReleases-D-n-w-st.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-Cto_NLUd.js.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-DQT8N3A-.mjs.map +1 -0
- package/dist/_chunks/{en-r0otWaln.js → en-BWPPsSH-.js} +31 -6
- package/dist/_chunks/en-BWPPsSH-.js.map +1 -0
- package/dist/_chunks/{en-veqvqeEr.mjs → en-D9Q4YW03.mjs} +31 -6
- package/dist/_chunks/en-D9Q4YW03.mjs.map +1 -0
- package/dist/_chunks/index-BjvFfTtA.mjs +1386 -0
- package/dist/_chunks/index-BjvFfTtA.mjs.map +1 -0
- package/dist/_chunks/index-CyU534vL.js +1404 -0
- package/dist/_chunks/index-CyU534vL.js.map +1 -0
- package/dist/_chunks/schemas-DBYv9gK8.js +61 -0
- package/dist/_chunks/schemas-DBYv9gK8.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/EntryValidationPopover.d.ts +13 -0
- 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 +1233 -515
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1232 -513
- 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 +2111 -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 +1824 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release-action.d.ts +34 -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 +137 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +184 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +35 -40
- package/dist/_chunks/App-OK4Xac-O.js +0 -1315
- package/dist/_chunks/App-OK4Xac-O.js.map +0 -1
- package/dist/_chunks/App-xAkiD42p.mjs +0 -1292
- package/dist/_chunks/App-xAkiD42p.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +0 -1
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +0 -1
- package/dist/_chunks/en-r0otWaln.js.map +0 -1
- package/dist/_chunks/en-veqvqeEr.mjs.map +0 -1
- package/dist/_chunks/index-JvA2_26n.js +0 -1015
- package/dist/_chunks/index-JvA2_26n.js.map +0 -1
- package/dist/_chunks/index-exoiSU3V.mjs +0 -994
- package/dist/_chunks/index-exoiSU3V.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,11 +48,94 @@ const ACTIONS = [
|
|
|
48
48
|
displayName: "Add an entry to a release",
|
|
49
49
|
uid: "create-action",
|
|
50
50
|
pluginName: "content-releases"
|
|
51
|
+
},
|
|
52
|
+
// Settings
|
|
53
|
+
{
|
|
54
|
+
uid: "settings.read",
|
|
55
|
+
section: "settings",
|
|
56
|
+
displayName: "Read",
|
|
57
|
+
category: "content releases",
|
|
58
|
+
subCategory: "options",
|
|
59
|
+
pluginName: "content-releases"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
uid: "settings.update",
|
|
63
|
+
section: "settings",
|
|
64
|
+
displayName: "Edit",
|
|
65
|
+
category: "content releases",
|
|
66
|
+
subCategory: "options",
|
|
67
|
+
pluginName: "content-releases"
|
|
51
68
|
}
|
|
52
69
|
];
|
|
53
70
|
const ALLOWED_WEBHOOK_EVENTS = {
|
|
54
71
|
RELEASES_PUBLISH: "releases.publish"
|
|
55
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
|
+
const workflowsService = strapi2.plugin("review-workflows").service("workflows");
|
|
92
|
+
const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
|
|
93
|
+
populate: "stageRequiredToPublish"
|
|
94
|
+
});
|
|
95
|
+
if (workflow?.stageRequiredToPublish) {
|
|
96
|
+
return entry.strapi_stage.id === workflow.stageRequiredToPublish.id;
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
} catch {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const getEntry = async ({
|
|
104
|
+
contentType,
|
|
105
|
+
documentId,
|
|
106
|
+
locale,
|
|
107
|
+
populate,
|
|
108
|
+
status = "draft"
|
|
109
|
+
}, { strapi: strapi2 }) => {
|
|
110
|
+
if (documentId) {
|
|
111
|
+
const entry = await strapi2.documents(contentType).findOne({ documentId, locale, populate, status });
|
|
112
|
+
if (status === "published" && !entry) {
|
|
113
|
+
return strapi2.documents(contentType).findOne({ documentId, locale, populate, status: "draft" });
|
|
114
|
+
}
|
|
115
|
+
return entry;
|
|
116
|
+
}
|
|
117
|
+
return strapi2.documents(contentType).findFirst({ locale, populate, status });
|
|
118
|
+
};
|
|
119
|
+
const getEntryStatus = async (contentType, entry) => {
|
|
120
|
+
if (entry.publishedAt) {
|
|
121
|
+
return "published";
|
|
122
|
+
}
|
|
123
|
+
const publishedEntry = await strapi.documents(contentType).findOne({
|
|
124
|
+
documentId: entry.documentId,
|
|
125
|
+
locale: entry.locale,
|
|
126
|
+
status: "published",
|
|
127
|
+
fields: ["updatedAt"]
|
|
128
|
+
});
|
|
129
|
+
if (!publishedEntry) {
|
|
130
|
+
return "draft";
|
|
131
|
+
}
|
|
132
|
+
const entryUpdatedAt = new Date(entry.updatedAt).getTime();
|
|
133
|
+
const publishedEntryUpdatedAt = new Date(publishedEntry.updatedAt).getTime();
|
|
134
|
+
if (entryUpdatedAt > publishedEntryUpdatedAt) {
|
|
135
|
+
return "modified";
|
|
136
|
+
}
|
|
137
|
+
return "published";
|
|
138
|
+
};
|
|
56
139
|
async function deleteActionsOnDisableDraftAndPublish({
|
|
57
140
|
oldContentTypes,
|
|
58
141
|
contentTypes: contentTypes2
|
|
@@ -74,90 +157,354 @@ async function deleteActionsOnDisableDraftAndPublish({
|
|
|
74
157
|
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
75
158
|
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
|
|
76
159
|
if (deletedContentTypes.length) {
|
|
77
|
-
await
|
|
160
|
+
await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
78
161
|
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
79
162
|
});
|
|
80
163
|
}
|
|
81
164
|
}
|
|
82
|
-
|
|
165
|
+
async function migrateIsValidAndStatusReleases() {
|
|
166
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
167
|
+
where: {
|
|
168
|
+
status: null,
|
|
169
|
+
releasedAt: null
|
|
170
|
+
},
|
|
171
|
+
populate: {
|
|
172
|
+
actions: {
|
|
173
|
+
populate: {
|
|
174
|
+
entry: true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
async.map(releasesWithoutStatus, async (release2) => {
|
|
180
|
+
const actions = release2.actions;
|
|
181
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
182
|
+
for (const action of notValidatedActions) {
|
|
183
|
+
if (action.entry) {
|
|
184
|
+
const isEntryValid2 = getDraftEntryValidStatus(
|
|
185
|
+
{
|
|
186
|
+
contentType: action.contentType,
|
|
187
|
+
documentId: action.entryDocumentId,
|
|
188
|
+
locale: action.locale
|
|
189
|
+
},
|
|
190
|
+
{ strapi }
|
|
191
|
+
);
|
|
192
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
193
|
+
where: {
|
|
194
|
+
id: action.id
|
|
195
|
+
},
|
|
196
|
+
data: {
|
|
197
|
+
isEntryValid: isEntryValid2
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
203
|
+
});
|
|
204
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
205
|
+
where: {
|
|
206
|
+
status: null,
|
|
207
|
+
releasedAt: {
|
|
208
|
+
$notNull: true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
async.map(publishedReleases, async (release2) => {
|
|
213
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
214
|
+
where: {
|
|
215
|
+
id: release2.id
|
|
216
|
+
},
|
|
217
|
+
data: {
|
|
218
|
+
status: "done"
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
224
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
225
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes).filter(
|
|
226
|
+
(uid) => oldContentTypes[uid]?.options?.draftAndPublish
|
|
227
|
+
);
|
|
228
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
229
|
+
async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
230
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
231
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
232
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
233
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
234
|
+
where: {
|
|
235
|
+
contentType: contentTypeUID
|
|
236
|
+
},
|
|
237
|
+
populate: {
|
|
238
|
+
entry: true,
|
|
239
|
+
release: true
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
await async.map(actions, async (action) => {
|
|
243
|
+
if (action.entry && action.release && action.type === "publish") {
|
|
244
|
+
const isEntryValid2 = await getDraftEntryValidStatus(
|
|
245
|
+
{
|
|
246
|
+
contentType: contentTypeUID,
|
|
247
|
+
documentId: action.entryDocumentId,
|
|
248
|
+
locale: action.locale
|
|
249
|
+
},
|
|
250
|
+
{ strapi }
|
|
251
|
+
);
|
|
252
|
+
releasesAffected.add(action.release.id);
|
|
253
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
254
|
+
where: {
|
|
255
|
+
id: action.id
|
|
256
|
+
},
|
|
257
|
+
data: {
|
|
258
|
+
isEntryValid: isEntryValid2
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
}).then(() => {
|
|
265
|
+
async.map(releasesAffected, async (releaseId) => {
|
|
266
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async function disableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
272
|
+
if (!oldContentTypes) {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
276
|
+
if (!i18nPlugin) {
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
for (const uid in contentTypes2) {
|
|
280
|
+
if (!oldContentTypes[uid]) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const oldContentType = oldContentTypes[uid];
|
|
284
|
+
const contentType = contentTypes2[uid];
|
|
285
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
286
|
+
if (isLocalizedContentType(oldContentType) && !isLocalizedContentType(contentType)) {
|
|
287
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
288
|
+
locale: null
|
|
289
|
+
}).where({ contentType: uid }).execute();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
async function enableContentTypeLocalized({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
294
|
+
if (!oldContentTypes) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const i18nPlugin = strapi.plugin("i18n");
|
|
298
|
+
if (!i18nPlugin) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
for (const uid in contentTypes2) {
|
|
302
|
+
if (!oldContentTypes[uid]) {
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
const oldContentType = oldContentTypes[uid];
|
|
306
|
+
const contentType = contentTypes2[uid];
|
|
307
|
+
const { isLocalizedContentType } = i18nPlugin.service("content-types");
|
|
308
|
+
const { getDefaultLocale } = i18nPlugin.service("locales");
|
|
309
|
+
if (!isLocalizedContentType(oldContentType) && isLocalizedContentType(contentType)) {
|
|
310
|
+
const defaultLocale = await getDefaultLocale();
|
|
311
|
+
await strapi.db.queryBuilder(RELEASE_ACTION_MODEL_UID).update({
|
|
312
|
+
locale: defaultLocale
|
|
313
|
+
}).where({ contentType: uid }).execute();
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
const addEntryDocumentToReleaseActions = {
|
|
318
|
+
name: "content-releases::5.0.0-add-entry-document-id-to-release-actions",
|
|
319
|
+
async up(trx, db) {
|
|
320
|
+
const hasTable = await trx.schema.hasTable("strapi_release_actions");
|
|
321
|
+
if (!hasTable) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const hasPolymorphicColumn = await trx.schema.hasColumn("strapi_release_actions", "target_id");
|
|
325
|
+
if (hasPolymorphicColumn) {
|
|
326
|
+
const hasEntryDocumentIdColumn = await trx.schema.hasColumn(
|
|
327
|
+
"strapi_release_actions",
|
|
328
|
+
"entry_document_id"
|
|
329
|
+
);
|
|
330
|
+
if (!hasEntryDocumentIdColumn) {
|
|
331
|
+
await trx.schema.alterTable("strapi_release_actions", (table) => {
|
|
332
|
+
table.string("entry_document_id");
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
const releaseActions = await trx.select("*").from("strapi_release_actions");
|
|
336
|
+
async.map(releaseActions, async (action) => {
|
|
337
|
+
const { target_type, target_id } = action;
|
|
338
|
+
const entry = await db.query(target_type).findOne({ where: { id: target_id } });
|
|
339
|
+
if (entry) {
|
|
340
|
+
await trx("strapi_release_actions").update({ entry_document_id: entry.documentId }).where("id", action.id);
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
async down() {
|
|
346
|
+
throw new Error("not implemented");
|
|
347
|
+
}
|
|
348
|
+
};
|
|
83
349
|
const register = async ({ strapi: strapi2 }) => {
|
|
84
|
-
if (features
|
|
85
|
-
await strapi2.admin
|
|
86
|
-
strapi2.
|
|
87
|
-
strapi2.hook("strapi::content-types.
|
|
350
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
351
|
+
await strapi2.service("admin::permission").actionProvider.registerMany(ACTIONS);
|
|
352
|
+
strapi2.db.migrations.providers.internal.register(addEntryDocumentToReleaseActions);
|
|
353
|
+
strapi2.hook("strapi::content-types.beforeSync").register(disableContentTypeLocalized).register(deleteActionsOnDisableDraftAndPublish);
|
|
354
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(enableContentTypeLocalized).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
355
|
+
}
|
|
356
|
+
if (strapi2.plugin("graphql")) {
|
|
357
|
+
const graphqlExtensionService = strapi2.plugin("graphql").service("extension");
|
|
358
|
+
graphqlExtensionService.shadowCRUD(RELEASE_MODEL_UID).disable();
|
|
359
|
+
graphqlExtensionService.shadowCRUD(RELEASE_ACTION_MODEL_UID).disable();
|
|
88
360
|
}
|
|
89
361
|
};
|
|
90
|
-
const
|
|
91
|
-
|
|
362
|
+
const updateActionsStatusAndUpdateReleaseStatus = async (contentType, entry) => {
|
|
363
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
364
|
+
where: {
|
|
365
|
+
releasedAt: null,
|
|
366
|
+
actions: {
|
|
367
|
+
contentType,
|
|
368
|
+
entryDocumentId: entry.documentId,
|
|
369
|
+
locale: entry.locale
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
const entryStatus = await isEntryValid(contentType, entry, { strapi });
|
|
374
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).updateMany({
|
|
375
|
+
where: {
|
|
376
|
+
contentType,
|
|
377
|
+
entryDocumentId: entry.documentId,
|
|
378
|
+
locale: entry.locale
|
|
379
|
+
},
|
|
380
|
+
data: {
|
|
381
|
+
isEntryValid: entryStatus
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
for (const release2 of releases) {
|
|
385
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
const deleteActionsAndUpdateReleaseStatus = async (params) => {
|
|
389
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
390
|
+
where: {
|
|
391
|
+
actions: params
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
395
|
+
where: params
|
|
396
|
+
});
|
|
397
|
+
for (const release2 of releases) {
|
|
398
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const deleteActionsOnDelete = async (ctx, next) => {
|
|
402
|
+
if (ctx.action !== "delete") {
|
|
403
|
+
return next();
|
|
404
|
+
}
|
|
405
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
406
|
+
return next();
|
|
407
|
+
}
|
|
408
|
+
const contentType = ctx.contentType.uid;
|
|
409
|
+
const { documentId, locale } = ctx.params;
|
|
410
|
+
const result = await next();
|
|
411
|
+
if (!result) {
|
|
412
|
+
return result;
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
deleteActionsAndUpdateReleaseStatus({
|
|
416
|
+
contentType,
|
|
417
|
+
entryDocumentId: documentId,
|
|
418
|
+
...locale !== "*" && { locale }
|
|
419
|
+
});
|
|
420
|
+
} catch (error) {
|
|
421
|
+
strapi.log.error("Error while deleting release actions after delete", {
|
|
422
|
+
error
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
return result;
|
|
426
|
+
};
|
|
427
|
+
const updateActionsOnUpdate = async (ctx, next) => {
|
|
428
|
+
if (ctx.action !== "update") {
|
|
429
|
+
return next();
|
|
430
|
+
}
|
|
431
|
+
if (!contentTypes$1.hasDraftAndPublish(ctx.contentType)) {
|
|
432
|
+
return next();
|
|
433
|
+
}
|
|
434
|
+
const contentType = ctx.contentType.uid;
|
|
435
|
+
const result = await next();
|
|
436
|
+
if (!result) {
|
|
437
|
+
return result;
|
|
438
|
+
}
|
|
439
|
+
try {
|
|
440
|
+
updateActionsStatusAndUpdateReleaseStatus(contentType, result);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
strapi.log.error("Error while updating release actions after update", {
|
|
443
|
+
error
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
return result;
|
|
447
|
+
};
|
|
448
|
+
const deleteReleasesActionsAndUpdateReleaseStatus = async (params) => {
|
|
449
|
+
const releases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
450
|
+
where: {
|
|
451
|
+
actions: params
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
455
|
+
where: params
|
|
456
|
+
});
|
|
457
|
+
for (const release2 of releases) {
|
|
458
|
+
getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
459
|
+
}
|
|
92
460
|
};
|
|
93
|
-
const { features: features$1 } = require("@strapi/strapi/dist/utils/ee");
|
|
94
461
|
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
95
|
-
if (features
|
|
462
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
463
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes).filter(
|
|
464
|
+
(uid) => strapi2.contentTypes[uid]?.options?.draftAndPublish
|
|
465
|
+
);
|
|
96
466
|
strapi2.db.lifecycles.subscribe({
|
|
97
|
-
|
|
98
|
-
const { model, result } = event;
|
|
99
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
100
|
-
const { id } = result;
|
|
101
|
-
strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
102
|
-
where: {
|
|
103
|
-
target_type: model.uid,
|
|
104
|
-
target_id: id
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
/**
|
|
110
|
-
* deleteMany hook doesn't return the deleted entries ids
|
|
111
|
-
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
112
|
-
*/
|
|
113
|
-
async beforeDeleteMany(event) {
|
|
114
|
-
const { model, params } = event;
|
|
115
|
-
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
116
|
-
const { where } = params;
|
|
117
|
-
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
118
|
-
event.state.entriesToDelete = entriesToDelete;
|
|
119
|
-
}
|
|
120
|
-
},
|
|
467
|
+
models: contentTypesWithDraftAndPublish,
|
|
121
468
|
/**
|
|
122
|
-
*
|
|
123
|
-
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
469
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
124
470
|
*/
|
|
125
471
|
async afterDeleteMany(event) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
472
|
+
try {
|
|
473
|
+
const model = strapi2.getModel(event.model.uid);
|
|
474
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
475
|
+
const { where } = event.params;
|
|
476
|
+
deleteReleasesActionsAndUpdateReleaseStatus({
|
|
477
|
+
contentType: model.uid,
|
|
478
|
+
locale: where?.locale ?? null,
|
|
479
|
+
...where?.documentId && { entryDocumentId: where.documentId }
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
} catch (error) {
|
|
483
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
484
|
+
error
|
|
136
485
|
});
|
|
137
486
|
}
|
|
138
487
|
}
|
|
139
488
|
});
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
}
|
|
489
|
+
strapi2.documents.use(deleteActionsOnDelete);
|
|
490
|
+
strapi2.documents.use(updateActionsOnUpdate);
|
|
491
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
492
|
+
strapi2.log.error(
|
|
493
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
494
|
+
);
|
|
495
|
+
throw err;
|
|
496
|
+
});
|
|
497
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
498
|
+
strapi2.get("webhookStore").addAllowedEvent(key, value);
|
|
499
|
+
});
|
|
151
500
|
}
|
|
152
501
|
};
|
|
153
502
|
const destroy = async ({ strapi: strapi2 }) => {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
job.cancel();
|
|
160
|
-
}
|
|
503
|
+
const scheduledJobs = getService("scheduling", {
|
|
504
|
+
strapi: strapi2
|
|
505
|
+
}).getAll();
|
|
506
|
+
for (const [, job] of scheduledJobs) {
|
|
507
|
+
job.cancel();
|
|
161
508
|
}
|
|
162
509
|
};
|
|
163
510
|
const schema$1 = {
|
|
@@ -192,6 +539,11 @@ const schema$1 = {
|
|
|
192
539
|
timezone: {
|
|
193
540
|
type: "string"
|
|
194
541
|
},
|
|
542
|
+
status: {
|
|
543
|
+
type: "enumeration",
|
|
544
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
545
|
+
required: true
|
|
546
|
+
},
|
|
195
547
|
actions: {
|
|
196
548
|
type: "relation",
|
|
197
549
|
relation: "oneToMany",
|
|
@@ -227,15 +579,13 @@ const schema = {
|
|
|
227
579
|
enum: ["publish", "unpublish"],
|
|
228
580
|
required: true
|
|
229
581
|
},
|
|
230
|
-
entry: {
|
|
231
|
-
type: "relation",
|
|
232
|
-
relation: "morphToOne",
|
|
233
|
-
configurable: false
|
|
234
|
-
},
|
|
235
582
|
contentType: {
|
|
236
583
|
type: "string",
|
|
237
584
|
required: true
|
|
238
585
|
},
|
|
586
|
+
entryDocumentId: {
|
|
587
|
+
type: "string"
|
|
588
|
+
},
|
|
239
589
|
locale: {
|
|
240
590
|
type: "string"
|
|
241
591
|
},
|
|
@@ -244,6 +594,9 @@ const schema = {
|
|
|
244
594
|
relation: "manyToOne",
|
|
245
595
|
target: RELEASE_MODEL_UID,
|
|
246
596
|
inversedBy: "actions"
|
|
597
|
+
},
|
|
598
|
+
isEntryValid: {
|
|
599
|
+
type: "boolean"
|
|
247
600
|
}
|
|
248
601
|
}
|
|
249
602
|
};
|
|
@@ -254,18 +607,6 @@ const contentTypes = {
|
|
|
254
607
|
release: release$1,
|
|
255
608
|
"release-action": releaseAction$1
|
|
256
609
|
};
|
|
257
|
-
const getGroupName = (queryValue) => {
|
|
258
|
-
switch (queryValue) {
|
|
259
|
-
case "contentType":
|
|
260
|
-
return "contentType.displayName";
|
|
261
|
-
case "action":
|
|
262
|
-
return "type";
|
|
263
|
-
case "locale":
|
|
264
|
-
return _.getOr("No locale", "locale.name");
|
|
265
|
-
default:
|
|
266
|
-
return "contentType.displayName";
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
610
|
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
270
611
|
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
271
612
|
strapi2.eventHub.emit(event, {
|
|
@@ -274,6 +615,33 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
274
615
|
release: release2
|
|
275
616
|
});
|
|
276
617
|
};
|
|
618
|
+
const getFormattedActions = async (releaseId) => {
|
|
619
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
620
|
+
where: {
|
|
621
|
+
release: {
|
|
622
|
+
id: releaseId
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
if (actions.length === 0) {
|
|
627
|
+
throw new errors.ValidationError("No entries to publish");
|
|
628
|
+
}
|
|
629
|
+
const formattedActions = {};
|
|
630
|
+
for (const action of actions) {
|
|
631
|
+
const contentTypeUid = action.contentType;
|
|
632
|
+
if (!formattedActions[contentTypeUid]) {
|
|
633
|
+
formattedActions[contentTypeUid] = {
|
|
634
|
+
publish: [],
|
|
635
|
+
unpublish: []
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
formattedActions[contentTypeUid][action.type].push({
|
|
639
|
+
documentId: action.entryDocumentId,
|
|
640
|
+
locale: action.locale
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
return formattedActions;
|
|
644
|
+
};
|
|
277
645
|
return {
|
|
278
646
|
async create(releaseData, { user }) {
|
|
279
647
|
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
@@ -287,10 +655,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
287
655
|
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
288
656
|
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
289
657
|
]);
|
|
290
|
-
const release2 = await strapi2.
|
|
291
|
-
data:
|
|
658
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
659
|
+
data: {
|
|
660
|
+
...releaseWithCreatorFields,
|
|
661
|
+
status: "empty"
|
|
662
|
+
}
|
|
292
663
|
});
|
|
293
|
-
if (
|
|
664
|
+
if (releaseWithCreatorFields.scheduledAt) {
|
|
294
665
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
295
666
|
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
296
667
|
}
|
|
@@ -298,94 +669,28 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
298
669
|
return release2;
|
|
299
670
|
},
|
|
300
671
|
async findOne(id, query = {}) {
|
|
301
|
-
const
|
|
302
|
-
|
|
672
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query);
|
|
673
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
674
|
+
...dbQuery,
|
|
675
|
+
where: { id }
|
|
303
676
|
});
|
|
304
677
|
return release2;
|
|
305
678
|
},
|
|
306
679
|
findPage(query) {
|
|
307
|
-
|
|
308
|
-
|
|
680
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
681
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
682
|
+
...dbQuery,
|
|
309
683
|
populate: {
|
|
310
684
|
actions: {
|
|
311
|
-
// @ts-expect-error Ignore missing properties
|
|
312
685
|
count: true
|
|
313
686
|
}
|
|
314
687
|
}
|
|
315
688
|
});
|
|
316
689
|
},
|
|
317
|
-
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
target_type: contentTypeUid,
|
|
322
|
-
target_id: entryId
|
|
323
|
-
},
|
|
324
|
-
releasedAt: {
|
|
325
|
-
$null: true
|
|
326
|
-
}
|
|
327
|
-
},
|
|
328
|
-
populate: {
|
|
329
|
-
// Filter the action to get only the content type entry
|
|
330
|
-
actions: {
|
|
331
|
-
where: {
|
|
332
|
-
target_type: contentTypeUid,
|
|
333
|
-
target_id: entryId
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
});
|
|
338
|
-
return releases.map((release2) => {
|
|
339
|
-
if (release2.actions?.length) {
|
|
340
|
-
const [actionForEntry] = release2.actions;
|
|
341
|
-
delete release2.actions;
|
|
342
|
-
return {
|
|
343
|
-
...release2,
|
|
344
|
-
action: actionForEntry
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
return release2;
|
|
348
|
-
});
|
|
349
|
-
},
|
|
350
|
-
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
351
|
-
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
352
|
-
where: {
|
|
353
|
-
releasedAt: {
|
|
354
|
-
$null: true
|
|
355
|
-
},
|
|
356
|
-
actions: {
|
|
357
|
-
target_type: contentTypeUid,
|
|
358
|
-
target_id: entryId
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
363
|
-
where: {
|
|
364
|
-
$or: [
|
|
365
|
-
{
|
|
366
|
-
id: {
|
|
367
|
-
$notIn: releasesRelated.map((release2) => release2.id)
|
|
368
|
-
}
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
actions: null
|
|
372
|
-
}
|
|
373
|
-
],
|
|
374
|
-
releasedAt: {
|
|
375
|
-
$null: true
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
});
|
|
379
|
-
return releases.map((release2) => {
|
|
380
|
-
if (release2.actions?.length) {
|
|
381
|
-
const [actionForEntry] = release2.actions;
|
|
382
|
-
delete release2.actions;
|
|
383
|
-
return {
|
|
384
|
-
...release2,
|
|
385
|
-
action: actionForEntry
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
return release2;
|
|
690
|
+
findMany(query) {
|
|
691
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_MODEL_UID, query ?? {});
|
|
692
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
693
|
+
...dbQuery
|
|
389
694
|
});
|
|
390
695
|
},
|
|
391
696
|
async update(id, releaseData, { user }) {
|
|
@@ -400,151 +705,27 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
400
705
|
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
401
706
|
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
402
707
|
]);
|
|
403
|
-
const release2 = await strapi2.
|
|
708
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
404
709
|
if (!release2) {
|
|
405
710
|
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
406
711
|
}
|
|
407
712
|
if (release2.releasedAt) {
|
|
408
713
|
throw new errors.ValidationError("Release already published");
|
|
409
714
|
}
|
|
410
|
-
const updatedRelease = await strapi2.
|
|
411
|
-
|
|
412
|
-
* The type returned from the entity service: Partial<Input<"plugin::content-releases.release">>
|
|
413
|
-
* is not compatible with the type we are passing here: UpdateRelease.Request['body']
|
|
414
|
-
*/
|
|
415
|
-
// @ts-expect-error see above
|
|
715
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
716
|
+
where: { id },
|
|
416
717
|
data: releaseWithCreatorFields
|
|
417
718
|
});
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
schedulingService.cancel(id);
|
|
424
|
-
}
|
|
719
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
720
|
+
if (releaseData.scheduledAt) {
|
|
721
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
722
|
+
} else if (release2.scheduledAt) {
|
|
723
|
+
schedulingService.cancel(id);
|
|
425
724
|
}
|
|
725
|
+
this.updateReleaseStatus(id);
|
|
426
726
|
strapi2.telemetry.send("didUpdateContentRelease");
|
|
427
727
|
return updatedRelease;
|
|
428
728
|
},
|
|
429
|
-
async createAction(releaseId, action) {
|
|
430
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
431
|
-
strapi: strapi2
|
|
432
|
-
});
|
|
433
|
-
await Promise.all([
|
|
434
|
-
validateEntryContentType(action.entry.contentType),
|
|
435
|
-
validateUniqueEntry(releaseId, action)
|
|
436
|
-
]);
|
|
437
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId);
|
|
438
|
-
if (!release2) {
|
|
439
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
440
|
-
}
|
|
441
|
-
if (release2.releasedAt) {
|
|
442
|
-
throw new errors.ValidationError("Release already published");
|
|
443
|
-
}
|
|
444
|
-
const { entry, type } = action;
|
|
445
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
446
|
-
data: {
|
|
447
|
-
type,
|
|
448
|
-
contentType: entry.contentType,
|
|
449
|
-
locale: entry.locale,
|
|
450
|
-
entry: {
|
|
451
|
-
id: entry.id,
|
|
452
|
-
__type: entry.contentType,
|
|
453
|
-
__pivot: { field: "entry" }
|
|
454
|
-
},
|
|
455
|
-
release: releaseId
|
|
456
|
-
},
|
|
457
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
458
|
-
});
|
|
459
|
-
},
|
|
460
|
-
async findActions(releaseId, query) {
|
|
461
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
462
|
-
fields: ["id"]
|
|
463
|
-
});
|
|
464
|
-
if (!release2) {
|
|
465
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
466
|
-
}
|
|
467
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
468
|
-
...query,
|
|
469
|
-
populate: {
|
|
470
|
-
entry: {
|
|
471
|
-
populate: "*"
|
|
472
|
-
}
|
|
473
|
-
},
|
|
474
|
-
filters: {
|
|
475
|
-
release: releaseId
|
|
476
|
-
}
|
|
477
|
-
});
|
|
478
|
-
},
|
|
479
|
-
async countActions(query) {
|
|
480
|
-
return strapi2.entityService.count(RELEASE_ACTION_MODEL_UID, query);
|
|
481
|
-
},
|
|
482
|
-
async groupActions(actions, groupBy) {
|
|
483
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
484
|
-
if (!acc.includes(action.contentType)) {
|
|
485
|
-
acc.push(action.contentType);
|
|
486
|
-
}
|
|
487
|
-
return acc;
|
|
488
|
-
}, []);
|
|
489
|
-
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
490
|
-
contentTypeUids
|
|
491
|
-
);
|
|
492
|
-
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
493
|
-
const formattedData = actions.map((action) => {
|
|
494
|
-
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
495
|
-
return {
|
|
496
|
-
...action,
|
|
497
|
-
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
498
|
-
contentType: {
|
|
499
|
-
displayName,
|
|
500
|
-
mainFieldValue: action.entry[mainField],
|
|
501
|
-
uid: action.contentType
|
|
502
|
-
}
|
|
503
|
-
};
|
|
504
|
-
});
|
|
505
|
-
const groupName = getGroupName(groupBy);
|
|
506
|
-
return _.groupBy(groupName)(formattedData);
|
|
507
|
-
},
|
|
508
|
-
async getLocalesDataForActions() {
|
|
509
|
-
if (!strapi2.plugin("i18n")) {
|
|
510
|
-
return {};
|
|
511
|
-
}
|
|
512
|
-
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
513
|
-
return allLocales.reduce((acc, locale) => {
|
|
514
|
-
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
515
|
-
return acc;
|
|
516
|
-
}, {});
|
|
517
|
-
},
|
|
518
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
519
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
520
|
-
const contentTypesData = {};
|
|
521
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
522
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
523
|
-
uid: contentTypeUid
|
|
524
|
-
});
|
|
525
|
-
contentTypesData[contentTypeUid] = {
|
|
526
|
-
mainField: contentTypeConfig.settings.mainField,
|
|
527
|
-
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
return contentTypesData;
|
|
531
|
-
},
|
|
532
|
-
getContentTypeModelsFromActions(actions) {
|
|
533
|
-
const contentTypeUids = actions.reduce((acc, action) => {
|
|
534
|
-
if (!acc.includes(action.contentType)) {
|
|
535
|
-
acc.push(action.contentType);
|
|
536
|
-
}
|
|
537
|
-
return acc;
|
|
538
|
-
}, []);
|
|
539
|
-
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
540
|
-
(acc, contentTypeUid) => {
|
|
541
|
-
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
542
|
-
return acc;
|
|
543
|
-
},
|
|
544
|
-
{}
|
|
545
|
-
);
|
|
546
|
-
return contentTypeModelsMap;
|
|
547
|
-
},
|
|
548
729
|
async getAllComponents() {
|
|
549
730
|
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
550
731
|
const components = await contentManagerComponentsService.findAllComponents();
|
|
@@ -558,10 +739,11 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
558
739
|
return componentsMap;
|
|
559
740
|
},
|
|
560
741
|
async delete(releaseId) {
|
|
561
|
-
const release2 = await strapi2.
|
|
742
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
743
|
+
where: { id: releaseId },
|
|
562
744
|
populate: {
|
|
563
745
|
actions: {
|
|
564
|
-
|
|
746
|
+
select: ["id"]
|
|
565
747
|
}
|
|
566
748
|
}
|
|
567
749
|
});
|
|
@@ -579,9 +761,13 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
579
761
|
}
|
|
580
762
|
}
|
|
581
763
|
});
|
|
582
|
-
await strapi2.
|
|
764
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
765
|
+
where: {
|
|
766
|
+
id: releaseId
|
|
767
|
+
}
|
|
768
|
+
});
|
|
583
769
|
});
|
|
584
|
-
if (
|
|
770
|
+
if (release2.scheduledAt) {
|
|
585
771
|
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
586
772
|
await schedulingService.cancel(release2.id);
|
|
587
773
|
}
|
|
@@ -589,142 +775,294 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
589
775
|
return release2;
|
|
590
776
|
},
|
|
591
777
|
async publish(releaseId) {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
actions: {
|
|
599
|
-
populate: {
|
|
600
|
-
entry: {
|
|
601
|
-
fields: ["id"]
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
);
|
|
608
|
-
if (!releaseWithPopulatedActionEntries) {
|
|
778
|
+
const {
|
|
779
|
+
release: release2,
|
|
780
|
+
error
|
|
781
|
+
} = await strapi2.db.transaction(async ({ trx }) => {
|
|
782
|
+
const lockedRelease = await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).select(["id", "name", "releasedAt", "status"]).first().transacting(trx).forUpdate().execute();
|
|
783
|
+
if (!lockedRelease) {
|
|
609
784
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
610
785
|
}
|
|
611
|
-
if (
|
|
786
|
+
if (lockedRelease.releasedAt) {
|
|
612
787
|
throw new errors.ValidationError("Release already published");
|
|
613
788
|
}
|
|
614
|
-
if (
|
|
615
|
-
throw new errors.ValidationError("
|
|
789
|
+
if (lockedRelease.status === "failed") {
|
|
790
|
+
throw new errors.ValidationError("Release failed to publish");
|
|
616
791
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
792
|
+
try {
|
|
793
|
+
strapi2.log.info(`[Content Releases] Starting to publish release ${lockedRelease.name}`);
|
|
794
|
+
const formattedActions = await getFormattedActions(releaseId);
|
|
795
|
+
await strapi2.db.transaction(
|
|
796
|
+
async () => Promise.all(
|
|
797
|
+
Object.keys(formattedActions).map(async (contentTypeUid) => {
|
|
798
|
+
const contentType = contentTypeUid;
|
|
799
|
+
const { publish, unpublish } = formattedActions[contentType];
|
|
800
|
+
return Promise.all([
|
|
801
|
+
...publish.map((params) => strapi2.documents(contentType).publish(params)),
|
|
802
|
+
...unpublish.map((params) => strapi2.documents(contentType).unpublish(params))
|
|
803
|
+
]);
|
|
804
|
+
})
|
|
805
|
+
)
|
|
806
|
+
);
|
|
807
|
+
const release22 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
808
|
+
where: {
|
|
809
|
+
id: releaseId
|
|
810
|
+
},
|
|
811
|
+
data: {
|
|
812
|
+
status: "done",
|
|
813
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
632
814
|
}
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
}
|
|
815
|
+
});
|
|
816
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
817
|
+
isPublished: true,
|
|
818
|
+
release: release22
|
|
819
|
+
});
|
|
820
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
821
|
+
return { release: release22, error: null };
|
|
822
|
+
} catch (error2) {
|
|
823
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
824
|
+
isPublished: false,
|
|
825
|
+
error: error2
|
|
826
|
+
});
|
|
827
|
+
await strapi2.db?.queryBuilder(RELEASE_MODEL_UID).where({ id: releaseId }).update({
|
|
828
|
+
status: "failed"
|
|
829
|
+
}).transacting(trx).execute();
|
|
830
|
+
return {
|
|
831
|
+
release: null,
|
|
832
|
+
error: error2
|
|
833
|
+
};
|
|
640
834
|
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
} catch (error) {
|
|
654
|
-
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
655
|
-
} else {
|
|
656
|
-
throw error;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
835
|
+
});
|
|
836
|
+
if (error instanceof Error) {
|
|
837
|
+
throw error;
|
|
838
|
+
}
|
|
839
|
+
return release2;
|
|
840
|
+
},
|
|
841
|
+
async updateReleaseStatus(releaseId) {
|
|
842
|
+
const releaseActionService = getService("release-action", { strapi: strapi2 });
|
|
843
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
844
|
+
releaseActionService.countActions({
|
|
845
|
+
filters: {
|
|
846
|
+
release: releaseId
|
|
659
847
|
}
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
{
|
|
677
|
-
filters: {
|
|
678
|
-
id: {
|
|
679
|
-
$in: entriesToUnpublishIds
|
|
680
|
-
}
|
|
681
|
-
},
|
|
682
|
-
populate
|
|
683
|
-
}
|
|
684
|
-
);
|
|
685
|
-
if (entriesToPublish.length > 0) {
|
|
686
|
-
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
687
|
-
}
|
|
688
|
-
if (entriesToUnpublish.length > 0) {
|
|
689
|
-
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
848
|
+
}),
|
|
849
|
+
releaseActionService.countActions({
|
|
850
|
+
filters: {
|
|
851
|
+
release: releaseId,
|
|
852
|
+
isEntryValid: false
|
|
853
|
+
}
|
|
854
|
+
})
|
|
855
|
+
]);
|
|
856
|
+
if (totalActions > 0) {
|
|
857
|
+
if (invalidActions > 0) {
|
|
858
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
859
|
+
where: {
|
|
860
|
+
id: releaseId
|
|
861
|
+
},
|
|
862
|
+
data: {
|
|
863
|
+
status: "blocked"
|
|
690
864
|
}
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
868
|
+
where: {
|
|
869
|
+
id: releaseId
|
|
870
|
+
},
|
|
871
|
+
data: {
|
|
872
|
+
status: "ready"
|
|
691
873
|
}
|
|
692
874
|
});
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
875
|
+
}
|
|
876
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
877
|
+
where: {
|
|
878
|
+
id: releaseId
|
|
879
|
+
},
|
|
880
|
+
data: {
|
|
881
|
+
status: "empty"
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
};
|
|
886
|
+
};
|
|
887
|
+
const getGroupName = (queryValue) => {
|
|
888
|
+
switch (queryValue) {
|
|
889
|
+
case "contentType":
|
|
890
|
+
return "contentType.displayName";
|
|
891
|
+
case "type":
|
|
892
|
+
return "type";
|
|
893
|
+
case "locale":
|
|
894
|
+
return _.getOr("No locale", "locale.name");
|
|
895
|
+
default:
|
|
896
|
+
return "contentType.displayName";
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
const createReleaseActionService = ({ strapi: strapi2 }) => {
|
|
900
|
+
const getLocalesDataForActions = async () => {
|
|
901
|
+
if (!strapi2.plugin("i18n")) {
|
|
902
|
+
return {};
|
|
903
|
+
}
|
|
904
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
905
|
+
return allLocales.reduce((acc, locale) => {
|
|
906
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
907
|
+
return acc;
|
|
908
|
+
}, {});
|
|
909
|
+
};
|
|
910
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
911
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
912
|
+
const contentTypesData = {};
|
|
913
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
914
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
915
|
+
uid: contentTypeUid
|
|
916
|
+
});
|
|
917
|
+
contentTypesData[contentTypeUid] = {
|
|
918
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
919
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
return contentTypesData;
|
|
923
|
+
};
|
|
924
|
+
return {
|
|
925
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
926
|
+
const { validateEntryData, validateUniqueEntry } = getService("release-validation", {
|
|
927
|
+
strapi: strapi2
|
|
928
|
+
});
|
|
929
|
+
await Promise.all([
|
|
930
|
+
validateEntryData(action.contentType, action.entryDocumentId),
|
|
931
|
+
validateUniqueEntry(releaseId, action)
|
|
932
|
+
]);
|
|
933
|
+
const model = strapi2.contentType(action.contentType);
|
|
934
|
+
if (model.kind === "singleType") {
|
|
935
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
936
|
+
if (!document) {
|
|
937
|
+
throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
938
|
+
}
|
|
939
|
+
action.entryDocumentId = document.documentId;
|
|
940
|
+
}
|
|
941
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
942
|
+
if (!release2) {
|
|
943
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
944
|
+
}
|
|
945
|
+
if (release2.releasedAt) {
|
|
946
|
+
throw new errors.ValidationError("Release already published");
|
|
947
|
+
}
|
|
948
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
949
|
+
{
|
|
950
|
+
contentType: action.contentType,
|
|
951
|
+
documentId: action.entryDocumentId,
|
|
952
|
+
locale: action.locale
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
strapi: strapi2
|
|
956
|
+
}
|
|
957
|
+
) : true;
|
|
958
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
959
|
+
data: {
|
|
960
|
+
...action,
|
|
961
|
+
release: release2.id,
|
|
962
|
+
isEntryValid: actionStatus
|
|
963
|
+
},
|
|
964
|
+
populate: { release: { select: ["id"] } }
|
|
965
|
+
});
|
|
966
|
+
if (!disableUpdateReleaseStatus) {
|
|
967
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
968
|
+
}
|
|
969
|
+
return releaseAction2;
|
|
970
|
+
},
|
|
971
|
+
async findPage(releaseId, query) {
|
|
972
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
973
|
+
where: { id: releaseId },
|
|
974
|
+
select: ["id"]
|
|
975
|
+
});
|
|
976
|
+
if (!release2) {
|
|
977
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
978
|
+
}
|
|
979
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
980
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
981
|
+
...dbQuery,
|
|
982
|
+
where: {
|
|
983
|
+
release: releaseId
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
987
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
988
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
989
|
+
const entry = await getEntry(
|
|
990
|
+
{
|
|
991
|
+
contentType: action.contentType,
|
|
992
|
+
documentId: action.entryDocumentId,
|
|
993
|
+
locale: action.locale,
|
|
994
|
+
populate,
|
|
995
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
700
996
|
},
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
997
|
+
{ strapi: strapi2 }
|
|
998
|
+
);
|
|
999
|
+
return {
|
|
1000
|
+
...action,
|
|
1001
|
+
entry,
|
|
1002
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
1003
|
+
};
|
|
1004
|
+
});
|
|
1005
|
+
return {
|
|
1006
|
+
results: actionsWithEntry,
|
|
1007
|
+
pagination
|
|
1008
|
+
};
|
|
1009
|
+
},
|
|
1010
|
+
async groupActions(actions, groupBy) {
|
|
1011
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1012
|
+
if (!acc.includes(action.contentType)) {
|
|
1013
|
+
acc.push(action.contentType);
|
|
1014
|
+
}
|
|
1015
|
+
return acc;
|
|
1016
|
+
}, []);
|
|
1017
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1018
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1019
|
+
const formattedData = actions.map((action) => {
|
|
1020
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1021
|
+
return {
|
|
1022
|
+
...action,
|
|
1023
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1024
|
+
contentType: {
|
|
1025
|
+
displayName,
|
|
1026
|
+
mainFieldValue: action.entry[mainField],
|
|
1027
|
+
uid: action.contentType
|
|
706
1028
|
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
1029
|
+
};
|
|
1030
|
+
});
|
|
1031
|
+
const groupName = getGroupName(groupBy);
|
|
1032
|
+
return _.groupBy(groupName)(formattedData);
|
|
1033
|
+
},
|
|
1034
|
+
async getContentTypeModelsFromActions(actions) {
|
|
1035
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1036
|
+
if (!acc.includes(action.contentType)) {
|
|
1037
|
+
acc.push(action.contentType);
|
|
713
1038
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1039
|
+
return acc;
|
|
1040
|
+
}, []);
|
|
1041
|
+
const workflowsService = strapi2.plugin("review-workflows").service("workflows");
|
|
1042
|
+
const contentTypeModelsMap = await async.reduce(contentTypeUids)(
|
|
1043
|
+
async (accPromise, contentTypeUid) => {
|
|
1044
|
+
const acc = await accPromise;
|
|
1045
|
+
const contentTypeModel = strapi2.getModel(contentTypeUid);
|
|
1046
|
+
const workflow = await workflowsService.getAssignedWorkflow(contentTypeUid, {
|
|
1047
|
+
populate: "stageRequiredToPublish"
|
|
721
1048
|
});
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1049
|
+
acc[contentTypeUid] = {
|
|
1050
|
+
...contentTypeModel,
|
|
1051
|
+
hasReviewWorkflow: !!workflow,
|
|
1052
|
+
stageRequiredToPublish: workflow?.stageRequiredToPublish
|
|
1053
|
+
};
|
|
1054
|
+
return acc;
|
|
1055
|
+
},
|
|
1056
|
+
{}
|
|
1057
|
+
);
|
|
1058
|
+
return contentTypeModelsMap;
|
|
725
1059
|
},
|
|
726
|
-
async
|
|
727
|
-
const
|
|
1060
|
+
async countActions(query) {
|
|
1061
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1062
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1063
|
+
},
|
|
1064
|
+
async update(actionId, releaseId, update) {
|
|
1065
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
728
1066
|
where: {
|
|
729
1067
|
id: actionId,
|
|
730
1068
|
release: {
|
|
@@ -733,17 +1071,42 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
733
1071
|
$null: true
|
|
734
1072
|
}
|
|
735
1073
|
}
|
|
736
|
-
}
|
|
737
|
-
data: update
|
|
1074
|
+
}
|
|
738
1075
|
});
|
|
739
|
-
if (!
|
|
1076
|
+
if (!action) {
|
|
740
1077
|
throw new errors.NotFoundError(
|
|
741
1078
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
742
1079
|
);
|
|
743
1080
|
}
|
|
1081
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1082
|
+
{
|
|
1083
|
+
contentType: action.contentType,
|
|
1084
|
+
documentId: action.entryDocumentId,
|
|
1085
|
+
locale: action.locale
|
|
1086
|
+
},
|
|
1087
|
+
{
|
|
1088
|
+
strapi: strapi2
|
|
1089
|
+
}
|
|
1090
|
+
) : true;
|
|
1091
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1092
|
+
where: {
|
|
1093
|
+
id: actionId,
|
|
1094
|
+
release: {
|
|
1095
|
+
id: releaseId,
|
|
1096
|
+
releasedAt: {
|
|
1097
|
+
$null: true
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
data: {
|
|
1102
|
+
...update,
|
|
1103
|
+
isEntryValid: actionStatus
|
|
1104
|
+
}
|
|
1105
|
+
});
|
|
1106
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
744
1107
|
return updatedAction;
|
|
745
1108
|
},
|
|
746
|
-
async
|
|
1109
|
+
async delete(actionId, releaseId) {
|
|
747
1110
|
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
748
1111
|
where: {
|
|
749
1112
|
id: actionId,
|
|
@@ -760,43 +1123,104 @@ const createReleaseService = ({ strapi: strapi2 }) => {
|
|
|
760
1123
|
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
761
1124
|
);
|
|
762
1125
|
}
|
|
1126
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
763
1127
|
return deletedAction;
|
|
1128
|
+
},
|
|
1129
|
+
async validateActionsByContentTypes(contentTypeUids) {
|
|
1130
|
+
const actions = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
1131
|
+
where: {
|
|
1132
|
+
contentType: {
|
|
1133
|
+
$in: contentTypeUids
|
|
1134
|
+
},
|
|
1135
|
+
// We only want to validate actions that are going to be published
|
|
1136
|
+
type: "publish",
|
|
1137
|
+
release: {
|
|
1138
|
+
releasedAt: {
|
|
1139
|
+
$null: true
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
populate: { release: true }
|
|
1144
|
+
});
|
|
1145
|
+
const releasesUpdated = [];
|
|
1146
|
+
await async.map(actions, async (action) => {
|
|
1147
|
+
const isValid = await getDraftEntryValidStatus(
|
|
1148
|
+
{
|
|
1149
|
+
contentType: action.contentType,
|
|
1150
|
+
documentId: action.entryDocumentId,
|
|
1151
|
+
locale: action.locale
|
|
1152
|
+
},
|
|
1153
|
+
{ strapi: strapi2 }
|
|
1154
|
+
);
|
|
1155
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1156
|
+
where: {
|
|
1157
|
+
id: action.id
|
|
1158
|
+
},
|
|
1159
|
+
data: {
|
|
1160
|
+
isEntryValid: isValid
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
if (!releasesUpdated.includes(action.release.id)) {
|
|
1164
|
+
releasesUpdated.push(action.release.id);
|
|
1165
|
+
}
|
|
1166
|
+
return {
|
|
1167
|
+
id: action.id,
|
|
1168
|
+
isEntryValid: isValid
|
|
1169
|
+
};
|
|
1170
|
+
});
|
|
1171
|
+
if (releasesUpdated.length > 0) {
|
|
1172
|
+
await async.map(releasesUpdated, async (releaseId) => {
|
|
1173
|
+
await getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
764
1176
|
}
|
|
765
1177
|
};
|
|
766
1178
|
};
|
|
1179
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1180
|
+
constructor(message) {
|
|
1181
|
+
super(message);
|
|
1182
|
+
this.name = "AlreadyOnReleaseError";
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
767
1185
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
768
1186
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
769
|
-
const release2 = await strapi2.
|
|
770
|
-
|
|
1187
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1188
|
+
where: {
|
|
1189
|
+
id: releaseId
|
|
1190
|
+
},
|
|
1191
|
+
populate: {
|
|
1192
|
+
actions: true
|
|
1193
|
+
}
|
|
771
1194
|
});
|
|
772
1195
|
if (!release2) {
|
|
773
1196
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
774
1197
|
}
|
|
775
1198
|
const isEntryInRelease = release2.actions.some(
|
|
776
|
-
(action) =>
|
|
1199
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
777
1200
|
);
|
|
778
1201
|
if (isEntryInRelease) {
|
|
779
|
-
throw new
|
|
780
|
-
`Entry with
|
|
1202
|
+
throw new AlreadyOnReleaseError(
|
|
1203
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
781
1204
|
);
|
|
782
1205
|
}
|
|
783
1206
|
},
|
|
784
|
-
|
|
1207
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
785
1208
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
786
1209
|
if (!contentType) {
|
|
787
1210
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
788
1211
|
}
|
|
789
|
-
if (!contentType
|
|
1212
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
790
1213
|
throw new errors.ValidationError(
|
|
791
1214
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
792
1215
|
);
|
|
793
1216
|
}
|
|
1217
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1218
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1219
|
+
}
|
|
794
1220
|
},
|
|
795
1221
|
async validatePendingReleasesLimit() {
|
|
796
|
-
const
|
|
797
|
-
|
|
798
|
-
EE.features.get("cms-content-releases")?.options?.maximumReleases || 3
|
|
799
|
-
);
|
|
1222
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1223
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
800
1224
|
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
801
1225
|
filters: {
|
|
802
1226
|
releasedAt: {
|
|
@@ -809,8 +1233,8 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
809
1233
|
}
|
|
810
1234
|
},
|
|
811
1235
|
async validateUniqueNameForPendingRelease(name, id) {
|
|
812
|
-
const pendingReleases = await strapi2.
|
|
813
|
-
|
|
1236
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1237
|
+
where: {
|
|
814
1238
|
releasedAt: {
|
|
815
1239
|
$null: true
|
|
816
1240
|
},
|
|
@@ -839,7 +1263,7 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
839
1263
|
}
|
|
840
1264
|
const job = scheduleJob(scheduleDate, async () => {
|
|
841
1265
|
try {
|
|
842
|
-
await getService("release").publish(releaseId);
|
|
1266
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
843
1267
|
} catch (error) {
|
|
844
1268
|
}
|
|
845
1269
|
this.cancel(releaseId);
|
|
@@ -881,80 +1305,172 @@ const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
|
881
1305
|
}
|
|
882
1306
|
};
|
|
883
1307
|
};
|
|
1308
|
+
const DEFAULT_SETTINGS = {
|
|
1309
|
+
defaultTimezone: null
|
|
1310
|
+
};
|
|
1311
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1312
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1313
|
+
return {
|
|
1314
|
+
async update({ settings: settings2 }) {
|
|
1315
|
+
const store = await getStore();
|
|
1316
|
+
store.set({ key: "settings", value: settings2 });
|
|
1317
|
+
return settings2;
|
|
1318
|
+
},
|
|
1319
|
+
async find() {
|
|
1320
|
+
const store = await getStore();
|
|
1321
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1322
|
+
return {
|
|
1323
|
+
...DEFAULT_SETTINGS,
|
|
1324
|
+
...settings2 || {}
|
|
1325
|
+
};
|
|
1326
|
+
}
|
|
1327
|
+
};
|
|
1328
|
+
};
|
|
884
1329
|
const services = {
|
|
885
1330
|
release: createReleaseService,
|
|
1331
|
+
"release-action": createReleaseActionService,
|
|
886
1332
|
"release-validation": createReleaseValidationService,
|
|
887
|
-
|
|
1333
|
+
scheduling: createSchedulingService,
|
|
1334
|
+
settings: createSettingsService
|
|
888
1335
|
};
|
|
889
|
-
const RELEASE_SCHEMA = yup.object().shape({
|
|
890
|
-
name: yup.string().trim().required(),
|
|
891
|
-
scheduledAt: yup.string().nullable(),
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
otherwise: yup.string().nullable()
|
|
897
|
-
}),
|
|
898
|
-
timezone: yup.string().when("isScheduled", {
|
|
899
|
-
is: true,
|
|
900
|
-
then: yup.string().required().nullable(),
|
|
901
|
-
otherwise: yup.string().nullable()
|
|
902
|
-
}),
|
|
903
|
-
date: yup.string().when("isScheduled", {
|
|
904
|
-
is: true,
|
|
905
|
-
then: yup.string().required().nullable(),
|
|
906
|
-
otherwise: yup.string().nullable()
|
|
1336
|
+
const RELEASE_SCHEMA = yup$1.object().shape({
|
|
1337
|
+
name: yup$1.string().trim().required(),
|
|
1338
|
+
scheduledAt: yup$1.string().nullable(),
|
|
1339
|
+
timezone: yup$1.string().when("scheduledAt", {
|
|
1340
|
+
is: (value) => value !== null && value !== void 0,
|
|
1341
|
+
then: yup$1.string().required(),
|
|
1342
|
+
otherwise: yup$1.string().nullable()
|
|
907
1343
|
})
|
|
908
1344
|
}).required().noUnknown();
|
|
1345
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
|
|
1346
|
+
contentType: yup$1.string().required(),
|
|
1347
|
+
entryDocumentId: yup$1.string().nullable(),
|
|
1348
|
+
hasEntryAttached: yup$1.string().nullable(),
|
|
1349
|
+
locale: yup$1.string().nullable()
|
|
1350
|
+
}).required().noUnknown();
|
|
909
1351
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1352
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1353
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1354
|
+
);
|
|
910
1355
|
const releaseController = {
|
|
911
|
-
|
|
912
|
-
|
|
1356
|
+
/**
|
|
1357
|
+
* Find releases based on documents attached or not to the release.
|
|
1358
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1359
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1360
|
+
*/
|
|
1361
|
+
async findByDocumentAttached(ctx) {
|
|
1362
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
913
1363
|
ability: ctx.state.userAbility,
|
|
914
1364
|
model: RELEASE_MODEL_UID
|
|
915
1365
|
});
|
|
916
1366
|
await permissionsManager.validateQuery(ctx.query);
|
|
917
1367
|
const releaseService = getService("release", { strapi });
|
|
918
|
-
const
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
const
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1368
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1369
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1370
|
+
const model = strapi.getModel(query.contentType);
|
|
1371
|
+
if (model.kind && model.kind === "singleType") {
|
|
1372
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1373
|
+
if (!document) {
|
|
1374
|
+
throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1375
|
+
}
|
|
1376
|
+
query.entryDocumentId = document.documentId;
|
|
1377
|
+
}
|
|
1378
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1379
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1380
|
+
if (isEntryAttached) {
|
|
1381
|
+
const releases = await releaseService.findMany({
|
|
1382
|
+
where: {
|
|
1383
|
+
releasedAt: null,
|
|
1384
|
+
actions: {
|
|
1385
|
+
contentType,
|
|
1386
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1387
|
+
locale: locale ?? null
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
populate: {
|
|
933
1391
|
actions: {
|
|
934
|
-
|
|
935
|
-
|
|
1392
|
+
fields: ["type"],
|
|
1393
|
+
filters: {
|
|
1394
|
+
contentType,
|
|
1395
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1396
|
+
locale: locale ?? null
|
|
936
1397
|
}
|
|
937
1398
|
}
|
|
938
|
-
}
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
ctx.body = { data: releases };
|
|
1402
|
+
} else {
|
|
1403
|
+
const relatedReleases = await releaseService.findMany({
|
|
1404
|
+
where: {
|
|
1405
|
+
releasedAt: null,
|
|
1406
|
+
actions: {
|
|
1407
|
+
contentType,
|
|
1408
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1409
|
+
locale: locale ?? null
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
});
|
|
1413
|
+
const releases = await releaseService.findMany({
|
|
1414
|
+
where: {
|
|
1415
|
+
$or: [
|
|
1416
|
+
{
|
|
1417
|
+
id: {
|
|
1418
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
actions: null
|
|
1423
|
+
}
|
|
1424
|
+
],
|
|
1425
|
+
releasedAt: null
|
|
1426
|
+
}
|
|
939
1427
|
});
|
|
940
|
-
ctx.body = { data
|
|
1428
|
+
ctx.body = { data: releases };
|
|
941
1429
|
}
|
|
942
1430
|
},
|
|
1431
|
+
async findPage(ctx) {
|
|
1432
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1433
|
+
ability: ctx.state.userAbility,
|
|
1434
|
+
model: RELEASE_MODEL_UID
|
|
1435
|
+
});
|
|
1436
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1437
|
+
const releaseService = getService("release", { strapi });
|
|
1438
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1439
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1440
|
+
const data = results.map((release2) => {
|
|
1441
|
+
const { actions, ...releaseData } = release2;
|
|
1442
|
+
return {
|
|
1443
|
+
...releaseData,
|
|
1444
|
+
actions: {
|
|
1445
|
+
meta: {
|
|
1446
|
+
count: actions.count
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
});
|
|
1451
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1452
|
+
where: {
|
|
1453
|
+
releasedAt: null
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1457
|
+
},
|
|
943
1458
|
async findOne(ctx) {
|
|
944
1459
|
const id = ctx.params.id;
|
|
945
1460
|
const releaseService = getService("release", { strapi });
|
|
1461
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
946
1462
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
947
1463
|
if (!release2) {
|
|
948
1464
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
949
1465
|
}
|
|
950
|
-
const count = await
|
|
1466
|
+
const count = await releaseActionService.countActions({
|
|
951
1467
|
filters: {
|
|
952
1468
|
release: id
|
|
953
1469
|
}
|
|
954
1470
|
});
|
|
955
1471
|
const sanitizedRelease = {
|
|
956
1472
|
...release2,
|
|
957
|
-
createdBy: release2.createdBy ? strapi.admin
|
|
1473
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
958
1474
|
};
|
|
959
1475
|
const data = {
|
|
960
1476
|
...sanitizedRelease,
|
|
@@ -966,19 +1482,63 @@ const releaseController = {
|
|
|
966
1482
|
};
|
|
967
1483
|
ctx.body = { data };
|
|
968
1484
|
},
|
|
1485
|
+
async mapEntriesToReleases(ctx) {
|
|
1486
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1487
|
+
if (!contentTypeUid || !documentIds) {
|
|
1488
|
+
throw new errors.ValidationError("Missing required query parameters");
|
|
1489
|
+
}
|
|
1490
|
+
const releaseService = getService("release", { strapi });
|
|
1491
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1492
|
+
where: {
|
|
1493
|
+
releasedAt: null,
|
|
1494
|
+
actions: {
|
|
1495
|
+
contentType: contentTypeUid,
|
|
1496
|
+
entryDocumentId: {
|
|
1497
|
+
$in: documentIds
|
|
1498
|
+
},
|
|
1499
|
+
locale
|
|
1500
|
+
}
|
|
1501
|
+
},
|
|
1502
|
+
populate: {
|
|
1503
|
+
actions: true
|
|
1504
|
+
}
|
|
1505
|
+
});
|
|
1506
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1507
|
+
(acc, release2) => {
|
|
1508
|
+
release2.actions.forEach((action) => {
|
|
1509
|
+
if (action.contentType !== contentTypeUid) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
if (locale && action.locale !== locale) {
|
|
1513
|
+
return;
|
|
1514
|
+
}
|
|
1515
|
+
if (!acc[action.entryDocumentId]) {
|
|
1516
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1517
|
+
} else {
|
|
1518
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1519
|
+
}
|
|
1520
|
+
});
|
|
1521
|
+
return acc;
|
|
1522
|
+
},
|
|
1523
|
+
{}
|
|
1524
|
+
);
|
|
1525
|
+
ctx.body = {
|
|
1526
|
+
data: mappedEntriesInReleases
|
|
1527
|
+
};
|
|
1528
|
+
},
|
|
969
1529
|
async create(ctx) {
|
|
970
1530
|
const user = ctx.state.user;
|
|
971
1531
|
const releaseArgs = ctx.request.body;
|
|
972
1532
|
await validateRelease(releaseArgs);
|
|
973
1533
|
const releaseService = getService("release", { strapi });
|
|
974
1534
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
975
|
-
const permissionsManager = strapi.admin
|
|
1535
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
976
1536
|
ability: ctx.state.userAbility,
|
|
977
1537
|
model: RELEASE_MODEL_UID
|
|
978
1538
|
});
|
|
979
|
-
ctx.
|
|
1539
|
+
ctx.created({
|
|
980
1540
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
981
|
-
};
|
|
1541
|
+
});
|
|
982
1542
|
},
|
|
983
1543
|
async update(ctx) {
|
|
984
1544
|
const user = ctx.state.user;
|
|
@@ -987,7 +1547,7 @@ const releaseController = {
|
|
|
987
1547
|
await validateRelease(releaseArgs);
|
|
988
1548
|
const releaseService = getService("release", { strapi });
|
|
989
1549
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
990
|
-
const permissionsManager = strapi.admin
|
|
1550
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
991
1551
|
ability: ctx.state.userAbility,
|
|
992
1552
|
model: RELEASE_MODEL_UID
|
|
993
1553
|
});
|
|
@@ -1004,18 +1564,18 @@ const releaseController = {
|
|
|
1004
1564
|
};
|
|
1005
1565
|
},
|
|
1006
1566
|
async publish(ctx) {
|
|
1007
|
-
const user = ctx.state.user;
|
|
1008
1567
|
const id = ctx.params.id;
|
|
1009
1568
|
const releaseService = getService("release", { strapi });
|
|
1010
|
-
const
|
|
1569
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1570
|
+
const release2 = await releaseService.publish(id);
|
|
1011
1571
|
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1012
|
-
|
|
1572
|
+
releaseActionService.countActions({
|
|
1013
1573
|
filters: {
|
|
1014
1574
|
release: id,
|
|
1015
1575
|
type: "publish"
|
|
1016
1576
|
}
|
|
1017
1577
|
}),
|
|
1018
|
-
|
|
1578
|
+
releaseActionService.countActions({
|
|
1019
1579
|
filters: {
|
|
1020
1580
|
release: id,
|
|
1021
1581
|
type: "unpublish"
|
|
@@ -1033,57 +1593,106 @@ const releaseController = {
|
|
|
1033
1593
|
}
|
|
1034
1594
|
};
|
|
1035
1595
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
}).required(),
|
|
1596
|
+
contentType: yup$1.string().required(),
|
|
1597
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1598
|
+
locale: yup$1.string(),
|
|
1040
1599
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1041
1600
|
});
|
|
1042
1601
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
1043
1602
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
1044
1603
|
});
|
|
1604
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1605
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1606
|
+
});
|
|
1045
1607
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
1046
1608
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1609
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
1047
1610
|
const releaseActionController = {
|
|
1048
1611
|
async create(ctx) {
|
|
1049
1612
|
const releaseId = ctx.params.releaseId;
|
|
1050
1613
|
const releaseActionArgs = ctx.request.body;
|
|
1051
1614
|
await validateReleaseAction(releaseActionArgs);
|
|
1052
|
-
const
|
|
1053
|
-
const releaseAction2 = await
|
|
1054
|
-
ctx.
|
|
1615
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1616
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1617
|
+
ctx.created({
|
|
1055
1618
|
data: releaseAction2
|
|
1056
|
-
};
|
|
1619
|
+
});
|
|
1620
|
+
},
|
|
1621
|
+
async createMany(ctx) {
|
|
1622
|
+
const releaseId = ctx.params.releaseId;
|
|
1623
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1624
|
+
await Promise.all(
|
|
1625
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1626
|
+
);
|
|
1627
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1628
|
+
const releaseService = getService("release", { strapi });
|
|
1629
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1630
|
+
const releaseActions2 = await Promise.all(
|
|
1631
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1632
|
+
try {
|
|
1633
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1634
|
+
disableUpdateReleaseStatus: true
|
|
1635
|
+
});
|
|
1636
|
+
return action;
|
|
1637
|
+
} catch (error) {
|
|
1638
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1639
|
+
return null;
|
|
1640
|
+
}
|
|
1641
|
+
throw error;
|
|
1642
|
+
}
|
|
1643
|
+
})
|
|
1644
|
+
);
|
|
1645
|
+
return releaseActions2;
|
|
1646
|
+
});
|
|
1647
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1648
|
+
if (newReleaseActions.length > 0) {
|
|
1649
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1650
|
+
}
|
|
1651
|
+
ctx.created({
|
|
1652
|
+
data: newReleaseActions,
|
|
1653
|
+
meta: {
|
|
1654
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1655
|
+
totalEntries: releaseActions.length
|
|
1656
|
+
}
|
|
1657
|
+
});
|
|
1057
1658
|
},
|
|
1058
1659
|
async findMany(ctx) {
|
|
1059
1660
|
const releaseId = ctx.params.releaseId;
|
|
1060
|
-
const permissionsManager = strapi.admin
|
|
1661
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1061
1662
|
ability: ctx.state.userAbility,
|
|
1062
1663
|
model: RELEASE_ACTION_MODEL_UID
|
|
1063
1664
|
});
|
|
1665
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1666
|
+
if (ctx.query.groupBy) {
|
|
1667
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1668
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1672
|
+
delete ctx.query.groupBy;
|
|
1064
1673
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1065
|
-
const
|
|
1066
|
-
const { results, pagination } = await
|
|
1067
|
-
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
1674
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1675
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1068
1676
|
...query
|
|
1069
1677
|
});
|
|
1070
1678
|
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1071
1679
|
if (acc[action.contentType]) {
|
|
1072
1680
|
return acc;
|
|
1073
1681
|
}
|
|
1074
|
-
const contentTypePermissionsManager = strapi.admin
|
|
1682
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1075
1683
|
ability: ctx.state.userAbility,
|
|
1076
1684
|
model: action.contentType
|
|
1077
1685
|
});
|
|
1078
1686
|
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
1079
1687
|
return acc;
|
|
1080
1688
|
}, {});
|
|
1081
|
-
const sanitizedResults = await
|
|
1689
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1082
1690
|
...action,
|
|
1083
|
-
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1691
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1084
1692
|
}));
|
|
1085
|
-
const groupedData = await
|
|
1086
|
-
const contentTypes2 =
|
|
1693
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1694
|
+
const contentTypes2 = await releaseActionService.getContentTypeModelsFromActions(results);
|
|
1695
|
+
const releaseService = getService("release", { strapi });
|
|
1087
1696
|
const components = await releaseService.getAllComponents();
|
|
1088
1697
|
ctx.body = {
|
|
1089
1698
|
data: groupedData,
|
|
@@ -1099,8 +1708,8 @@ const releaseActionController = {
|
|
|
1099
1708
|
const releaseId = ctx.params.releaseId;
|
|
1100
1709
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
1101
1710
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
1102
|
-
const
|
|
1103
|
-
const updatedAction = await
|
|
1711
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1712
|
+
const updatedAction = await releaseActionService.update(
|
|
1104
1713
|
actionId,
|
|
1105
1714
|
releaseId,
|
|
1106
1715
|
releaseActionUpdateArgs
|
|
@@ -1112,17 +1721,71 @@ const releaseActionController = {
|
|
|
1112
1721
|
async delete(ctx) {
|
|
1113
1722
|
const actionId = ctx.params.actionId;
|
|
1114
1723
|
const releaseId = ctx.params.releaseId;
|
|
1115
|
-
const
|
|
1116
|
-
const deletedReleaseAction = await
|
|
1724
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1725
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
1117
1726
|
ctx.body = {
|
|
1118
1727
|
data: deletedReleaseAction
|
|
1119
1728
|
};
|
|
1120
1729
|
}
|
|
1121
1730
|
};
|
|
1122
|
-
const
|
|
1731
|
+
const SETTINGS_SCHEMA = yup.object().shape({
|
|
1732
|
+
defaultTimezone: yup.string().nullable().default(null)
|
|
1733
|
+
}).required().noUnknown();
|
|
1734
|
+
const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
|
|
1735
|
+
const settingsController = {
|
|
1736
|
+
async find(ctx) {
|
|
1737
|
+
const settingsService = getService("settings", { strapi });
|
|
1738
|
+
const settings2 = await settingsService.find();
|
|
1739
|
+
ctx.body = { data: settings2 };
|
|
1740
|
+
},
|
|
1741
|
+
async update(ctx) {
|
|
1742
|
+
const settingsBody = ctx.request.body;
|
|
1743
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1744
|
+
const settingsService = getService("settings", { strapi });
|
|
1745
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1746
|
+
ctx.body = { data: updatedSettings };
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
const controllers = {
|
|
1750
|
+
release: releaseController,
|
|
1751
|
+
"release-action": releaseActionController,
|
|
1752
|
+
settings: settingsController
|
|
1753
|
+
};
|
|
1123
1754
|
const release = {
|
|
1124
1755
|
type: "admin",
|
|
1125
1756
|
routes: [
|
|
1757
|
+
{
|
|
1758
|
+
method: "GET",
|
|
1759
|
+
path: "/mapEntriesToReleases",
|
|
1760
|
+
handler: "release.mapEntriesToReleases",
|
|
1761
|
+
config: {
|
|
1762
|
+
policies: [
|
|
1763
|
+
"admin::isAuthenticatedAdmin",
|
|
1764
|
+
{
|
|
1765
|
+
name: "admin::hasPermissions",
|
|
1766
|
+
config: {
|
|
1767
|
+
actions: ["plugin::content-releases.read"]
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
]
|
|
1771
|
+
}
|
|
1772
|
+
},
|
|
1773
|
+
{
|
|
1774
|
+
method: "GET",
|
|
1775
|
+
path: "/getByDocumentAttached",
|
|
1776
|
+
handler: "release.findByDocumentAttached",
|
|
1777
|
+
config: {
|
|
1778
|
+
policies: [
|
|
1779
|
+
"admin::isAuthenticatedAdmin",
|
|
1780
|
+
{
|
|
1781
|
+
name: "admin::hasPermissions",
|
|
1782
|
+
config: {
|
|
1783
|
+
actions: ["plugin::content-releases.read"]
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
]
|
|
1787
|
+
}
|
|
1788
|
+
},
|
|
1126
1789
|
{
|
|
1127
1790
|
method: "POST",
|
|
1128
1791
|
path: "/",
|
|
@@ -1142,7 +1805,7 @@ const release = {
|
|
|
1142
1805
|
{
|
|
1143
1806
|
method: "GET",
|
|
1144
1807
|
path: "/",
|
|
1145
|
-
handler: "release.
|
|
1808
|
+
handler: "release.findPage",
|
|
1146
1809
|
config: {
|
|
1147
1810
|
policies: [
|
|
1148
1811
|
"admin::isAuthenticatedAdmin",
|
|
@@ -1240,6 +1903,22 @@ const releaseAction = {
|
|
|
1240
1903
|
]
|
|
1241
1904
|
}
|
|
1242
1905
|
},
|
|
1906
|
+
{
|
|
1907
|
+
method: "POST",
|
|
1908
|
+
path: "/:releaseId/actions/bulk",
|
|
1909
|
+
handler: "release-action.createMany",
|
|
1910
|
+
config: {
|
|
1911
|
+
policies: [
|
|
1912
|
+
"admin::isAuthenticatedAdmin",
|
|
1913
|
+
{
|
|
1914
|
+
name: "admin::hasPermissions",
|
|
1915
|
+
config: {
|
|
1916
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
]
|
|
1920
|
+
}
|
|
1921
|
+
},
|
|
1243
1922
|
{
|
|
1244
1923
|
method: "GET",
|
|
1245
1924
|
path: "/:releaseId/actions",
|
|
@@ -1290,13 +1969,50 @@ const releaseAction = {
|
|
|
1290
1969
|
}
|
|
1291
1970
|
]
|
|
1292
1971
|
};
|
|
1972
|
+
const settings = {
|
|
1973
|
+
type: "admin",
|
|
1974
|
+
routes: [
|
|
1975
|
+
{
|
|
1976
|
+
method: "GET",
|
|
1977
|
+
path: "/settings",
|
|
1978
|
+
handler: "settings.find",
|
|
1979
|
+
config: {
|
|
1980
|
+
policies: [
|
|
1981
|
+
"admin::isAuthenticatedAdmin",
|
|
1982
|
+
{
|
|
1983
|
+
name: "admin::hasPermissions",
|
|
1984
|
+
config: {
|
|
1985
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
]
|
|
1989
|
+
}
|
|
1990
|
+
},
|
|
1991
|
+
{
|
|
1992
|
+
method: "PUT",
|
|
1993
|
+
path: "/settings",
|
|
1994
|
+
handler: "settings.update",
|
|
1995
|
+
config: {
|
|
1996
|
+
policies: [
|
|
1997
|
+
"admin::isAuthenticatedAdmin",
|
|
1998
|
+
{
|
|
1999
|
+
name: "admin::hasPermissions",
|
|
2000
|
+
config: {
|
|
2001
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
]
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
]
|
|
2008
|
+
};
|
|
1293
2009
|
const routes = {
|
|
2010
|
+
settings,
|
|
1294
2011
|
release,
|
|
1295
2012
|
"release-action": releaseAction
|
|
1296
2013
|
};
|
|
1297
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
1298
2014
|
const getPlugin = () => {
|
|
1299
|
-
if (features.isEnabled("cms-content-releases")) {
|
|
2015
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
1300
2016
|
return {
|
|
1301
2017
|
register,
|
|
1302
2018
|
bootstrap,
|
|
@@ -1308,6 +2024,9 @@ const getPlugin = () => {
|
|
|
1308
2024
|
};
|
|
1309
2025
|
}
|
|
1310
2026
|
return {
|
|
2027
|
+
// Always return register, it handles its own feature check
|
|
2028
|
+
register,
|
|
2029
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
1311
2030
|
contentTypes
|
|
1312
2031
|
};
|
|
1313
2032
|
};
|