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