@strapi/content-releases 0.0.0-experimental.f7b9b47085e387e97f990d8695971b51d7f7149a → 0.0.0-experimental.fb442e5e12dd3f611303691bf85a249520ba348b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -1
- package/dist/_chunks/App-DMILern_.mjs +1356 -0
- package/dist/_chunks/App-DMILern_.mjs.map +1 -0
- package/dist/_chunks/App-fAgiijnc.js +1377 -0
- package/dist/_chunks/App-fAgiijnc.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js +52 -0
- package/dist/_chunks/PurchaseContentReleases-Be3acS2L.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs +52 -0
- package/dist/_chunks/PurchaseContentReleases-_MxP6-Dt.mjs.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-YVZJH-oN.js +178 -0
- package/dist/_chunks/ReleasesSettingsPage-YVZJH-oN.js.map +1 -0
- package/dist/_chunks/ReleasesSettingsPage-dwoRuXB-.mjs +178 -0
- package/dist/_chunks/ReleasesSettingsPage-dwoRuXB-.mjs.map +1 -0
- package/dist/_chunks/en-CmYoEnA7.js +93 -0
- package/dist/_chunks/en-CmYoEnA7.js.map +1 -0
- package/dist/_chunks/en-D0yVZFqf.mjs +93 -0
- package/dist/_chunks/en-D0yVZFqf.mjs.map +1 -0
- package/dist/_chunks/index--_NWfuDG.js +1358 -0
- package/dist/_chunks/index--_NWfuDG.js.map +1 -0
- package/dist/_chunks/index-CYsQToWs.mjs +1339 -0
- package/dist/_chunks/index-CYsQToWs.mjs.map +1 -0
- package/dist/_chunks/schemas-63pFihNF.mjs +44 -0
- package/dist/_chunks/schemas-63pFihNF.mjs.map +1 -0
- package/dist/_chunks/schemas-z5zp-_Gd.js +62 -0
- package/dist/_chunks/schemas-z5zp-_Gd.js.map +1 -0
- package/dist/admin/index.js +1 -14
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +2 -15
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseAction.d.ts +3 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionModal.d.ts +24 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseListCell.d.ts +28 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +17 -0
- package/dist/admin/src/components/ReleasesPanel.d.ts +3 -0
- package/dist/admin/src/constants.d.ts +76 -0
- package/dist/admin/src/index.d.ts +3 -0
- package/dist/admin/src/modules/hooks.d.ts +7 -0
- package/dist/admin/src/pages/App.d.ts +1 -0
- package/dist/admin/src/pages/PurchaseContentReleases.d.ts +2 -0
- package/dist/admin/src/pages/ReleaseDetailsPage.d.ts +2 -0
- package/dist/admin/src/pages/ReleasesPage.d.ts +8 -0
- package/dist/admin/src/pages/ReleasesSettingsPage.d.ts +1 -0
- package/dist/admin/src/pages/tests/mockReleaseDetailsPageData.d.ts +181 -0
- package/dist/admin/src/pages/tests/mockReleasesPageData.d.ts +39 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/admin/src/services/release.d.ts +112 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/api.d.ts +6 -0
- package/dist/admin/src/utils/prefixPluginTranslations.d.ts +3 -0
- package/dist/admin/src/utils/time.d.ts +10 -0
- package/dist/admin/src/validation/schemas.d.ts +6 -0
- package/dist/server/index.js +1432 -344
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +1430 -345
- package/dist/server/index.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts +5 -0
- package/dist/server/src/bootstrap.d.ts.map +1 -0
- package/dist/server/src/constants.d.ts +21 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +97 -0
- package/dist/server/src/content-types/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/index.d.ts +48 -0
- package/dist/server/src/content-types/release/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release/schema.d.ts +47 -0
- package/dist/server/src/content-types/release/schema.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/index.d.ts +48 -0
- package/dist/server/src/content-types/release-action/index.d.ts.map +1 -0
- package/dist/server/src/content-types/release-action/schema.d.ts +47 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +25 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +10 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +18 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/settings.d.ts +11 -0
- package/dist/server/src/controllers/settings.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +14 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +4 -0
- package/dist/server/src/controllers/validation/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/settings.d.ts +3 -0
- package/dist/server/src/controllers/validation/settings.d.ts.map +1 -0
- package/dist/server/src/destroy.d.ts +5 -0
- package/dist/server/src/destroy.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +2113 -0
- package/dist/server/src/index.d.ts.map +1 -0
- package/dist/server/src/middlewares/documents.d.ts +6 -0
- package/dist/server/src/middlewares/documents.d.ts.map +1 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts +9 -0
- package/dist/server/src/migrations/database/5.0.0-document-id-in-actions.d.ts.map +1 -0
- package/dist/server/src/migrations/index.d.ts +13 -0
- package/dist/server/src/migrations/index.d.ts.map +1 -0
- package/dist/server/src/register.d.ts +5 -0
- package/dist/server/src/register.d.ts.map +1 -0
- package/dist/server/src/routes/index.d.ts +51 -0
- package/dist/server/src/routes/index.d.ts.map +1 -0
- package/dist/server/src/routes/release-action.d.ts +18 -0
- package/dist/server/src/routes/release-action.d.ts.map +1 -0
- package/dist/server/src/routes/release.d.ts +18 -0
- package/dist/server/src/routes/release.d.ts.map +1 -0
- package/dist/server/src/routes/settings.d.ts +18 -0
- package/dist/server/src/routes/settings.d.ts.map +1 -0
- package/dist/server/src/services/index.d.ts +1826 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release-action.d.ts +36 -0
- package/dist/server/src/services/release-action.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +31 -0
- package/dist/server/src/services/release.d.ts.map +1 -0
- package/dist/server/src/services/scheduling.d.ts +18 -0
- package/dist/server/src/services/scheduling.d.ts.map +1 -0
- package/dist/server/src/services/settings.d.ts +13 -0
- package/dist/server/src/services/settings.d.ts.map +1 -0
- package/dist/server/src/services/validation.d.ts +18 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +35 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +130 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +184 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/contracts/settings.d.ts +39 -0
- package/dist/shared/contracts/settings.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/package.json +33 -34
- package/dist/_chunks/App-a4843fda.mjs +0 -855
- package/dist/_chunks/App-a4843fda.mjs.map +0 -1
- package/dist/_chunks/App-f2cafd81.js +0 -877
- package/dist/_chunks/App-f2cafd81.js.map +0 -1
- package/dist/_chunks/en-13576ce2.js +0 -53
- package/dist/_chunks/en-13576ce2.js.map +0 -1
- package/dist/_chunks/en-e98d8b57.mjs +0 -53
- package/dist/_chunks/en-e98d8b57.mjs.map +0 -1
- package/dist/_chunks/index-66d129ac.js +0 -838
- package/dist/_chunks/index-66d129ac.js.map +0 -1
- package/dist/_chunks/index-937f8179.mjs +0 -817
- package/dist/_chunks/index-937f8179.mjs.map +0 -1
package/dist/server/index.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,477 @@ 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) {
|
|
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}`);
|
|
920
|
+
}
|
|
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
|
|
312
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}`);
|
|
313
952
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
318
|
-
if (release2.releasedAt) {
|
|
319
|
-
throw new errors.ValidationError("Release already published");
|
|
320
|
-
}
|
|
321
|
-
await strapi2.db.transaction(async () => {
|
|
322
|
-
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,
|
|
323
956
|
where: {
|
|
324
|
-
|
|
325
|
-
$in: release2.actions.map((action) => action.id)
|
|
326
|
-
}
|
|
957
|
+
release: releaseId
|
|
327
958
|
}
|
|
328
959
|
});
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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
|
|
977
|
+
};
|
|
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);
|
|
344
988
|
}
|
|
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: []
|
|
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
|
+
}
|
|
363
1003
|
};
|
|
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);
|
|
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);
|
|
377
1012
|
}
|
|
378
|
-
|
|
379
|
-
|
|
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
|
+
}
|
|
380
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
|
+
);
|
|
381
1044
|
}
|
|
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
|
-
|
|
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
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
data: {
|
|
1066
|
+
...update,
|
|
1067
|
+
isEntryValid: actionStatus
|
|
1068
|
+
}
|
|
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
|
+
}
|
|
1082
|
+
}
|
|
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
|
+
);
|
|
414
1089
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
throw new errors.NotFoundError(
|
|
418
|
-
`Action with id ${actionId} not found in release with id ${releaseId}`
|
|
419
|
-
);
|
|
1090
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(releaseId);
|
|
1091
|
+
return deletedAction;
|
|
420
1092
|
}
|
|
421
|
-
|
|
1093
|
+
};
|
|
1094
|
+
};
|
|
1095
|
+
class AlreadyOnReleaseError extends errors.ApplicationError {
|
|
1096
|
+
constructor(message) {
|
|
1097
|
+
super(message);
|
|
1098
|
+
this.name = "AlreadyOnReleaseError";
|
|
422
1099
|
}
|
|
423
|
-
}
|
|
1100
|
+
}
|
|
424
1101
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
425
1102
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
426
|
-
const release2 = await strapi2.
|
|
427
|
-
|
|
1103
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1104
|
+
where: {
|
|
1105
|
+
id: releaseId
|
|
1106
|
+
},
|
|
1107
|
+
populate: {
|
|
1108
|
+
actions: true
|
|
1109
|
+
}
|
|
428
1110
|
});
|
|
429
1111
|
if (!release2) {
|
|
430
1112
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
431
1113
|
}
|
|
432
1114
|
const isEntryInRelease = release2.actions.some(
|
|
433
|
-
(action) =>
|
|
1115
|
+
(action) => action.entryDocumentId === releaseActionArgs.entryDocumentId && action.contentType === releaseActionArgs.contentType && (releaseActionArgs.locale ? action.locale === releaseActionArgs.locale : true)
|
|
434
1116
|
);
|
|
435
1117
|
if (isEntryInRelease) {
|
|
436
|
-
throw new
|
|
437
|
-
`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}`
|
|
438
1120
|
);
|
|
439
1121
|
}
|
|
440
1122
|
},
|
|
441
|
-
|
|
1123
|
+
validateEntryData(contentTypeUid, entryDocumentId) {
|
|
442
1124
|
const contentType = strapi2.contentType(contentTypeUid);
|
|
443
1125
|
if (!contentType) {
|
|
444
1126
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
445
1127
|
}
|
|
446
|
-
if (!contentType
|
|
1128
|
+
if (!contentTypes$1.hasDraftAndPublish(contentType)) {
|
|
447
1129
|
throw new errors.ValidationError(
|
|
448
1130
|
`Content type with uid ${contentTypeUid} does not have draftAndPublish enabled`
|
|
449
1131
|
);
|
|
450
1132
|
}
|
|
1133
|
+
if (contentType.kind === "collectionType" && !entryDocumentId) {
|
|
1134
|
+
throw new errors.ValidationError("Document id is required for collection type");
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
async validatePendingReleasesLimit() {
|
|
1138
|
+
const featureCfg = strapi2.ee.features.get("cms-content-releases");
|
|
1139
|
+
const maximumPendingReleases = typeof featureCfg === "object" && featureCfg?.options?.maximumReleases || 3;
|
|
1140
|
+
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
1141
|
+
filters: {
|
|
1142
|
+
releasedAt: {
|
|
1143
|
+
$null: true
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
1148
|
+
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
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
|
+
}
|
|
451
1170
|
}
|
|
452
1171
|
});
|
|
453
|
-
const
|
|
454
|
-
const
|
|
455
|
-
|
|
1172
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1173
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1174
|
+
return {
|
|
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;
|
|
1192
|
+
},
|
|
1193
|
+
cancel(releaseId) {
|
|
1194
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1195
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1196
|
+
scheduledJobs.delete(releaseId);
|
|
1197
|
+
}
|
|
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
|
+
}
|
|
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
|
+
};
|
|
1242
|
+
}
|
|
1243
|
+
};
|
|
1244
|
+
};
|
|
1245
|
+
const services = {
|
|
1246
|
+
release: createReleaseService,
|
|
1247
|
+
"release-action": createReleaseActionService,
|
|
1248
|
+
"release-validation": createReleaseValidationService,
|
|
1249
|
+
scheduling: createSchedulingService,
|
|
1250
|
+
settings: createSettingsService
|
|
1251
|
+
};
|
|
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()
|
|
456
1266
|
}).required().noUnknown();
|
|
457
1267
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
1268
|
+
const validatefindByDocumentAttachedParams = validateYupSchema(
|
|
1269
|
+
FIND_BY_DOCUMENT_ATTACHED_PARAMS_SCHEMA
|
|
1270
|
+
);
|
|
458
1271
|
const releaseController = {
|
|
459
|
-
|
|
460
|
-
|
|
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({
|
|
461
1279
|
ability: ctx.state.userAbility,
|
|
462
1280
|
model: RELEASE_MODEL_UID
|
|
463
1281
|
});
|
|
464
1282
|
await permissionsManager.validateQuery(ctx.query);
|
|
465
1283
|
const releaseService = getService("release", { strapi });
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
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
|
+
}
|
|
474
1303
|
});
|
|
475
|
-
ctx.body = { data };
|
|
1304
|
+
ctx.body = { data: releases };
|
|
476
1305
|
} else {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const { actions, ...releaseData } = release2;
|
|
481
|
-
return {
|
|
482
|
-
...releaseData,
|
|
1306
|
+
const relatedReleases = await releaseService.findMany({
|
|
1307
|
+
where: {
|
|
1308
|
+
releasedAt: null,
|
|
483
1309
|
actions: {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
1310
|
+
contentType,
|
|
1311
|
+
entryDocumentId: entryDocumentId ?? null,
|
|
1312
|
+
locale: locale ?? null
|
|
487
1313
|
}
|
|
488
|
-
}
|
|
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
|
+
}
|
|
489
1330
|
});
|
|
490
|
-
ctx.body = { data
|
|
1331
|
+
ctx.body = { data: releases };
|
|
491
1332
|
}
|
|
492
1333
|
},
|
|
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({
|
|
1334
|
+
async findPage(ctx) {
|
|
1335
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
498
1336
|
ability: ctx.state.userAbility,
|
|
499
1337
|
model: RELEASE_MODEL_UID
|
|
500
1338
|
});
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
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
|
|
505
1357
|
}
|
|
506
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"] });
|
|
507
1366
|
if (!release2) {
|
|
508
1367
|
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
509
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
|
+
};
|
|
510
1378
|
const data = {
|
|
511
1379
|
...sanitizedRelease,
|
|
512
1380
|
actions: {
|
|
@@ -517,19 +1385,63 @@ const releaseController = {
|
|
|
517
1385
|
};
|
|
518
1386
|
ctx.body = { data };
|
|
519
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
|
+
},
|
|
520
1432
|
async create(ctx) {
|
|
521
1433
|
const user = ctx.state.user;
|
|
522
1434
|
const releaseArgs = ctx.request.body;
|
|
523
1435
|
await validateRelease(releaseArgs);
|
|
524
1436
|
const releaseService = getService("release", { strapi });
|
|
525
1437
|
const release2 = await releaseService.create(releaseArgs, { user });
|
|
526
|
-
const permissionsManager = strapi.admin
|
|
1438
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
527
1439
|
ability: ctx.state.userAbility,
|
|
528
1440
|
model: RELEASE_MODEL_UID
|
|
529
1441
|
});
|
|
530
|
-
ctx.
|
|
1442
|
+
ctx.created({
|
|
531
1443
|
data: await permissionsManager.sanitizeOutput(release2)
|
|
532
|
-
};
|
|
1444
|
+
});
|
|
533
1445
|
},
|
|
534
1446
|
async update(ctx) {
|
|
535
1447
|
const user = ctx.state.user;
|
|
@@ -538,7 +1450,7 @@ const releaseController = {
|
|
|
538
1450
|
await validateRelease(releaseArgs);
|
|
539
1451
|
const releaseService = getService("release", { strapi });
|
|
540
1452
|
const release2 = await releaseService.update(id, releaseArgs, { user });
|
|
541
|
-
const permissionsManager = strapi.admin
|
|
1453
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
542
1454
|
ability: ctx.state.userAbility,
|
|
543
1455
|
model: RELEASE_MODEL_UID
|
|
544
1456
|
});
|
|
@@ -555,73 +1467,136 @@ const releaseController = {
|
|
|
555
1467
|
};
|
|
556
1468
|
},
|
|
557
1469
|
async publish(ctx) {
|
|
558
|
-
const user = ctx.state.user;
|
|
559
1470
|
const id = ctx.params.id;
|
|
560
1471
|
const releaseService = getService("release", { strapi });
|
|
561
|
-
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
|
+
]);
|
|
562
1488
|
ctx.body = {
|
|
563
|
-
data: release2
|
|
1489
|
+
data: release2,
|
|
1490
|
+
meta: {
|
|
1491
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1492
|
+
totalPublishedEntries: countPublishActions,
|
|
1493
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1494
|
+
}
|
|
564
1495
|
};
|
|
565
1496
|
}
|
|
566
1497
|
};
|
|
567
1498
|
const RELEASE_ACTION_SCHEMA = yup$1.object().shape({
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}).required(),
|
|
1499
|
+
contentType: yup$1.string().required(),
|
|
1500
|
+
entryDocumentId: yup$1.strapiID(),
|
|
1501
|
+
locale: yup$1.string(),
|
|
572
1502
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
573
1503
|
});
|
|
574
1504
|
const RELEASE_ACTION_UPDATE_SCHEMA = yup$1.object().shape({
|
|
575
1505
|
type: yup$1.string().oneOf(["publish", "unpublish"]).required()
|
|
576
1506
|
});
|
|
1507
|
+
const FIND_MANY_ACTIONS_PARAMS = yup$1.object().shape({
|
|
1508
|
+
groupBy: yup$1.string().oneOf(["action", "contentType", "locale"])
|
|
1509
|
+
});
|
|
577
1510
|
const validateReleaseAction = validateYupSchema(RELEASE_ACTION_SCHEMA);
|
|
578
1511
|
const validateReleaseActionUpdateSchema = validateYupSchema(RELEASE_ACTION_UPDATE_SCHEMA);
|
|
1512
|
+
const validateFindManyActionsParams = validateYupSchema(FIND_MANY_ACTIONS_PARAMS);
|
|
579
1513
|
const releaseActionController = {
|
|
580
1514
|
async create(ctx) {
|
|
581
1515
|
const releaseId = ctx.params.releaseId;
|
|
582
1516
|
const releaseActionArgs = ctx.request.body;
|
|
583
1517
|
await validateReleaseAction(releaseActionArgs);
|
|
584
|
-
const
|
|
585
|
-
const releaseAction2 = await
|
|
586
|
-
ctx.
|
|
1518
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1519
|
+
const releaseAction2 = await releaseActionService.create(releaseId, releaseActionArgs);
|
|
1520
|
+
ctx.created({
|
|
587
1521
|
data: releaseAction2
|
|
588
|
-
};
|
|
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
|
+
});
|
|
589
1555
|
},
|
|
590
1556
|
async findMany(ctx) {
|
|
591
1557
|
const releaseId = ctx.params.releaseId;
|
|
592
|
-
const permissionsManager = strapi.admin
|
|
1558
|
+
const permissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
593
1559
|
ability: ctx.state.userAbility,
|
|
594
1560
|
model: RELEASE_ACTION_MODEL_UID
|
|
595
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;
|
|
596
1570
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
597
|
-
const
|
|
598
|
-
const { results, pagination } = await
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
)
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
1571
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1572
|
+
const { results, pagination } = await releaseActionService.findPage(releaseId, {
|
|
1573
|
+
...query
|
|
1574
|
+
});
|
|
1575
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1576
|
+
if (acc[action.contentType]) {
|
|
1577
|
+
return acc;
|
|
1578
|
+
}
|
|
1579
|
+
const contentTypePermissionsManager = strapi.service("admin::permission").createPermissionsManager({
|
|
1580
|
+
ability: ctx.state.userAbility,
|
|
1581
|
+
model: action.contentType
|
|
1582
|
+
});
|
|
1583
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
605
1584
|
return acc;
|
|
606
1585
|
}, {});
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
mainFieldValue: action.entry[mainField]
|
|
616
|
-
},
|
|
617
|
-
locale: allLocalesDictionary[action.entry.locale]
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
});
|
|
1586
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1587
|
+
...action,
|
|
1588
|
+
entry: action.entry ? await contentTypeOutputSanitizers[action.contentType](action.entry) : {}
|
|
1589
|
+
}));
|
|
1590
|
+
const groupedData = await releaseActionService.groupActions(sanitizedResults, query.sort);
|
|
1591
|
+
const contentTypes2 = releaseActionService.getContentTypeModelsFromActions(results);
|
|
1592
|
+
const releaseService = getService("release", { strapi });
|
|
1593
|
+
const components = await releaseService.getAllComponents();
|
|
621
1594
|
ctx.body = {
|
|
622
|
-
data,
|
|
1595
|
+
data: groupedData,
|
|
623
1596
|
meta: {
|
|
624
|
-
pagination
|
|
1597
|
+
pagination,
|
|
1598
|
+
contentTypes: contentTypes2,
|
|
1599
|
+
components
|
|
625
1600
|
}
|
|
626
1601
|
};
|
|
627
1602
|
},
|
|
@@ -630,8 +1605,8 @@ const releaseActionController = {
|
|
|
630
1605
|
const releaseId = ctx.params.releaseId;
|
|
631
1606
|
const releaseActionUpdateArgs = ctx.request.body;
|
|
632
1607
|
await validateReleaseActionUpdateSchema(releaseActionUpdateArgs);
|
|
633
|
-
const
|
|
634
|
-
const updatedAction = await
|
|
1608
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1609
|
+
const updatedAction = await releaseActionService.update(
|
|
635
1610
|
actionId,
|
|
636
1611
|
releaseId,
|
|
637
1612
|
releaseActionUpdateArgs
|
|
@@ -643,19 +1618,71 @@ const releaseActionController = {
|
|
|
643
1618
|
async delete(ctx) {
|
|
644
1619
|
const actionId = ctx.params.actionId;
|
|
645
1620
|
const releaseId = ctx.params.releaseId;
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
releaseId
|
|
649
|
-
);
|
|
1621
|
+
const releaseActionService = getService("release-action", { strapi });
|
|
1622
|
+
const deletedReleaseAction = await releaseActionService.delete(actionId, releaseId);
|
|
650
1623
|
ctx.body = {
|
|
651
1624
|
data: deletedReleaseAction
|
|
652
1625
|
};
|
|
653
1626
|
}
|
|
654
1627
|
};
|
|
655
|
-
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
|
+
};
|
|
656
1651
|
const release = {
|
|
657
1652
|
type: "admin",
|
|
658
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
|
+
},
|
|
659
1686
|
{
|
|
660
1687
|
method: "POST",
|
|
661
1688
|
path: "/",
|
|
@@ -675,7 +1702,7 @@ const release = {
|
|
|
675
1702
|
{
|
|
676
1703
|
method: "GET",
|
|
677
1704
|
path: "/",
|
|
678
|
-
handler: "release.
|
|
1705
|
+
handler: "release.findPage",
|
|
679
1706
|
config: {
|
|
680
1707
|
policies: [
|
|
681
1708
|
"admin::isAuthenticatedAdmin",
|
|
@@ -773,6 +1800,22 @@ const releaseAction = {
|
|
|
773
1800
|
]
|
|
774
1801
|
}
|
|
775
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
|
+
},
|
|
776
1819
|
{
|
|
777
1820
|
method: "GET",
|
|
778
1821
|
path: "/:releaseId/actions",
|
|
@@ -823,15 +1866,54 @@ const releaseAction = {
|
|
|
823
1866
|
}
|
|
824
1867
|
]
|
|
825
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
|
+
};
|
|
826
1906
|
const routes = {
|
|
1907
|
+
settings,
|
|
827
1908
|
release,
|
|
828
1909
|
"release-action": releaseAction
|
|
829
1910
|
};
|
|
830
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
831
1911
|
const getPlugin = () => {
|
|
832
|
-
if (features.isEnabled("cms-content-releases")
|
|
1912
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
833
1913
|
return {
|
|
834
1914
|
register,
|
|
1915
|
+
bootstrap,
|
|
1916
|
+
destroy,
|
|
835
1917
|
contentTypes,
|
|
836
1918
|
services,
|
|
837
1919
|
controllers,
|
|
@@ -839,6 +1921,9 @@ const getPlugin = () => {
|
|
|
839
1921
|
};
|
|
840
1922
|
}
|
|
841
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
|
|
842
1927
|
contentTypes
|
|
843
1928
|
};
|
|
844
1929
|
};
|