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