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