@strapi/content-releases 0.0.0-experimental.f7b9b47085e387e97f990d8695971b51d7f7149a → 0.0.0-experimental.f8a68bc03b751aa0a66c4dcfaf83553c9b6adf2b
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-BA2xDdy0.mjs +1374 -0
- package/dist/_chunks/App-BA2xDdy0.mjs.map +1 -0
- package/dist/_chunks/App-D4Wira1X.js +1395 -0
- package/dist/_chunks/App-D4Wira1X.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js +52 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs +52 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-BAlbMWpw.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-BAlbMWpw.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-xhFyRXCM.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-xhFyRXCM.js.map +1 -0
- package/dist/_chunks/en-CmYoEnA7.js +93 -0
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/en-D0yVZFqf.mjs +93 -0
- package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
- package/dist/_chunks/index-CCFFG3Zs.mjs +1365 -0
- package/dist/_chunks/index-CCFFG3Zs.mjs.map +1 -0
- package/dist/_chunks/index-DxkQGp4N.js +1384 -0
- package/dist/_chunks/index-DxkQGp4N.js.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 -14
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +2 -15
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionModal.d.ts +24 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +17 -0
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +76 -0
- package/dist/admin/src/index.d.ts +3 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -0
- package/dist/admin/src/pages/App.d.ts +1 -0
- package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
- package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
- package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
- package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
- package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
- package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/services/release.d.ts +112 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/time.d.ts +10 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +1460 -343
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1459 -345
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/constants.d.ts +21 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +97 -0
- package/dist/server/src/content-types/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/index.d.ts +48 -0
- package/dist/server/src/content-types/release/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/schema.d.ts +47 -0
- package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/index.d.ts +48 -0
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/schema.d.ts +47 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +25 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +10 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +18 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/settings.d.ts +11 -0
- package/dist/server/src/controllers/settings.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +14 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +4 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/settings.d.ts +3 -0
- package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/destroy.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +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 +34 -34
- package/dist/_chunks/App-a4843fda.mjs +0 -855
- package/dist/_chunks/App-a4843fda.mjs.map +0 -1
- package/dist/_chunks/App-f2cafd81.js +0 -877
- package/dist/_chunks/App-f2cafd81.js.map +0 -1
- package/dist/_chunks/en-13576ce2.js +0 -53
- package/dist/_chunks/en-13576ce2.js.map +0 -1
- package/dist/_chunks/en-e98d8b57.mjs +0 -53
- package/dist/_chunks/en-e98d8b57.mjs.map +0 -1
- package/dist/_chunks/index-66d129ac.js +0 -838
- package/dist/_chunks/index-66d129ac.js.map +0 -1
- package/dist/_chunks/index-937f8179.mjs +0 -817
- package/dist/_chunks/index-937f8179.mjs.map +0 -1
- package/strapi-server.js +0 -3
package/dist/server/index.mjs
CHANGED
|
@@ -1,4 +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";
|
|
4
|
+
import _ from "lodash/fp";
|
|
5
|
+
import { scheduleJob } from "node-schedule";
|
|
2
6
|
import * as yup from "yup";
|
|
3
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
4
8
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -44,12 +48,447 @@ const ACTIONS = [
|
|
|
44
48
|
displayName: "Add an entry to a release",
|
|
45
49
|
uid: "create-action",
|
|
46
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"
|
|
47
68
|
}
|
|
48
69
|
];
|
|
49
|
-
const
|
|
70
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
71
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
72
|
+
};
|
|
73
|
+
const getService = (name, { strapi: strapi2 }) => {
|
|
74
|
+
return strapi2.plugin("content-releases").service(name);
|
|
75
|
+
};
|
|
76
|
+
const getDraftEntryValidStatus = async ({ contentType, documentId, locale }, { strapi: strapi2 }) => {
|
|
77
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
78
|
+
const populate = await populateBuilderService(contentType).populateDeep(Infinity).build();
|
|
79
|
+
const entry = await getEntry({ contentType, documentId, locale, populate }, { strapi: strapi2 });
|
|
80
|
+
return isEntryValid(contentType, entry, { strapi: strapi2 });
|
|
81
|
+
};
|
|
82
|
+
const isEntryValid = async (contentTypeUid, entry, { strapi: strapi2 }) => {
|
|
83
|
+
try {
|
|
84
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
85
|
+
strapi2.getModel(contentTypeUid),
|
|
86
|
+
entry,
|
|
87
|
+
void 0,
|
|
88
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
89
|
+
entry
|
|
90
|
+
);
|
|
91
|
+
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
|
|
164
|
+
}
|
|
165
|
+
}
|
|
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
|
+
});
|
|
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
|
|
216
|
+
);
|
|
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
|
+
};
|
|
50
334
|
const register = async ({ strapi: strapi2 }) => {
|
|
51
|
-
if (features
|
|
52
|
-
await strapi2.admin
|
|
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);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
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
|
+
);
|
|
450
|
+
strapi2.db.lifecycles.subscribe({
|
|
451
|
+
models: contentTypesWithDraftAndPublish,
|
|
452
|
+
/**
|
|
453
|
+
* deleteMany is still used outside documents service, for example when deleting a locale
|
|
454
|
+
*/
|
|
455
|
+
async afterDeleteMany(event) {
|
|
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
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
}
|
|
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();
|
|
53
492
|
}
|
|
54
493
|
};
|
|
55
494
|
const schema$1 = {
|
|
@@ -78,6 +517,17 @@ const schema$1 = {
|
|
|
78
517
|
releasedAt: {
|
|
79
518
|
type: "datetime"
|
|
80
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
|
+
},
|
|
81
531
|
actions: {
|
|
82
532
|
type: "relation",
|
|
83
533
|
relation: "oneToMany",
|
|
@@ -113,20 +563,24 @@ const schema = {
|
|
|
113
563
|
enum: ["publish", "unpublish"],
|
|
114
564
|
required: true
|
|
115
565
|
},
|
|
116
|
-
entry: {
|
|
117
|
-
type: "relation",
|
|
118
|
-
relation: "morphToOne",
|
|
119
|
-
configurable: false
|
|
120
|
-
},
|
|
121
566
|
contentType: {
|
|
122
567
|
type: "string",
|
|
123
568
|
required: true
|
|
124
569
|
},
|
|
570
|
+
entryDocumentId: {
|
|
571
|
+
type: "string"
|
|
572
|
+
},
|
|
573
|
+
locale: {
|
|
574
|
+
type: "string"
|
|
575
|
+
},
|
|
125
576
|
release: {
|
|
126
577
|
type: "relation",
|
|
127
578
|
relation: "manyToOne",
|
|
128
579
|
target: RELEASE_MODEL_UID,
|
|
129
580
|
inversedBy: "actions"
|
|
581
|
+
},
|
|
582
|
+
isEntryValid: {
|
|
583
|
+
type: "boolean"
|
|
130
584
|
}
|
|
131
585
|
}
|
|
132
586
|
};
|
|
@@ -137,160 +591,307 @@ const contentTypes = {
|
|
|
137
591
|
release: release$1,
|
|
138
592
|
"release-action": releaseAction$1
|
|
139
593
|
};
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return strapi2.entityService.create(RELEASE_MODEL_UID, {
|
|
147
|
-
data: releaseWithCreatorFields
|
|
148
|
-
});
|
|
149
|
-
},
|
|
150
|
-
async findOne(id, query = {}) {
|
|
151
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, id, {
|
|
152
|
-
...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
|
|
153
600
|
});
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
actions: {
|
|
161
|
-
// @ts-expect-error Ignore missing properties
|
|
162
|
-
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
|
|
163
607
|
}
|
|
164
608
|
}
|
|
165
609
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
+
};
|
|
177
621
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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"
|
|
191
646
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
}
|
|
200
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}`);
|
|
201
695
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
+
}
|
|
208
732
|
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
|
|
733
|
+
});
|
|
734
|
+
if (!release2) {
|
|
735
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
212
736
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
737
|
+
if (release2.releasedAt) {
|
|
738
|
+
throw new errors.ValidationError("Release already published");
|
|
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);
|
|
222
757
|
}
|
|
758
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
223
759
|
return release2;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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}`);
|
|
769
|
+
}
|
|
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;
|
|
276
822
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
+
});
|
|
287
850
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
851
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
852
|
+
where: {
|
|
853
|
+
id: releaseId
|
|
854
|
+
},
|
|
855
|
+
data: {
|
|
856
|
+
status: "ready"
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
861
|
+
where: {
|
|
862
|
+
id: releaseId
|
|
863
|
+
},
|
|
864
|
+
data: {
|
|
865
|
+
status: "empty"
|
|
866
|
+
}
|
|
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 () => {
|
|
885
|
+
if (!strapi2.plugin("i18n")) {
|
|
886
|
+
return {};
|
|
887
|
+
}
|
|
888
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
889
|
+
return allLocales.reduce((acc, locale) => {
|
|
890
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
891
|
+
return acc;
|
|
892
|
+
}, {});
|
|
893
|
+
};
|
|
894
|
+
const getContentTypesDataForActions = async (contentTypesUids) => {
|
|
294
895
|
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
295
896
|
const contentTypesData = {};
|
|
296
897
|
for (const contentTypeUid of contentTypesUids) {
|
|
@@ -303,210 +904,500 @@ const createReleaseService = ({ strapi: strapi2 }) => ({
|
|
|
303
904
|
};
|
|
304
905
|
}
|
|
305
906
|
return contentTypesData;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
907
|
+
};
|
|
908
|
+
return {
|
|
909
|
+
async create(releaseId, action, { disableUpdateReleaseStatus = false } = {}) {
|
|
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 model = strapi2.contentType(action.contentType);
|
|
918
|
+
if (model.kind === "singleType") {
|
|
919
|
+
const document = await strapi2.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
920
|
+
if (!document) {
|
|
921
|
+
throw new errors.NotFoundError(`No entry found for contentType ${action.contentType}`);
|
|
312
922
|
}
|
|
923
|
+
action.entryDocumentId = document.documentId;
|
|
313
924
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
925
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
926
|
+
if (!release2) {
|
|
927
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
928
|
+
}
|
|
929
|
+
if (release2.releasedAt) {
|
|
930
|
+
throw new errors.ValidationError("Release already published");
|
|
931
|
+
}
|
|
932
|
+
const actionStatus = action.type === "publish" ? await getDraftEntryValidStatus(
|
|
933
|
+
{
|
|
934
|
+
contentType: action.contentType,
|
|
935
|
+
documentId: action.entryDocumentId,
|
|
936
|
+
locale: action.locale
|
|
937
|
+
},
|
|
938
|
+
{
|
|
939
|
+
strapi: strapi2
|
|
940
|
+
}
|
|
941
|
+
) : true;
|
|
942
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
943
|
+
data: {
|
|
944
|
+
...action,
|
|
945
|
+
release: release2.id,
|
|
946
|
+
isEntryValid: actionStatus
|
|
947
|
+
},
|
|
948
|
+
populate: { release: { select: ["id"] } }
|
|
949
|
+
});
|
|
950
|
+
if (!disableUpdateReleaseStatus) {
|
|
951
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
952
|
+
}
|
|
953
|
+
return releaseAction2;
|
|
954
|
+
},
|
|
955
|
+
async findPage(releaseId, query) {
|
|
956
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
957
|
+
where: { id: releaseId },
|
|
958
|
+
select: ["id"]
|
|
959
|
+
});
|
|
960
|
+
if (!release2) {
|
|
961
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
962
|
+
}
|
|
963
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
964
|
+
const { results: actions, pagination } = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
965
|
+
...dbQuery,
|
|
323
966
|
where: {
|
|
324
|
-
|
|
325
|
-
$in: release2.actions.map((action) => action.id)
|
|
326
|
-
}
|
|
967
|
+
release: releaseId
|
|
327
968
|
}
|
|
328
969
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
970
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
971
|
+
const actionsWithEntry = await async.map(actions, async (action) => {
|
|
972
|
+
const populate = await populateBuilderService(action.contentType).populateDeep(Infinity).build();
|
|
973
|
+
const entry = await getEntry(
|
|
974
|
+
{
|
|
975
|
+
contentType: action.contentType,
|
|
976
|
+
documentId: action.entryDocumentId,
|
|
977
|
+
locale: action.locale,
|
|
978
|
+
populate,
|
|
979
|
+
status: action.type === "publish" ? "draft" : "published"
|
|
980
|
+
},
|
|
981
|
+
{ strapi: strapi2 }
|
|
982
|
+
);
|
|
983
|
+
return {
|
|
984
|
+
...action,
|
|
985
|
+
entry,
|
|
986
|
+
status: entry ? await getEntryStatus(action.contentType, entry) : null
|
|
987
|
+
};
|
|
988
|
+
});
|
|
989
|
+
return {
|
|
990
|
+
results: actionsWithEntry,
|
|
991
|
+
pagination
|
|
992
|
+
};
|
|
993
|
+
},
|
|
994
|
+
async groupActions(actions, groupBy) {
|
|
995
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
996
|
+
if (!acc.includes(action.contentType)) {
|
|
997
|
+
acc.push(action.contentType);
|
|
344
998
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
if (!actions[contentTypeUid]) {
|
|
360
|
-
actions[contentTypeUid] = {
|
|
361
|
-
publish: [],
|
|
362
|
-
unpublish: []
|
|
999
|
+
return acc;
|
|
1000
|
+
}, []);
|
|
1001
|
+
const allReleaseContentTypesDictionary = await getContentTypesDataForActions(contentTypeUids);
|
|
1002
|
+
const allLocalesDictionary = await getLocalesDataForActions();
|
|
1003
|
+
const formattedData = actions.map((action) => {
|
|
1004
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
1005
|
+
return {
|
|
1006
|
+
...action,
|
|
1007
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
1008
|
+
contentType: {
|
|
1009
|
+
displayName,
|
|
1010
|
+
mainFieldValue: action.entry[mainField],
|
|
1011
|
+
uid: action.contentType
|
|
1012
|
+
}
|
|
363
1013
|
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
await strapi2.db.transaction(async () => {
|
|
373
|
-
for (const contentTypeUid of Object.keys(actions)) {
|
|
374
|
-
const { publish, unpublish } = actions[contentTypeUid];
|
|
375
|
-
if (publish.length > 0) {
|
|
376
|
-
await entityManagerService.publishMany(publish, contentTypeUid);
|
|
1014
|
+
});
|
|
1015
|
+
const groupName = getGroupName(groupBy);
|
|
1016
|
+
return _.groupBy(groupName)(formattedData);
|
|
1017
|
+
},
|
|
1018
|
+
getContentTypeModelsFromActions(actions) {
|
|
1019
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
1020
|
+
if (!acc.includes(action.contentType)) {
|
|
1021
|
+
acc.push(action.contentType);
|
|
377
1022
|
}
|
|
378
|
-
|
|
379
|
-
|
|
1023
|
+
return acc;
|
|
1024
|
+
}, []);
|
|
1025
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
1026
|
+
(acc, contentTypeUid) => {
|
|
1027
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
1028
|
+
return acc;
|
|
1029
|
+
},
|
|
1030
|
+
{}
|
|
1031
|
+
);
|
|
1032
|
+
return contentTypeModelsMap;
|
|
1033
|
+
},
|
|
1034
|
+
async countActions(query) {
|
|
1035
|
+
const dbQuery = strapi2.get("query-params").transform(RELEASE_ACTION_MODEL_UID, query ?? {});
|
|
1036
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
1037
|
+
},
|
|
1038
|
+
async update(actionId, releaseId, update) {
|
|
1039
|
+
const action = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).findOne({
|
|
1040
|
+
where: {
|
|
1041
|
+
id: actionId,
|
|
1042
|
+
release: {
|
|
1043
|
+
id: releaseId,
|
|
1044
|
+
releasedAt: {
|
|
1045
|
+
$null: true
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
380
1048
|
}
|
|
1049
|
+
});
|
|
1050
|
+
if (!action) {
|
|
1051
|
+
throw new errors.NotFoundError(
|
|
1052
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1053
|
+
);
|
|
381
1054
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
1055
|
+
const actionStatus = update.type === "publish" ? await getDraftEntryValidStatus(
|
|
1056
|
+
{
|
|
1057
|
+
contentType: action.contentType,
|
|
1058
|
+
documentId: action.entryDocumentId,
|
|
1059
|
+
locale: action.locale
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
strapi: strapi2
|
|
1063
|
+
}
|
|
1064
|
+
) : true;
|
|
1065
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
1066
|
+
where: {
|
|
1067
|
+
id: actionId,
|
|
1068
|
+
release: {
|
|
1069
|
+
id: releaseId,
|
|
1070
|
+
releasedAt: {
|
|
1071
|
+
$null: true
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
},
|
|
1075
|
+
data: {
|
|
1076
|
+
...update,
|
|
1077
|
+
isEntryValid: actionStatus
|
|
1078
|
+
}
|
|
1079
|
+
});
|
|
1080
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1081
|
+
return updatedAction;
|
|
1082
|
+
},
|
|
1083
|
+
async delete(actionId, releaseId) {
|
|
1084
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
1085
|
+
where: {
|
|
1086
|
+
id: actionId,
|
|
1087
|
+
release: {
|
|
1088
|
+
id: releaseId,
|
|
1089
|
+
releasedAt: {
|
|
1090
|
+
$null: true
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
});
|
|
1095
|
+
if (!deletedAction) {
|
|
1096
|
+
throw new errors.NotFoundError(
|
|
1097
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
1098
|
+
);
|
|
414
1099
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
throw new errors.NotFoundError(
|
|
418
|
-
`Action with id ${actionId} not found in release with id ${releaseId}`
|
|
419
|
-
);
|
|
1100
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1101
|
+
return deletedAction;
|
|
420
1102
|
}
|
|
421
|
-
|
|
1103
|
+
};
|
|
1104
|
+
};
|
|
1105
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1106
|
+
constructor(message) {
|
|
1107
|
+
super(message);
|
|
1108
|
+
this.name = "AlreadyOnReleaseError";
|
|
422
1109
|
}
|
|
423
|
-
}
|
|
1110
|
+
}
|
|
424
1111
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
425
1112
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
426
|
-
const release2 = await strapi2.
|
|
427
|
-
|
|
1113
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1114
|
+
where: {
|
|
1115
|
+
id: releaseId
|
|
1116
|
+
},
|
|
1117
|
+
populate: {
|
|
1118
|
+
actions: true
|
|
1119
|
+
}
|
|
428
1120
|
});
|
|
429
1121
|
if (!release2) {
|
|
430
1122
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
431
1123
|
}
|
|
432
1124
|
const isEntryInRelease = release2.actions.some(
|
|
433
|
-
(action) =>
|
|
1125
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
434
1126
|
);
|
|
435
1127
|
if (isEntryInRelease) {
|
|
436
|
-
throw new
|
|
437
|
-
`Entry with
|
|
1128
|
+
throw new AlreadyOnReleaseError(
|
|
1129
|
+
`Entry with documentId ${releaseActionArgs.entryDocumentId}${releaseActionArgs.locale ? `( ${releaseActionArgs.locale})` : ""} and contentType ${releaseActionArgs.contentType} already exists in release with id ${releaseId}`
|
|
438
1130
|
);
|
|
439
1131
|
}
|
|
440
1132
|
},
|
|
441
|
-
|
|
1133
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
442
1134
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
443
1135
|
if (!contentType) {
|
|
444
1136
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
445
1137
|
}
|
|
446
|
-
if (!contentType
|
|
1138
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
447
1139
|
throw new errors.ValidationError(
|
|
448
1140
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
449
1141
|
);
|
|
450
1142
|
}
|
|
1143
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1144
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1145
|
+
}
|
|
1146
|
+
},
|
|
1147
|
+
async validatePendingReleasesLimit() {
|
|
1148
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1149
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
1150
|
+
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
1151
|
+
filters: {
|
|
1152
|
+
releasedAt: {
|
|
1153
|
+
$null: true
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
1158
|
+
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1162
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1163
|
+
where: {
|
|
1164
|
+
releasedAt: {
|
|
1165
|
+
$null: true
|
|
1166
|
+
},
|
|
1167
|
+
name,
|
|
1168
|
+
...id && { id: { $ne: id } }
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
1172
|
+
if (!isNameUnique) {
|
|
1173
|
+
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
1174
|
+
}
|
|
1175
|
+
},
|
|
1176
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1177
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1178
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
1179
|
+
}
|
|
451
1180
|
}
|
|
452
1181
|
});
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
1182
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1183
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1184
|
+
return {
|
|
1185
|
+
async set(releaseId, scheduleDate) {
|
|
1186
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1187
|
+
if (!release2) {
|
|
1188
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1189
|
+
}
|
|
1190
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
1191
|
+
try {
|
|
1192
|
+
await getService("release", { strapi: strapi2 }).publish(releaseId);
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
}
|
|
1195
|
+
this.cancel(releaseId);
|
|
1196
|
+
});
|
|
1197
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1198
|
+
this.cancel(releaseId);
|
|
1199
|
+
}
|
|
1200
|
+
scheduledJobs.set(releaseId, job);
|
|
1201
|
+
return scheduledJobs;
|
|
1202
|
+
},
|
|
1203
|
+
cancel(releaseId) {
|
|
1204
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1205
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1206
|
+
scheduledJobs.delete(releaseId);
|
|
1207
|
+
}
|
|
1208
|
+
return scheduledJobs;
|
|
1209
|
+
},
|
|
1210
|
+
getAll() {
|
|
1211
|
+
return scheduledJobs;
|
|
1212
|
+
},
|
|
1213
|
+
/**
|
|
1214
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1215
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1216
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1217
|
+
*/
|
|
1218
|
+
async syncFromDatabase() {
|
|
1219
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1220
|
+
where: {
|
|
1221
|
+
scheduledAt: {
|
|
1222
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1223
|
+
},
|
|
1224
|
+
releasedAt: null
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
for (const release2 of releases) {
|
|
1228
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1229
|
+
}
|
|
1230
|
+
return scheduledJobs;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
};
|
|
1234
|
+
const DEFAULT_SETTINGS = {
|
|
1235
|
+
defaultTimezone: null
|
|
1236
|
+
};
|
|
1237
|
+
const createSettingsService = ({ strapi: strapi2 }) => {
|
|
1238
|
+
const getStore = async () => strapi2.store({ type: "core", name: "content-releases" });
|
|
1239
|
+
return {
|
|
1240
|
+
async update({ settings: settings2 }) {
|
|
1241
|
+
const store = await getStore();
|
|
1242
|
+
store.set({ key: "settings", value: settings2 });
|
|
1243
|
+
return settings2;
|
|
1244
|
+
},
|
|
1245
|
+
async find() {
|
|
1246
|
+
const store = await getStore();
|
|
1247
|
+
const settings2 = await store.get({ key: "settings" });
|
|
1248
|
+
return {
|
|
1249
|
+
...DEFAULT_SETTINGS,
|
|
1250
|
+
...settings2 || {}
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
};
|
|
1254
|
+
};
|
|
1255
|
+
const services = {
|
|
1256
|
+
release: createReleaseService,
|
|
1257
|
+
"release-action": createReleaseActionService,
|
|
1258
|
+
"release-validation": createReleaseValidationService,
|
|
1259
|
+
scheduling: createSchedulingService,
|
|
1260
|
+
settings: createSettingsService
|
|
1261
|
+
};
|
|
1262
|
+
const RELEASE_SCHEMA = yup$1.object().shape({
|
|
1263
|
+
name: yup$1.string().trim().required(),
|
|
1264
|
+
scheduledAt: yup$1.string().nullable(),
|
|
1265
|
+
timezone: yup$1.string().when("scheduledAt", {
|
|
1266
|
+
is: (value) => value !== null && value !== void 0,
|
|
1267
|
+
then: yup$1.string().required(),
|
|
1268
|
+
otherwise: yup$1.string().nullable()
|
|
1269
|
+
})
|
|
1270
|
+
}).required().noUnknown();
|
|
1271
|
+
const FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA = yup$1.object().shape({
|
|
1272
|
+
contentType: yup$1.string().required(),
|
|
1273
|
+
entryDocumentId: yup$1.string().nullable(),
|
|
1274
|
+
hasEntryAttached: yup$1.string().nullable(),
|
|
1275
|
+
locale: yup$1.string().nullable()
|
|
456
1276
|
}).required().noUnknown();
|
|
457
1277
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1278
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1279
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1280
|
+
);
|
|
458
1281
|
const releaseController = {
|
|
459
|
-
|
|
460
|
-
|
|
1282
|
+
/**
|
|
1283
|
+
* Find releases based on documents attached or not to the release.
|
|
1284
|
+
* If `hasEntryAttached` is true, it will return all releases that have the entry attached.
|
|
1285
|
+
* If `hasEntryAttached` is false, it will return all releases that don't have the entry attached.
|
|
1286
|
+
*/
|
|
1287
|
+
async findByDocumentAttached(ctx) {
|
|
1288
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
461
1289
|
ability: ctx.state.userAbility,
|
|
462
1290
|
model: RELEASE_MODEL_UID
|
|
463
1291
|
});
|
|
464
1292
|
await permissionsManager.validateQuery(ctx.query);
|
|
465
1293
|
const releaseService = getService("release", { strapi });
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
1294
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1295
|
+
await validatefindByDocumentAttachedParams(query);
|
|
1296
|
+
const model = strapi.getModel(query.contentType);
|
|
1297
|
+
if (model.kind && model.kind === "singleType") {
|
|
1298
|
+
const document = await strapi.db.query(model.uid).findOne({ select: ["documentId"] });
|
|
1299
|
+
if (!document) {
|
|
1300
|
+
throw new errors.NotFoundError(`No entry found for contentType ${query.contentType}`);
|
|
1301
|
+
}
|
|
1302
|
+
query.entryDocumentId = document.documentId;
|
|
1303
|
+
}
|
|
1304
|
+
const { contentType, hasEntryAttached, entryDocumentId, locale } = query;
|
|
1305
|
+
const isEntryAttached = typeof hasEntryAttached === "string" ? Boolean(JSON.parse(hasEntryAttached)) : false;
|
|
1306
|
+
if (isEntryAttached) {
|
|
1307
|
+
const releases = await releaseService.findMany({
|
|
1308
|
+
where: {
|
|
1309
|
+
releasedAt: null,
|
|
1310
|
+
actions: {
|
|
1311
|
+
contentType,
|
|
1312
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1313
|
+
locale: locale ?? null
|
|
1314
|
+
}
|
|
1315
|
+
},
|
|
1316
|
+
populate: {
|
|
1317
|
+
actions: {
|
|
1318
|
+
fields: ["type"],
|
|
1319
|
+
filters: {
|
|
1320
|
+
contentType,
|
|
1321
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1322
|
+
locale: locale ?? null
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
474
1326
|
});
|
|
475
|
-
ctx.body = { data };
|
|
1327
|
+
ctx.body = { data: releases };
|
|
476
1328
|
} else {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const { actions, ...releaseData } = release2;
|
|
481
|
-
return {
|
|
482
|
-
...releaseData,
|
|
1329
|
+
const relatedReleases = await releaseService.findMany({
|
|
1330
|
+
where: {
|
|
1331
|
+
releasedAt: null,
|
|
483
1332
|
actions: {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1333
|
+
contentType,
|
|
1334
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1335
|
+
locale: locale ?? null
|
|
487
1336
|
}
|
|
488
|
-
}
|
|
1337
|
+
}
|
|
1338
|
+
});
|
|
1339
|
+
const releases = await releaseService.findMany({
|
|
1340
|
+
where: {
|
|
1341
|
+
$or: [
|
|
1342
|
+
{
|
|
1343
|
+
id: {
|
|
1344
|
+
$notIn: relatedReleases.map((release2) => release2.id)
|
|
1345
|
+
}
|
|
1346
|
+
},
|
|
1347
|
+
{
|
|
1348
|
+
actions: null
|
|
1349
|
+
}
|
|
1350
|
+
],
|
|
1351
|
+
releasedAt: null
|
|
1352
|
+
}
|
|
489
1353
|
});
|
|
490
|
-
ctx.body = { data
|
|
1354
|
+
ctx.body = { data: releases };
|
|
491
1355
|
}
|
|
492
1356
|
},
|
|
493
|
-
async
|
|
494
|
-
const
|
|
495
|
-
const releaseService = getService("release", { strapi });
|
|
496
|
-
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
497
|
-
const permissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1357
|
+
async findPage(ctx) {
|
|
1358
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
498
1359
|
ability: ctx.state.userAbility,
|
|
499
1360
|
model: RELEASE_MODEL_UID
|
|
500
1361
|
});
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
1362
|
+
await permissionsManager.validateQuery(ctx.query);
|
|
1363
|
+
const releaseService = getService("release", { strapi });
|
|
1364
|
+
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
1365
|
+
const { results, pagination } = await releaseService.findPage(query);
|
|
1366
|
+
const data = results.map((release2) => {
|
|
1367
|
+
const { actions, ...releaseData } = release2;
|
|
1368
|
+
return {
|
|
1369
|
+
...releaseData,
|
|
1370
|
+
actions: {
|
|
1371
|
+
meta: {
|
|
1372
|
+
count: actions.count
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
};
|
|
1376
|
+
});
|
|
1377
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1378
|
+
where: {
|
|
1379
|
+
releasedAt: null
|
|
505
1380
|
}
|
|
506
1381
|
});
|
|
1382
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
1383
|
+
},
|
|
1384
|
+
async findOne(ctx) {
|
|
1385
|
+
const id = ctx.params.id;
|
|
1386
|
+
const releaseService = getService("release", { strapi });
|
|
1387
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1388
|
+
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
507
1389
|
if (!release2) {
|
|
508
1390
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
509
1391
|
}
|
|
1392
|
+
const count = await releaseActionService.countActions({
|
|
1393
|
+
filters: {
|
|
1394
|
+
release: id
|
|
1395
|
+
}
|
|
1396
|
+
});
|
|
1397
|
+
const sanitizedRelease = {
|
|
1398
|
+
...release2,
|
|
1399
|
+
createdBy: release2.createdBy ? strapi.service("admin::user").sanitizeUser(release2.createdBy) : null
|
|
1400
|
+
};
|
|
510
1401
|
const data = {
|
|
511
1402
|
...sanitizedRelease,
|
|
512
1403
|
actions: {
|
|
@@ -517,19 +1408,63 @@ const releaseController = {
|
|
|
517
1408
|
};
|
|
518
1409
|
ctx.body = { data };
|
|
519
1410
|
},
|
|
1411
|
+
async mapEntriesToReleases(ctx) {
|
|
1412
|
+
const { contentTypeUid, documentIds, locale } = ctx.query;
|
|
1413
|
+
if (!contentTypeUid || !documentIds) {
|
|
1414
|
+
throw new errors.ValidationError("Missing required query parameters");
|
|
1415
|
+
}
|
|
1416
|
+
const releaseService = getService("release", { strapi });
|
|
1417
|
+
const releasesWithActions = await releaseService.findMany({
|
|
1418
|
+
where: {
|
|
1419
|
+
releasedAt: null,
|
|
1420
|
+
actions: {
|
|
1421
|
+
contentType: contentTypeUid,
|
|
1422
|
+
entryDocumentId: {
|
|
1423
|
+
$in: documentIds
|
|
1424
|
+
},
|
|
1425
|
+
locale
|
|
1426
|
+
}
|
|
1427
|
+
},
|
|
1428
|
+
populate: {
|
|
1429
|
+
actions: true
|
|
1430
|
+
}
|
|
1431
|
+
});
|
|
1432
|
+
const mappedEntriesInReleases = releasesWithActions.reduce(
|
|
1433
|
+
(acc, release2) => {
|
|
1434
|
+
release2.actions.forEach((action) => {
|
|
1435
|
+
if (action.contentType !== contentTypeUid) {
|
|
1436
|
+
return;
|
|
1437
|
+
}
|
|
1438
|
+
if (locale && action.locale !== locale) {
|
|
1439
|
+
return;
|
|
1440
|
+
}
|
|
1441
|
+
if (!acc[action.entryDocumentId]) {
|
|
1442
|
+
acc[action.entryDocumentId] = [{ id: release2.id, name: release2.name }];
|
|
1443
|
+
} else {
|
|
1444
|
+
acc[action.entryDocumentId].push({ id: release2.id, name: release2.name });
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
return acc;
|
|
1448
|
+
},
|
|
1449
|
+
{}
|
|
1450
|
+
);
|
|
1451
|
+
ctx.body = {
|
|
1452
|
+
data: mappedEntriesInReleases
|
|
1453
|
+
};
|
|
1454
|
+
},
|
|
520
1455
|
async create(ctx) {
|
|
521
1456
|
const user = ctx.state.user;
|
|
522
1457
|
const releaseArgs = ctx.request.body;
|
|
523
1458
|
await validateRelease(releaseArgs);
|
|
524
1459
|
const releaseService = getService("release", { strapi });
|
|
525
1460
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
526
|
-
const permissionsManager = strapi.admin
|
|
1461
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
527
1462
|
ability: ctx.state.userAbility,
|
|
528
1463
|
model: RELEASE_MODEL_UID
|
|
529
1464
|
});
|
|
530
|
-
ctx.
|
|
1465
|
+
ctx.created({
|
|
531
1466
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
532
|
-
};
|
|
1467
|
+
});
|
|
533
1468
|
},
|
|
534
1469
|
async update(ctx) {
|
|
535
1470
|
const user = ctx.state.user;
|
|
@@ -538,7 +1473,7 @@ const releaseController = {
|
|
|
538
1473
|
await validateRelease(releaseArgs);
|
|
539
1474
|
const releaseService = getService("release", { strapi });
|
|
540
1475
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
541
|
-
const permissionsManager = strapi.admin
|
|
1476
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
542
1477
|
ability: ctx.state.userAbility,
|
|
543
1478
|
model: RELEASE_MODEL_UID
|
|
544
1479
|
});
|
|
@@ -555,73 +1490,142 @@ const releaseController = {
|
|
|
555
1490
|
};
|
|
556
1491
|
},
|
|
557
1492
|
async publish(ctx) {
|
|
558
|
-
const user = ctx.state.user;
|
|
559
1493
|
const id = ctx.params.id;
|
|
560
1494
|
const releaseService = getService("release", { strapi });
|
|
561
|
-
const
|
|
1495
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1496
|
+
const release2 = await releaseService.publish(id);
|
|
1497
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1498
|
+
releaseActionService.countActions({
|
|
1499
|
+
filters: {
|
|
1500
|
+
release: id,
|
|
1501
|
+
type: "publish"
|
|
1502
|
+
}
|
|
1503
|
+
}),
|
|
1504
|
+
releaseActionService.countActions({
|
|
1505
|
+
filters: {
|
|
1506
|
+
release: id,
|
|
1507
|
+
type: "unpublish"
|
|
1508
|
+
}
|
|
1509
|
+
})
|
|
1510
|
+
]);
|
|
562
1511
|
ctx.body = {
|
|
563
|
-
data: release2
|
|
1512
|
+
data: release2,
|
|
1513
|
+
meta: {
|
|
1514
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1515
|
+
totalPublishedEntries: countPublishActions,
|
|
1516
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1517
|
+
}
|
|
564
1518
|
};
|
|
565
1519
|
}
|
|
566
1520
|
};
|
|
567
1521
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}).required(),
|
|
1522
|
+
contentType: yup$1.string().required(),
|
|
1523
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1524
|
+
locale: yup$1.string(),
|
|
572
1525
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
573
1526
|
});
|
|
574
1527
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
575
1528
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
576
1529
|
});
|
|
1530
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1531
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1532
|
+
});
|
|
577
1533
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
578
1534
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1535
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
579
1536
|
const releaseActionController = {
|
|
580
1537
|
async create(ctx) {
|
|
581
1538
|
const releaseId = ctx.params.releaseId;
|
|
582
1539
|
const releaseActionArgs = ctx.request.body;
|
|
583
1540
|
await validateReleaseAction(releaseActionArgs);
|
|
584
|
-
const
|
|
585
|
-
const releaseAction2 = await
|
|
586
|
-
ctx.
|
|
1541
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1542
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1543
|
+
ctx.created({
|
|
587
1544
|
data: releaseAction2
|
|
588
|
-
};
|
|
1545
|
+
});
|
|
1546
|
+
},
|
|
1547
|
+
async createMany(ctx) {
|
|
1548
|
+
const releaseId = ctx.params.releaseId;
|
|
1549
|
+
const releaseActionsArgs = ctx.request.body;
|
|
1550
|
+
await Promise.all(
|
|
1551
|
+
releaseActionsArgs.map((releaseActionArgs) => validateReleaseAction(releaseActionArgs))
|
|
1552
|
+
);
|
|
1553
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1554
|
+
const releaseService = getService("release", { strapi });
|
|
1555
|
+
const releaseActions = await strapi.db.transaction(async () => {
|
|
1556
|
+
const releaseActions2 = await Promise.all(
|
|
1557
|
+
releaseActionsArgs.map(async (releaseActionArgs) => {
|
|
1558
|
+
try {
|
|
1559
|
+
const action = await releaseActionService.create(releaseId, releaseActionArgs, {
|
|
1560
|
+
disableUpdateReleaseStatus: true
|
|
1561
|
+
});
|
|
1562
|
+
return action;
|
|
1563
|
+
} catch (error) {
|
|
1564
|
+
if (error instanceof AlreadyOnReleaseError) {
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
throw error;
|
|
1568
|
+
}
|
|
1569
|
+
})
|
|
1570
|
+
);
|
|
1571
|
+
return releaseActions2;
|
|
1572
|
+
});
|
|
1573
|
+
const newReleaseActions = releaseActions.filter((action) => action !== null);
|
|
1574
|
+
if (newReleaseActions.length > 0) {
|
|
1575
|
+
releaseService.updateReleaseStatus(releaseId);
|
|
1576
|
+
}
|
|
1577
|
+
ctx.created({
|
|
1578
|
+
data: newReleaseActions,
|
|
1579
|
+
meta: {
|
|
1580
|
+
entriesAlreadyInRelease: releaseActions.length - newReleaseActions.length,
|
|
1581
|
+
totalEntries: releaseActions.length
|
|
1582
|
+
}
|
|
1583
|
+
});
|
|
589
1584
|
},
|
|
590
1585
|
async findMany(ctx) {
|
|
591
1586
|
const releaseId = ctx.params.releaseId;
|
|
592
|
-
const permissionsManager = strapi.admin
|
|
1587
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
593
1588
|
ability: ctx.state.userAbility,
|
|
594
1589
|
model: RELEASE_ACTION_MODEL_UID
|
|
595
1590
|
});
|
|
1591
|
+
await validateFindManyActionsParams(ctx.query);
|
|
1592
|
+
if (ctx.query.groupBy) {
|
|
1593
|
+
if (!["action", "contentType", "locale"].includes(ctx.query.groupBy)) {
|
|
1594
|
+
ctx.badRequest("Invalid groupBy parameter");
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
ctx.query.sort = ctx.query.groupBy === "action" ? "type" : ctx.query.groupBy;
|
|
1598
|
+
delete ctx.query.groupBy;
|
|
596
1599
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
597
|
-
const
|
|
598
|
-
const { results, pagination } = await
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1600
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1601
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1602
|
+
...query
|
|
1603
|
+
});
|
|
1604
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1605
|
+
if (acc[action.contentType]) {
|
|
1606
|
+
return acc;
|
|
1607
|
+
}
|
|
1608
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1609
|
+
ability: ctx.state.userAbility,
|
|
1610
|
+
model: action.contentType
|
|
1611
|
+
});
|
|
1612
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
605
1613
|
return acc;
|
|
606
1614
|
}, {});
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
mainFieldValue: action.entry[mainField]
|
|
616
|
-
},
|
|
617
|
-
locale: allLocalesDictionary[action.entry.locale]
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
});
|
|
1615
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1616
|
+
...action,
|
|
1617
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1618
|
+
}));
|
|
1619
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1620
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1621
|
+
const releaseService = getService("release", { strapi });
|
|
1622
|
+
const components = await releaseService.getAllComponents();
|
|
621
1623
|
ctx.body = {
|
|
622
|
-
data,
|
|
1624
|
+
data: groupedData,
|
|
623
1625
|
meta: {
|
|
624
|
-
pagination
|
|
1626
|
+
pagination,
|
|
1627
|
+
contentTypes: contentTypes2,
|
|
1628
|
+
components
|
|
625
1629
|
}
|
|
626
1630
|
};
|
|
627
1631
|
},
|
|
@@ -630,8 +1634,8 @@ const releaseActionController = {
|
|
|
630
1634
|
const releaseId = ctx.params.releaseId;
|
|
631
1635
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
632
1636
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
633
|
-
const
|
|
634
|
-
const updatedAction = await
|
|
1637
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1638
|
+
const updatedAction = await releaseActionService.update(
|
|
635
1639
|
actionId,
|
|
636
1640
|
releaseId,
|
|
637
1641
|
releaseActionUpdateArgs
|
|
@@ -643,19 +1647,71 @@ const releaseActionController = {
|
|
|
643
1647
|
async delete(ctx) {
|
|
644
1648
|
const actionId = ctx.params.actionId;
|
|
645
1649
|
const releaseId = ctx.params.releaseId;
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
releaseId
|
|
649
|
-
);
|
|
1650
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1651
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
650
1652
|
ctx.body = {
|
|
651
1653
|
data: deletedReleaseAction
|
|
652
1654
|
};
|
|
653
1655
|
}
|
|
654
1656
|
};
|
|
655
|
-
const
|
|
1657
|
+
const SETTINGS_SCHEMA = yup.object().shape({
|
|
1658
|
+
defaultTimezone: yup.string().nullable().default(null)
|
|
1659
|
+
}).required().noUnknown();
|
|
1660
|
+
const validateSettings = validateYupSchema(SETTINGS_SCHEMA);
|
|
1661
|
+
const settingsController = {
|
|
1662
|
+
async find(ctx) {
|
|
1663
|
+
const settingsService = getService("settings", { strapi });
|
|
1664
|
+
const settings2 = await settingsService.find();
|
|
1665
|
+
ctx.body = { data: settings2 };
|
|
1666
|
+
},
|
|
1667
|
+
async update(ctx) {
|
|
1668
|
+
const settingsBody = ctx.request.body;
|
|
1669
|
+
const settings2 = await validateSettings(settingsBody);
|
|
1670
|
+
const settingsService = getService("settings", { strapi });
|
|
1671
|
+
const updatedSettings = await settingsService.update({ settings: settings2 });
|
|
1672
|
+
ctx.body = { data: updatedSettings };
|
|
1673
|
+
}
|
|
1674
|
+
};
|
|
1675
|
+
const controllers = {
|
|
1676
|
+
release: releaseController,
|
|
1677
|
+
"release-action": releaseActionController,
|
|
1678
|
+
settings: settingsController
|
|
1679
|
+
};
|
|
656
1680
|
const release = {
|
|
657
1681
|
type: "admin",
|
|
658
1682
|
routes: [
|
|
1683
|
+
{
|
|
1684
|
+
method: "GET",
|
|
1685
|
+
path: "/mapEntriesToReleases",
|
|
1686
|
+
handler: "release.mapEntriesToReleases",
|
|
1687
|
+
config: {
|
|
1688
|
+
policies: [
|
|
1689
|
+
"admin::isAuthenticatedAdmin",
|
|
1690
|
+
{
|
|
1691
|
+
name: "admin::hasPermissions",
|
|
1692
|
+
config: {
|
|
1693
|
+
actions: ["plugin::content-releases.read"]
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
]
|
|
1697
|
+
}
|
|
1698
|
+
},
|
|
1699
|
+
{
|
|
1700
|
+
method: "GET",
|
|
1701
|
+
path: "/getByDocumentAttached",
|
|
1702
|
+
handler: "release.findByDocumentAttached",
|
|
1703
|
+
config: {
|
|
1704
|
+
policies: [
|
|
1705
|
+
"admin::isAuthenticatedAdmin",
|
|
1706
|
+
{
|
|
1707
|
+
name: "admin::hasPermissions",
|
|
1708
|
+
config: {
|
|
1709
|
+
actions: ["plugin::content-releases.read"]
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
]
|
|
1713
|
+
}
|
|
1714
|
+
},
|
|
659
1715
|
{
|
|
660
1716
|
method: "POST",
|
|
661
1717
|
path: "/",
|
|
@@ -675,7 +1731,7 @@ const release = {
|
|
|
675
1731
|
{
|
|
676
1732
|
method: "GET",
|
|
677
1733
|
path: "/",
|
|
678
|
-
handler: "release.
|
|
1734
|
+
handler: "release.findPage",
|
|
679
1735
|
config: {
|
|
680
1736
|
policies: [
|
|
681
1737
|
"admin::isAuthenticatedAdmin",
|
|
@@ -773,6 +1829,22 @@ const releaseAction = {
|
|
|
773
1829
|
]
|
|
774
1830
|
}
|
|
775
1831
|
},
|
|
1832
|
+
{
|
|
1833
|
+
method: "POST",
|
|
1834
|
+
path: "/:releaseId/actions/bulk",
|
|
1835
|
+
handler: "release-action.createMany",
|
|
1836
|
+
config: {
|
|
1837
|
+
policies: [
|
|
1838
|
+
"admin::isAuthenticatedAdmin",
|
|
1839
|
+
{
|
|
1840
|
+
name: "admin::hasPermissions",
|
|
1841
|
+
config: {
|
|
1842
|
+
actions: ["plugin::content-releases.create-action"]
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
]
|
|
1846
|
+
}
|
|
1847
|
+
},
|
|
776
1848
|
{
|
|
777
1849
|
method: "GET",
|
|
778
1850
|
path: "/:releaseId/actions",
|
|
@@ -823,15 +1895,54 @@ const releaseAction = {
|
|
|
823
1895
|
}
|
|
824
1896
|
]
|
|
825
1897
|
};
|
|
1898
|
+
const settings = {
|
|
1899
|
+
type: "admin",
|
|
1900
|
+
routes: [
|
|
1901
|
+
{
|
|
1902
|
+
method: "GET",
|
|
1903
|
+
path: "/settings",
|
|
1904
|
+
handler: "settings.find",
|
|
1905
|
+
config: {
|
|
1906
|
+
policies: [
|
|
1907
|
+
"admin::isAuthenticatedAdmin",
|
|
1908
|
+
{
|
|
1909
|
+
name: "admin::hasPermissions",
|
|
1910
|
+
config: {
|
|
1911
|
+
actions: ["plugin::content-releases.settings.read"]
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
]
|
|
1915
|
+
}
|
|
1916
|
+
},
|
|
1917
|
+
{
|
|
1918
|
+
method: "PUT",
|
|
1919
|
+
path: "/settings",
|
|
1920
|
+
handler: "settings.update",
|
|
1921
|
+
config: {
|
|
1922
|
+
policies: [
|
|
1923
|
+
"admin::isAuthenticatedAdmin",
|
|
1924
|
+
{
|
|
1925
|
+
name: "admin::hasPermissions",
|
|
1926
|
+
config: {
|
|
1927
|
+
actions: ["plugin::content-releases.settings.update"]
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
]
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
]
|
|
1934
|
+
};
|
|
826
1935
|
const routes = {
|
|
1936
|
+
settings,
|
|
827
1937
|
release,
|
|
828
1938
|
"release-action": releaseAction
|
|
829
1939
|
};
|
|
830
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
831
1940
|
const getPlugin = () => {
|
|
832
|
-
if (features.isEnabled("cms-content-releases")
|
|
1941
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
833
1942
|
return {
|
|
834
1943
|
register,
|
|
1944
|
+
bootstrap,
|
|
1945
|
+
destroy,
|
|
835
1946
|
contentTypes,
|
|
836
1947
|
services,
|
|
837
1948
|
controllers,
|
|
@@ -839,6 +1950,9 @@ const getPlugin = () => {
|
|
|
839
1950
|
};
|
|
840
1951
|
}
|
|
841
1952
|
return {
|
|
1953
|
+
// Always return register, it handles its own feature check
|
|
1954
|
+
register,
|
|
1955
|
+
// Always return contentTypes to avoid losing data when the feature is disabled
|
|
842
1956
|
contentTypes
|
|
843
1957
|
};
|
|
844
1958
|
};
|