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