@strapi/content-releases 0.0.0-experimental.e1ede8c55a0e1e22ce20137bf238fc374bd5dd51 → 0.0.0-experimental.e3e48deb89bd0a1b6cc69b698696566fa7854a95
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/dist/_chunks/App-PQlYzNfb.mjs +1337 -0
- package/dist/_chunks/App-PQlYzNfb.mjs.map +1 -0
- package/dist/_chunks/App-lzeJz92X.js +1360 -0
- package/dist/_chunks/App-lzeJz92X.js.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs +51 -0
- package/dist/_chunks/PurchaseContentReleases-Clm0iACO.mjs.map +1 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js +51 -0
- package/dist/_chunks/PurchaseContentReleases-YhAPgpG9.js.map +1 -0
- package/dist/_chunks/{en-ngTk74JV.mjs → en-WuuhP6Bn.mjs} +22 -6
- package/dist/_chunks/en-WuuhP6Bn.mjs.map +1 -0
- package/dist/_chunks/{en-haKSQIo8.js → en-gcJJ5htG.js} +22 -6
- package/dist/_chunks/en-gcJJ5htG.js.map +1 -0
- package/dist/_chunks/{index-XAQOX_IB.mjs → index--4AgLDzb.mjs} +262 -83
- package/dist/_chunks/index--4AgLDzb.mjs.map +1 -0
- package/dist/_chunks/{index-EdBmRHRU.js → index-Nf1JfI-m.js} +246 -67
- package/dist/_chunks/index-Nf1JfI-m.js.map +1 -0
- package/dist/admin/index.js +2 -1
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +3 -2
- package/dist/admin/index.mjs.map +1 -1
- package/dist/admin/src/components/CMReleasesContainer.d.ts +1 -0
- package/dist/admin/src/components/RelativeTime.d.ts +28 -0
- package/dist/admin/src/components/ReleaseActionMenu.d.ts +26 -0
- package/dist/admin/src/components/ReleaseActionOptions.d.ts +9 -0
- package/dist/admin/src/components/ReleaseModal.d.ts +16 -0
- package/dist/admin/src/constants.d.ts +58 -0
- package/dist/admin/src/index.d.ts +3 -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/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/axios.d.ts +29 -0
- package/dist/admin/src/services/release.d.ts +369 -0
- package/dist/admin/src/store/hooks.d.ts +7 -0
- package/dist/admin/src/utils/time.d.ts +1 -0
- package/dist/server/index.js +999 -309
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +999 -310
- 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 +12 -0
- package/dist/server/src/constants.d.ts.map +1 -0
- package/dist/server/src/content-types/index.d.ts +99 -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 +50 -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 +49 -0
- package/dist/server/src/content-types/release-action/schema.d.ts.map +1 -0
- package/dist/server/src/controllers/index.d.ts +18 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -0
- package/dist/server/src/controllers/release-action.d.ts +9 -0
- package/dist/server/src/controllers/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/release.d.ts +11 -0
- package/dist/server/src/controllers/release.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release-action.d.ts +3 -0
- package/dist/server/src/controllers/validation/release-action.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/release.d.ts +2 -0
- package/dist/server/src/controllers/validation/release.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 +2092 -0
- package/dist/server/src/index.d.ts.map +1 -0
- package/dist/server/src/migrations/index.d.ts +10 -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 +35 -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/services/index.d.ts +1826 -0
- package/dist/server/src/services/index.d.ts.map +1 -0
- package/dist/server/src/services/release.d.ts +66 -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/validation.d.ts +14 -0
- package/dist/server/src/services/validation.d.ts.map +1 -0
- package/dist/server/src/utils/index.d.ts +14 -0
- package/dist/server/src/utils/index.d.ts.map +1 -0
- package/dist/shared/contracts/release-actions.d.ts +105 -0
- package/dist/shared/contracts/release-actions.d.ts.map +1 -0
- package/dist/shared/contracts/releases.d.ts +166 -0
- package/dist/shared/contracts/releases.d.ts.map +1 -0
- package/dist/shared/types.d.ts +24 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/validation-schemas.d.ts +2 -0
- package/dist/shared/validation-schemas.d.ts.map +1 -0
- package/package.json +19 -22
- package/dist/_chunks/App-g2P5kbSm.mjs +0 -945
- package/dist/_chunks/App-g2P5kbSm.mjs.map +0 -1
- package/dist/_chunks/App-o5_WfqR-.js +0 -967
- package/dist/_chunks/App-o5_WfqR-.js.map +0 -1
- package/dist/_chunks/en-haKSQIo8.js.map +0 -1
- package/dist/_chunks/en-ngTk74JV.mjs.map +0 -1
- package/dist/_chunks/index-EdBmRHRU.js.map +0 -1
- package/dist/_chunks/index-XAQOX_IB.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { setCreatorFields, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
1
|
+
import { async, setCreatorFields, convertQueryParams, errors, validateYupSchema, yup as yup$1 } from "@strapi/utils";
|
|
2
|
+
import isEqual from "lodash/isEqual";
|
|
3
|
+
import { difference, keys } from "lodash";
|
|
2
4
|
import _ from "lodash/fp";
|
|
5
|
+
import { scheduleJob } from "node-schedule";
|
|
3
6
|
import * as yup from "yup";
|
|
4
7
|
const RELEASE_MODEL_UID = "plugin::content-releases.release";
|
|
5
8
|
const RELEASE_ACTION_MODEL_UID = "plugin::content-releases.release-action";
|
|
@@ -47,10 +50,292 @@ const ACTIONS = [
|
|
|
47
50
|
pluginName: "content-releases"
|
|
48
51
|
}
|
|
49
52
|
];
|
|
50
|
-
const
|
|
53
|
+
const ALLOWED_WEBHOOK_EVENTS = {
|
|
54
|
+
RELEASES_PUBLISH: "releases.publish"
|
|
55
|
+
};
|
|
56
|
+
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
57
|
+
return strapi2.plugin("content-releases").service(name);
|
|
58
|
+
};
|
|
59
|
+
const getPopulatedEntry = async (contentTypeUid, entryId, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
60
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
61
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
62
|
+
const entry = await strapi2.db.query(contentTypeUid).findOne({
|
|
63
|
+
where: { id: entryId },
|
|
64
|
+
populate
|
|
65
|
+
});
|
|
66
|
+
return entry;
|
|
67
|
+
};
|
|
68
|
+
const getEntryValidStatus = async (contentTypeUid, entry, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
69
|
+
try {
|
|
70
|
+
await strapi2.entityValidator.validateEntityCreation(
|
|
71
|
+
strapi2.getModel(contentTypeUid),
|
|
72
|
+
entry,
|
|
73
|
+
void 0,
|
|
74
|
+
// @ts-expect-error - FIXME: entity here is unnecessary
|
|
75
|
+
entry
|
|
76
|
+
);
|
|
77
|
+
return true;
|
|
78
|
+
} catch {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
async function deleteActionsOnDeleteContentType({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
83
|
+
const deletedContentTypes = difference(keys(oldContentTypes), keys(contentTypes2)) ?? [];
|
|
84
|
+
if (deletedContentTypes.length) {
|
|
85
|
+
await async.map(deletedContentTypes, async (deletedContentTypeUID) => {
|
|
86
|
+
return strapi.db?.queryBuilder(RELEASE_ACTION_MODEL_UID).delete().where({ contentType: deletedContentTypeUID }).execute();
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function migrateIsValidAndStatusReleases() {
|
|
91
|
+
const releasesWithoutStatus = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
92
|
+
where: {
|
|
93
|
+
status: null,
|
|
94
|
+
releasedAt: null
|
|
95
|
+
},
|
|
96
|
+
populate: {
|
|
97
|
+
actions: {
|
|
98
|
+
populate: {
|
|
99
|
+
entry: true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
async.map(releasesWithoutStatus, async (release2) => {
|
|
105
|
+
const actions = release2.actions;
|
|
106
|
+
const notValidatedActions = actions.filter((action) => action.isEntryValid === null);
|
|
107
|
+
for (const action of notValidatedActions) {
|
|
108
|
+
if (action.entry) {
|
|
109
|
+
const populatedEntry = await getPopulatedEntry(action.contentType, action.entry.id, {
|
|
110
|
+
strapi
|
|
111
|
+
});
|
|
112
|
+
if (populatedEntry) {
|
|
113
|
+
const isEntryValid = getEntryValidStatus(action.contentType, populatedEntry, { strapi });
|
|
114
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
115
|
+
where: {
|
|
116
|
+
id: action.id
|
|
117
|
+
},
|
|
118
|
+
data: {
|
|
119
|
+
isEntryValid
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return getService("release", { strapi }).updateReleaseStatus(release2.id);
|
|
126
|
+
});
|
|
127
|
+
const publishedReleases = await strapi.db.query(RELEASE_MODEL_UID).findMany({
|
|
128
|
+
where: {
|
|
129
|
+
status: null,
|
|
130
|
+
releasedAt: {
|
|
131
|
+
$notNull: true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
async.map(publishedReleases, async (release2) => {
|
|
136
|
+
return strapi.db.query(RELEASE_MODEL_UID).update({
|
|
137
|
+
where: {
|
|
138
|
+
id: release2.id
|
|
139
|
+
},
|
|
140
|
+
data: {
|
|
141
|
+
status: "done"
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
async function revalidateChangedContentTypes({ oldContentTypes, contentTypes: contentTypes2 }) {
|
|
147
|
+
if (oldContentTypes !== void 0 && contentTypes2 !== void 0) {
|
|
148
|
+
const contentTypesWithDraftAndPublish = Object.keys(oldContentTypes);
|
|
149
|
+
const releasesAffected = /* @__PURE__ */ new Set();
|
|
150
|
+
async.map(contentTypesWithDraftAndPublish, async (contentTypeUID) => {
|
|
151
|
+
const oldContentType = oldContentTypes[contentTypeUID];
|
|
152
|
+
const contentType = contentTypes2[contentTypeUID];
|
|
153
|
+
if (!isEqual(oldContentType?.attributes, contentType?.attributes)) {
|
|
154
|
+
const actions = await strapi.db.query(RELEASE_ACTION_MODEL_UID).findMany({
|
|
155
|
+
where: {
|
|
156
|
+
contentType: contentTypeUID
|
|
157
|
+
},
|
|
158
|
+
populate: {
|
|
159
|
+
entry: true,
|
|
160
|
+
release: true
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
await async.map(actions, async (action) => {
|
|
164
|
+
if (action.entry) {
|
|
165
|
+
const populatedEntry = await getPopulatedEntry(contentTypeUID, action.entry.id, {
|
|
166
|
+
strapi
|
|
167
|
+
});
|
|
168
|
+
if (populatedEntry) {
|
|
169
|
+
const isEntryValid = await getEntryValidStatus(contentTypeUID, populatedEntry, {
|
|
170
|
+
strapi
|
|
171
|
+
});
|
|
172
|
+
releasesAffected.add(action.release.id);
|
|
173
|
+
await strapi.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
174
|
+
where: {
|
|
175
|
+
id: action.id
|
|
176
|
+
},
|
|
177
|
+
data: {
|
|
178
|
+
isEntryValid
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}).then(() => {
|
|
186
|
+
async.map(releasesAffected, async (releaseId) => {
|
|
187
|
+
return getService("release", { strapi }).updateReleaseStatus(releaseId);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
51
192
|
const register = async ({ strapi: strapi2 }) => {
|
|
52
|
-
if (features
|
|
193
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
53
194
|
await strapi2.admin.services.permission.actionProvider.registerMany(ACTIONS);
|
|
195
|
+
strapi2.hook("strapi::content-types.afterSync").register(deleteActionsOnDeleteContentType).register(revalidateChangedContentTypes).register(migrateIsValidAndStatusReleases);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
const bootstrap = async ({ strapi: strapi2 }) => {
|
|
199
|
+
if (strapi2.ee.features.isEnabled("cms-content-releases")) {
|
|
200
|
+
const contentTypesWithDraftAndPublish = Object.keys(strapi2.contentTypes);
|
|
201
|
+
strapi2.db.lifecycles.subscribe({
|
|
202
|
+
models: contentTypesWithDraftAndPublish,
|
|
203
|
+
async afterDelete(event) {
|
|
204
|
+
try {
|
|
205
|
+
const { model, result } = event;
|
|
206
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
207
|
+
const { id } = result;
|
|
208
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
209
|
+
where: {
|
|
210
|
+
actions: {
|
|
211
|
+
target_type: model.uid,
|
|
212
|
+
target_id: id
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
217
|
+
where: {
|
|
218
|
+
target_type: model.uid,
|
|
219
|
+
target_id: id
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
for (const release2 of releases) {
|
|
223
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
strapi2.log.error("Error while deleting release actions after entry delete", { error });
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
/**
|
|
231
|
+
* deleteMany hook doesn't return the deleted entries ids
|
|
232
|
+
* so we need to fetch them before deleting the entries to save the ids on our state
|
|
233
|
+
*/
|
|
234
|
+
async beforeDeleteMany(event) {
|
|
235
|
+
const { model, params } = event;
|
|
236
|
+
if (model.kind === "collectionType") {
|
|
237
|
+
const { where } = params;
|
|
238
|
+
const entriesToDelete = await strapi2.db.query(model.uid).findMany({ select: ["id"], where });
|
|
239
|
+
event.state.entriesToDelete = entriesToDelete;
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
/**
|
|
243
|
+
* We delete the release actions related to deleted entries
|
|
244
|
+
* We make this only after deleteMany is succesfully executed to avoid errors
|
|
245
|
+
*/
|
|
246
|
+
async afterDeleteMany(event) {
|
|
247
|
+
try {
|
|
248
|
+
const { model, state } = event;
|
|
249
|
+
const entriesToDelete = state.entriesToDelete;
|
|
250
|
+
if (entriesToDelete) {
|
|
251
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
252
|
+
where: {
|
|
253
|
+
actions: {
|
|
254
|
+
target_type: model.uid,
|
|
255
|
+
target_id: {
|
|
256
|
+
$in: entriesToDelete.map(
|
|
257
|
+
(entry) => entry.id
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
264
|
+
where: {
|
|
265
|
+
target_type: model.uid,
|
|
266
|
+
target_id: {
|
|
267
|
+
$in: entriesToDelete.map((entry) => entry.id)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
for (const release2 of releases) {
|
|
272
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
} catch (error) {
|
|
276
|
+
strapi2.log.error("Error while deleting release actions after entry deleteMany", {
|
|
277
|
+
error
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
async afterUpdate(event) {
|
|
282
|
+
try {
|
|
283
|
+
const { model, result } = event;
|
|
284
|
+
if (model.kind === "collectionType" && model.options?.draftAndPublish) {
|
|
285
|
+
const isEntryValid = await getEntryValidStatus(
|
|
286
|
+
model.uid,
|
|
287
|
+
result,
|
|
288
|
+
{
|
|
289
|
+
strapi: strapi2
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
293
|
+
where: {
|
|
294
|
+
target_type: model.uid,
|
|
295
|
+
target_id: result.id
|
|
296
|
+
},
|
|
297
|
+
data: {
|
|
298
|
+
isEntryValid
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
302
|
+
where: {
|
|
303
|
+
actions: {
|
|
304
|
+
target_type: model.uid,
|
|
305
|
+
target_id: result.id
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
for (const release2 of releases) {
|
|
310
|
+
getService("release", { strapi: strapi2 }).updateReleaseStatus(release2.id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
strapi2.log.error("Error while updating release actions after entry update", { error });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
319
|
+
getService("scheduling", { strapi: strapi2 }).syncFromDatabase().catch((err) => {
|
|
320
|
+
strapi2.log.error(
|
|
321
|
+
"Error while syncing scheduled jobs from the database in the content-releases plugin. This could lead to errors in the releases scheduling."
|
|
322
|
+
);
|
|
323
|
+
throw err;
|
|
324
|
+
});
|
|
325
|
+
Object.entries(ALLOWED_WEBHOOK_EVENTS).forEach(([key, value]) => {
|
|
326
|
+
strapi2.webhookStore.addAllowedEvent(key, value);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
const destroy = async ({ strapi: strapi2 }) => {
|
|
332
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
333
|
+
const scheduledJobs = getService("scheduling", {
|
|
334
|
+
strapi: strapi2
|
|
335
|
+
}).getAll();
|
|
336
|
+
for (const [, job] of scheduledJobs) {
|
|
337
|
+
job.cancel();
|
|
338
|
+
}
|
|
54
339
|
}
|
|
55
340
|
};
|
|
56
341
|
const schema$1 = {
|
|
@@ -79,6 +364,17 @@ const schema$1 = {
|
|
|
79
364
|
releasedAt: {
|
|
80
365
|
type: "datetime"
|
|
81
366
|
},
|
|
367
|
+
scheduledAt: {
|
|
368
|
+
type: "datetime"
|
|
369
|
+
},
|
|
370
|
+
timezone: {
|
|
371
|
+
type: "string"
|
|
372
|
+
},
|
|
373
|
+
status: {
|
|
374
|
+
type: "enumeration",
|
|
375
|
+
enum: ["ready", "blocked", "failed", "done", "empty"],
|
|
376
|
+
required: true
|
|
377
|
+
},
|
|
82
378
|
actions: {
|
|
83
379
|
type: "relation",
|
|
84
380
|
relation: "oneToMany",
|
|
@@ -131,6 +427,9 @@ const schema = {
|
|
|
131
427
|
relation: "manyToOne",
|
|
132
428
|
target: RELEASE_MODEL_UID,
|
|
133
429
|
inversedBy: "actions"
|
|
430
|
+
},
|
|
431
|
+
isEntryValid: {
|
|
432
|
+
type: "boolean"
|
|
134
433
|
}
|
|
135
434
|
}
|
|
136
435
|
};
|
|
@@ -141,331 +440,581 @@ const contentTypes = {
|
|
|
141
440
|
release: release$1,
|
|
142
441
|
"release-action": releaseAction$1
|
|
143
442
|
};
|
|
144
|
-
const getService = (name, { strapi: strapi2 } = { strapi: global.strapi }) => {
|
|
145
|
-
return strapi2.plugin("content-releases").service(name);
|
|
146
|
-
};
|
|
147
443
|
const getGroupName = (queryValue) => {
|
|
148
444
|
switch (queryValue) {
|
|
149
445
|
case "contentType":
|
|
150
|
-
return "
|
|
446
|
+
return "contentType.displayName";
|
|
151
447
|
case "action":
|
|
152
448
|
return "type";
|
|
153
449
|
case "locale":
|
|
154
|
-
return _.getOr("No locale", "
|
|
450
|
+
return _.getOr("No locale", "locale.name");
|
|
155
451
|
default:
|
|
156
|
-
return "
|
|
452
|
+
return "contentType.displayName";
|
|
157
453
|
}
|
|
158
454
|
};
|
|
159
|
-
const createReleaseService = ({ strapi: strapi2 }) =>
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
455
|
+
const createReleaseService = ({ strapi: strapi2 }) => {
|
|
456
|
+
const dispatchWebhook = (event, { isPublished, release: release2, error }) => {
|
|
457
|
+
strapi2.eventHub.emit(event, {
|
|
458
|
+
isPublished,
|
|
459
|
+
error,
|
|
460
|
+
release: release2
|
|
164
461
|
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
462
|
+
};
|
|
463
|
+
return {
|
|
464
|
+
async create(releaseData, { user }) {
|
|
465
|
+
const releaseWithCreatorFields = await setCreatorFields({ user })(releaseData);
|
|
466
|
+
const {
|
|
467
|
+
validatePendingReleasesLimit,
|
|
468
|
+
validateUniqueNameForPendingRelease,
|
|
469
|
+
validateScheduledAtIsLaterThanNow
|
|
470
|
+
} = getService("release-validation", { strapi: strapi2 });
|
|
471
|
+
await Promise.all([
|
|
472
|
+
validatePendingReleasesLimit(),
|
|
473
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name),
|
|
474
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
475
|
+
]);
|
|
476
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).create({
|
|
477
|
+
data: {
|
|
478
|
+
...releaseWithCreatorFields,
|
|
479
|
+
status: "empty"
|
|
179
480
|
}
|
|
481
|
+
});
|
|
482
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && releaseWithCreatorFields.scheduledAt) {
|
|
483
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
484
|
+
await schedulingService.set(release2.id, release2.scheduledAt);
|
|
180
485
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
486
|
+
strapi2.telemetry.send("didCreateContentRelease");
|
|
487
|
+
return release2;
|
|
488
|
+
},
|
|
489
|
+
async findOne(id, query = {}) {
|
|
490
|
+
const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query);
|
|
491
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
492
|
+
...dbQuery,
|
|
493
|
+
where: { id }
|
|
494
|
+
});
|
|
495
|
+
return release2;
|
|
496
|
+
},
|
|
497
|
+
findPage(query) {
|
|
498
|
+
const dbQuery = convertQueryParams.transformParamsToQuery(RELEASE_MODEL_UID, query ?? {});
|
|
499
|
+
return strapi2.db.query(RELEASE_MODEL_UID).findPage({
|
|
500
|
+
...dbQuery,
|
|
501
|
+
populate: {
|
|
502
|
+
actions: {
|
|
503
|
+
count: true
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
});
|
|
507
|
+
},
|
|
508
|
+
async findManyWithContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
509
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
510
|
+
where: {
|
|
511
|
+
actions: {
|
|
512
|
+
target_type: contentTypeUid,
|
|
513
|
+
target_id: entryId
|
|
514
|
+
},
|
|
515
|
+
releasedAt: {
|
|
516
|
+
$null: true
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
populate: {
|
|
520
|
+
// Filter the action to get only the content type entry
|
|
521
|
+
actions: {
|
|
522
|
+
where: {
|
|
200
523
|
target_type: contentTypeUid,
|
|
201
524
|
target_id: entryId
|
|
202
525
|
}
|
|
203
526
|
}
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
actions: null
|
|
207
527
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
528
|
+
});
|
|
529
|
+
return releases.map((release2) => {
|
|
530
|
+
if (release2.actions?.length) {
|
|
531
|
+
const [actionForEntry] = release2.actions;
|
|
532
|
+
delete release2.actions;
|
|
533
|
+
return {
|
|
534
|
+
...release2,
|
|
535
|
+
action: actionForEntry
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
return release2;
|
|
539
|
+
});
|
|
540
|
+
},
|
|
541
|
+
async findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId) {
|
|
542
|
+
const releasesRelated = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
213
543
|
where: {
|
|
214
|
-
|
|
215
|
-
|
|
544
|
+
releasedAt: {
|
|
545
|
+
$null: true
|
|
546
|
+
},
|
|
547
|
+
actions: {
|
|
548
|
+
target_type: contentTypeUid,
|
|
549
|
+
target_id: entryId
|
|
550
|
+
}
|
|
216
551
|
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
552
|
+
});
|
|
553
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
554
|
+
where: {
|
|
555
|
+
$or: [
|
|
556
|
+
{
|
|
557
|
+
id: {
|
|
558
|
+
$notIn: releasesRelated.map((release2) => release2.id)
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
actions: null
|
|
563
|
+
}
|
|
564
|
+
],
|
|
565
|
+
releasedAt: {
|
|
566
|
+
$null: true
|
|
567
|
+
}
|
|
224
568
|
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
|
|
569
|
+
});
|
|
570
|
+
return releases.map((release2) => {
|
|
571
|
+
if (release2.actions?.length) {
|
|
572
|
+
const [actionForEntry] = release2.actions;
|
|
573
|
+
delete release2.actions;
|
|
574
|
+
return {
|
|
575
|
+
...release2,
|
|
576
|
+
action: actionForEntry
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
return release2;
|
|
580
|
+
});
|
|
581
|
+
},
|
|
582
|
+
async update(id, releaseData, { user }) {
|
|
583
|
+
const releaseWithCreatorFields = await setCreatorFields({ user, isEdition: true })(
|
|
584
|
+
releaseData
|
|
585
|
+
);
|
|
586
|
+
const { validateUniqueNameForPendingRelease, validateScheduledAtIsLaterThanNow } = getService(
|
|
587
|
+
"release-validation",
|
|
588
|
+
{ strapi: strapi2 }
|
|
589
|
+
);
|
|
590
|
+
await Promise.all([
|
|
591
|
+
validateUniqueNameForPendingRelease(releaseWithCreatorFields.name, id),
|
|
592
|
+
validateScheduledAtIsLaterThanNow(releaseWithCreatorFields.scheduledAt)
|
|
593
|
+
]);
|
|
594
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id } });
|
|
595
|
+
if (!release2) {
|
|
596
|
+
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
228
597
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (release2.actions?.length) {
|
|
232
|
-
const [actionForEntry] = release2.actions;
|
|
233
|
-
delete release2.actions;
|
|
234
|
-
return {
|
|
235
|
-
...release2,
|
|
236
|
-
action: actionForEntry
|
|
237
|
-
};
|
|
598
|
+
if (release2.releasedAt) {
|
|
599
|
+
throw new errors.ValidationError("Release already published");
|
|
238
600
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
data: updatedRelease
|
|
251
|
-
});
|
|
252
|
-
if (!release2) {
|
|
253
|
-
throw new errors.NotFoundError(`No release found for id ${id}`);
|
|
254
|
-
}
|
|
255
|
-
return release2;
|
|
256
|
-
},
|
|
257
|
-
async createAction(releaseId, action) {
|
|
258
|
-
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
259
|
-
strapi: strapi2
|
|
260
|
-
});
|
|
261
|
-
await Promise.all([
|
|
262
|
-
validateEntryContentType(action.entry.contentType),
|
|
263
|
-
validateUniqueEntry(releaseId, action)
|
|
264
|
-
]);
|
|
265
|
-
const { entry, type } = action;
|
|
266
|
-
return strapi2.entityService.create(RELEASE_ACTION_MODEL_UID, {
|
|
267
|
-
data: {
|
|
268
|
-
type,
|
|
269
|
-
contentType: entry.contentType,
|
|
270
|
-
locale: entry.locale,
|
|
271
|
-
entry: {
|
|
272
|
-
id: entry.id,
|
|
273
|
-
__type: entry.contentType,
|
|
274
|
-
__pivot: { field: "entry" }
|
|
275
|
-
},
|
|
276
|
-
release: releaseId
|
|
277
|
-
},
|
|
278
|
-
populate: { release: { fields: ["id"] }, entry: { fields: ["id"] } }
|
|
279
|
-
});
|
|
280
|
-
},
|
|
281
|
-
async findActions(releaseId, query) {
|
|
282
|
-
const release2 = await strapi2.entityService.findOne(RELEASE_MODEL_UID, releaseId, {
|
|
283
|
-
fields: ["id"]
|
|
284
|
-
});
|
|
285
|
-
if (!release2) {
|
|
286
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
287
|
-
}
|
|
288
|
-
return strapi2.entityService.findPage(RELEASE_ACTION_MODEL_UID, {
|
|
289
|
-
...query,
|
|
290
|
-
populate: {
|
|
291
|
-
entry: true
|
|
292
|
-
},
|
|
293
|
-
filters: {
|
|
294
|
-
release: releaseId
|
|
601
|
+
const updatedRelease = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
602
|
+
where: { id },
|
|
603
|
+
data: releaseWithCreatorFields
|
|
604
|
+
});
|
|
605
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
606
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
607
|
+
if (releaseData.scheduledAt) {
|
|
608
|
+
await schedulingService.set(id, releaseData.scheduledAt);
|
|
609
|
+
} else if (release2.scheduledAt) {
|
|
610
|
+
schedulingService.cancel(id);
|
|
611
|
+
}
|
|
295
612
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
613
|
+
this.updateReleaseStatus(id);
|
|
614
|
+
strapi2.telemetry.send("didUpdateContentRelease");
|
|
615
|
+
return updatedRelease;
|
|
616
|
+
},
|
|
617
|
+
async createAction(releaseId, action) {
|
|
618
|
+
const { validateEntryContentType, validateUniqueEntry } = getService("release-validation", {
|
|
619
|
+
strapi: strapi2
|
|
620
|
+
});
|
|
621
|
+
await Promise.all([
|
|
622
|
+
validateEntryContentType(action.entry.contentType),
|
|
623
|
+
validateUniqueEntry(releaseId, action)
|
|
624
|
+
]);
|
|
625
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId } });
|
|
626
|
+
if (!release2) {
|
|
627
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
305
628
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
contentType: {
|
|
323
|
-
displayName,
|
|
324
|
-
mainFieldValue: action.entry[mainField]
|
|
629
|
+
if (release2.releasedAt) {
|
|
630
|
+
throw new errors.ValidationError("Release already published");
|
|
631
|
+
}
|
|
632
|
+
const { entry, type } = action;
|
|
633
|
+
const populatedEntry = await getPopulatedEntry(entry.contentType, entry.id, { strapi: strapi2 });
|
|
634
|
+
const isEntryValid = await getEntryValidStatus(entry.contentType, populatedEntry, { strapi: strapi2 });
|
|
635
|
+
const releaseAction2 = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).create({
|
|
636
|
+
data: {
|
|
637
|
+
type,
|
|
638
|
+
contentType: entry.contentType,
|
|
639
|
+
locale: entry.locale,
|
|
640
|
+
isEntryValid,
|
|
641
|
+
entry: {
|
|
642
|
+
id: entry.id,
|
|
643
|
+
__type: entry.contentType,
|
|
644
|
+
__pivot: { field: "entry" }
|
|
325
645
|
},
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
};
|
|
330
|
-
});
|
|
331
|
-
const groupName = getGroupName(groupBy);
|
|
332
|
-
return _.groupBy(groupName)(formattedData);
|
|
333
|
-
},
|
|
334
|
-
async getContentTypesDataForActions(contentTypesUids) {
|
|
335
|
-
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
336
|
-
const contentTypesData = {};
|
|
337
|
-
for (const contentTypeUid of contentTypesUids) {
|
|
338
|
-
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
339
|
-
uid: contentTypeUid
|
|
646
|
+
release: releaseId
|
|
647
|
+
},
|
|
648
|
+
populate: { release: { select: ["id"] }, entry: { select: ["id"] } }
|
|
340
649
|
});
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
actions: {
|
|
352
|
-
fields: ["id"]
|
|
353
|
-
}
|
|
650
|
+
this.updateReleaseStatus(releaseId);
|
|
651
|
+
return releaseAction2;
|
|
652
|
+
},
|
|
653
|
+
async findActions(releaseId, query) {
|
|
654
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
655
|
+
where: { id: releaseId },
|
|
656
|
+
select: ["id"]
|
|
657
|
+
});
|
|
658
|
+
if (!release2) {
|
|
659
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
354
660
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
where: {
|
|
365
|
-
id: {
|
|
366
|
-
$in: release2.actions.map((action) => action.id)
|
|
661
|
+
const dbQuery = convertQueryParams.transformParamsToQuery(
|
|
662
|
+
RELEASE_ACTION_MODEL_UID,
|
|
663
|
+
query ?? {}
|
|
664
|
+
);
|
|
665
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).findPage({
|
|
666
|
+
...dbQuery,
|
|
667
|
+
populate: {
|
|
668
|
+
entry: {
|
|
669
|
+
populate: "*"
|
|
367
670
|
}
|
|
671
|
+
},
|
|
672
|
+
where: {
|
|
673
|
+
release: releaseId
|
|
368
674
|
}
|
|
369
675
|
});
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
676
|
+
},
|
|
677
|
+
async countActions(query) {
|
|
678
|
+
const dbQuery = convertQueryParams.transformParamsToQuery(
|
|
679
|
+
RELEASE_ACTION_MODEL_UID,
|
|
680
|
+
query ?? {}
|
|
681
|
+
);
|
|
682
|
+
return strapi2.db.query(RELEASE_ACTION_MODEL_UID).count(dbQuery);
|
|
683
|
+
},
|
|
684
|
+
async groupActions(actions, groupBy) {
|
|
685
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
686
|
+
if (!acc.includes(action.contentType)) {
|
|
687
|
+
acc.push(action.contentType);
|
|
688
|
+
}
|
|
689
|
+
return acc;
|
|
690
|
+
}, []);
|
|
691
|
+
const allReleaseContentTypesDictionary = await this.getContentTypesDataForActions(
|
|
692
|
+
contentTypeUids
|
|
693
|
+
);
|
|
694
|
+
const allLocalesDictionary = await this.getLocalesDataForActions();
|
|
695
|
+
const formattedData = actions.map((action) => {
|
|
696
|
+
const { mainField, displayName } = allReleaseContentTypesDictionary[action.contentType];
|
|
697
|
+
return {
|
|
698
|
+
...action,
|
|
699
|
+
locale: action.locale ? allLocalesDictionary[action.locale] : null,
|
|
700
|
+
contentType: {
|
|
701
|
+
displayName,
|
|
702
|
+
mainFieldValue: action.entry[mainField],
|
|
703
|
+
uid: action.contentType
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
});
|
|
707
|
+
const groupName = getGroupName(groupBy);
|
|
708
|
+
return _.groupBy(groupName)(formattedData);
|
|
709
|
+
},
|
|
710
|
+
async getLocalesDataForActions() {
|
|
711
|
+
if (!strapi2.plugin("i18n")) {
|
|
712
|
+
return {};
|
|
713
|
+
}
|
|
714
|
+
const allLocales = await strapi2.plugin("i18n").service("locales").find() || [];
|
|
715
|
+
return allLocales.reduce((acc, locale) => {
|
|
716
|
+
acc[locale.code] = { name: locale.name, code: locale.code };
|
|
717
|
+
return acc;
|
|
718
|
+
}, {});
|
|
719
|
+
},
|
|
720
|
+
async getContentTypesDataForActions(contentTypesUids) {
|
|
721
|
+
const contentManagerContentTypeService = strapi2.plugin("content-manager").service("content-types");
|
|
722
|
+
const contentTypesData = {};
|
|
723
|
+
for (const contentTypeUid of contentTypesUids) {
|
|
724
|
+
const contentTypeConfig = await contentManagerContentTypeService.findConfiguration({
|
|
725
|
+
uid: contentTypeUid
|
|
726
|
+
});
|
|
727
|
+
contentTypesData[contentTypeUid] = {
|
|
728
|
+
mainField: contentTypeConfig.settings.mainField,
|
|
729
|
+
displayName: strapi2.getModel(contentTypeUid).info.displayName
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
return contentTypesData;
|
|
733
|
+
},
|
|
734
|
+
getContentTypeModelsFromActions(actions) {
|
|
735
|
+
const contentTypeUids = actions.reduce((acc, action) => {
|
|
736
|
+
if (!acc.includes(action.contentType)) {
|
|
737
|
+
acc.push(action.contentType);
|
|
738
|
+
}
|
|
739
|
+
return acc;
|
|
740
|
+
}, []);
|
|
741
|
+
const contentTypeModelsMap = contentTypeUids.reduce(
|
|
742
|
+
(acc, contentTypeUid) => {
|
|
743
|
+
acc[contentTypeUid] = strapi2.getModel(contentTypeUid);
|
|
744
|
+
return acc;
|
|
745
|
+
},
|
|
746
|
+
{}
|
|
747
|
+
);
|
|
748
|
+
return contentTypeModelsMap;
|
|
749
|
+
},
|
|
750
|
+
async getAllComponents() {
|
|
751
|
+
const contentManagerComponentsService = strapi2.plugin("content-manager").service("components");
|
|
752
|
+
const components = await contentManagerComponentsService.findAllComponents();
|
|
753
|
+
const componentsMap = components.reduce(
|
|
754
|
+
(acc, component) => {
|
|
755
|
+
acc[component.uid] = component;
|
|
756
|
+
return acc;
|
|
757
|
+
},
|
|
758
|
+
{}
|
|
759
|
+
);
|
|
760
|
+
return componentsMap;
|
|
761
|
+
},
|
|
762
|
+
async delete(releaseId) {
|
|
763
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
764
|
+
where: { id: releaseId },
|
|
379
765
|
populate: {
|
|
380
766
|
actions: {
|
|
381
|
-
|
|
382
|
-
entry: true
|
|
383
|
-
}
|
|
767
|
+
select: ["id"]
|
|
384
768
|
}
|
|
385
769
|
}
|
|
770
|
+
});
|
|
771
|
+
if (!release2) {
|
|
772
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
386
773
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
390
|
-
}
|
|
391
|
-
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
392
|
-
throw new errors.ValidationError("Release already published");
|
|
393
|
-
}
|
|
394
|
-
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
395
|
-
throw new errors.ValidationError("No entries to publish");
|
|
396
|
-
}
|
|
397
|
-
const actions = {};
|
|
398
|
-
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
399
|
-
const contentTypeUid = action.contentType;
|
|
400
|
-
if (!actions[contentTypeUid]) {
|
|
401
|
-
actions[contentTypeUid] = {
|
|
402
|
-
publish: [],
|
|
403
|
-
unpublish: []
|
|
404
|
-
};
|
|
774
|
+
if (release2.releasedAt) {
|
|
775
|
+
throw new errors.ValidationError("Release already published");
|
|
405
776
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
777
|
+
await strapi2.db.transaction(async () => {
|
|
778
|
+
await strapi2.db.query(RELEASE_ACTION_MODEL_UID).deleteMany({
|
|
779
|
+
where: {
|
|
780
|
+
id: {
|
|
781
|
+
$in: release2.actions.map((action) => action.id)
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
await strapi2.db.query(RELEASE_MODEL_UID).delete({
|
|
786
|
+
where: {
|
|
787
|
+
id: releaseId
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling") && release2.scheduledAt) {
|
|
792
|
+
const schedulingService = getService("scheduling", { strapi: strapi2 });
|
|
793
|
+
await schedulingService.cancel(release2.id);
|
|
410
794
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
795
|
+
strapi2.telemetry.send("didDeleteContentRelease");
|
|
796
|
+
return release2;
|
|
797
|
+
},
|
|
798
|
+
async publish(releaseId) {
|
|
799
|
+
try {
|
|
800
|
+
const releaseWithPopulatedActionEntries = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
801
|
+
where: { id: releaseId },
|
|
802
|
+
populate: {
|
|
803
|
+
actions: {
|
|
804
|
+
populate: {
|
|
805
|
+
entry: {
|
|
806
|
+
select: ["id"]
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
});
|
|
812
|
+
if (!releaseWithPopulatedActionEntries) {
|
|
813
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
814
|
+
}
|
|
815
|
+
if (releaseWithPopulatedActionEntries.releasedAt) {
|
|
816
|
+
throw new errors.ValidationError("Release already published");
|
|
817
|
+
}
|
|
818
|
+
if (releaseWithPopulatedActionEntries.actions.length === 0) {
|
|
819
|
+
throw new errors.ValidationError("No entries to publish");
|
|
820
|
+
}
|
|
821
|
+
const collectionTypeActions = {};
|
|
822
|
+
const singleTypeActions = [];
|
|
823
|
+
for (const action of releaseWithPopulatedActionEntries.actions) {
|
|
824
|
+
const contentTypeUid = action.contentType;
|
|
825
|
+
if (strapi2.contentTypes[contentTypeUid].kind === "collectionType") {
|
|
826
|
+
if (!collectionTypeActions[contentTypeUid]) {
|
|
827
|
+
collectionTypeActions[contentTypeUid] = {
|
|
828
|
+
entriestoPublishIds: [],
|
|
829
|
+
entriesToUnpublishIds: []
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
if (action.type === "publish") {
|
|
833
|
+
collectionTypeActions[contentTypeUid].entriestoPublishIds.push(action.entry.id);
|
|
834
|
+
} else {
|
|
835
|
+
collectionTypeActions[contentTypeUid].entriesToUnpublishIds.push(action.entry.id);
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
singleTypeActions.push({
|
|
839
|
+
uid: contentTypeUid,
|
|
840
|
+
action: action.type,
|
|
841
|
+
id: action.entry.id
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
const entityManagerService = strapi2.plugin("content-manager").service("entity-manager");
|
|
846
|
+
const populateBuilderService = strapi2.plugin("content-manager").service("populate-builder");
|
|
847
|
+
await strapi2.db.transaction(async () => {
|
|
848
|
+
for (const { uid, action, id } of singleTypeActions) {
|
|
849
|
+
const populate = await populateBuilderService(uid).populateDeep(Infinity).build();
|
|
850
|
+
const entry = await strapi2.db.query(uid).findOne({ where: { id }, populate });
|
|
851
|
+
try {
|
|
852
|
+
if (action === "publish") {
|
|
853
|
+
await entityManagerService.publish(entry, uid);
|
|
854
|
+
} else {
|
|
855
|
+
await entityManagerService.unpublish(entry, uid);
|
|
856
|
+
}
|
|
857
|
+
} catch (error) {
|
|
858
|
+
if (error instanceof errors.ApplicationError && (error.message === "already.published" || error.message === "already.draft")) {
|
|
859
|
+
} else {
|
|
860
|
+
throw error;
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
for (const contentTypeUid of Object.keys(collectionTypeActions)) {
|
|
865
|
+
const populate = await populateBuilderService(contentTypeUid).populateDeep(Infinity).build();
|
|
866
|
+
const { entriestoPublishIds, entriesToUnpublishIds } = collectionTypeActions[contentTypeUid];
|
|
867
|
+
const entriesToPublish = await strapi2.db.query(contentTypeUid).findMany({
|
|
868
|
+
where: {
|
|
869
|
+
id: {
|
|
870
|
+
$in: entriestoPublishIds
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
populate
|
|
874
|
+
});
|
|
875
|
+
const entriesToUnpublish = await strapi2.db.query(contentTypeUid).findMany({
|
|
876
|
+
where: {
|
|
877
|
+
id: {
|
|
878
|
+
$in: entriesToUnpublishIds
|
|
879
|
+
}
|
|
880
|
+
},
|
|
881
|
+
populate
|
|
882
|
+
});
|
|
883
|
+
if (entriesToPublish.length > 0) {
|
|
884
|
+
await entityManagerService.publishMany(entriesToPublish, contentTypeUid);
|
|
885
|
+
}
|
|
886
|
+
if (entriesToUnpublish.length > 0) {
|
|
887
|
+
await entityManagerService.unpublishMany(entriesToUnpublish, contentTypeUid);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
892
|
+
where: { id: releaseId },
|
|
893
|
+
data: {
|
|
894
|
+
releasedAt: /* @__PURE__ */ new Date()
|
|
895
|
+
},
|
|
896
|
+
populate: {
|
|
897
|
+
actions: {
|
|
898
|
+
count: true
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
903
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
904
|
+
isPublished: true,
|
|
905
|
+
release: release2
|
|
906
|
+
});
|
|
418
907
|
}
|
|
419
|
-
|
|
420
|
-
|
|
908
|
+
strapi2.telemetry.send("didPublishContentRelease");
|
|
909
|
+
return release2;
|
|
910
|
+
} catch (error) {
|
|
911
|
+
if (strapi2.features.future.isEnabled("contentReleasesScheduling")) {
|
|
912
|
+
dispatchWebhook(ALLOWED_WEBHOOK_EVENTS.RELEASES_PUBLISH, {
|
|
913
|
+
isPublished: false,
|
|
914
|
+
error
|
|
915
|
+
});
|
|
421
916
|
}
|
|
917
|
+
strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
918
|
+
where: { id: releaseId },
|
|
919
|
+
data: {
|
|
920
|
+
status: "failed"
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
throw error;
|
|
422
924
|
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
925
|
+
},
|
|
926
|
+
async updateAction(actionId, releaseId, update) {
|
|
927
|
+
const updatedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).update({
|
|
928
|
+
where: {
|
|
929
|
+
id: actionId,
|
|
930
|
+
release: {
|
|
931
|
+
id: releaseId,
|
|
932
|
+
releasedAt: {
|
|
933
|
+
$null: true
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
},
|
|
937
|
+
data: update
|
|
938
|
+
});
|
|
939
|
+
if (!updatedAction) {
|
|
940
|
+
throw new errors.NotFoundError(
|
|
941
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
942
|
+
);
|
|
431
943
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
async deleteAction(actionId, releaseId) {
|
|
451
|
-
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
452
|
-
where: {
|
|
453
|
-
id: actionId,
|
|
454
|
-
release: releaseId
|
|
944
|
+
return updatedAction;
|
|
945
|
+
},
|
|
946
|
+
async deleteAction(actionId, releaseId) {
|
|
947
|
+
const deletedAction = await strapi2.db.query(RELEASE_ACTION_MODEL_UID).delete({
|
|
948
|
+
where: {
|
|
949
|
+
id: actionId,
|
|
950
|
+
release: {
|
|
951
|
+
id: releaseId,
|
|
952
|
+
releasedAt: {
|
|
953
|
+
$null: true
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
});
|
|
958
|
+
if (!deletedAction) {
|
|
959
|
+
throw new errors.NotFoundError(
|
|
960
|
+
`Action with id ${actionId} not found in release with id ${releaseId} or it is already published`
|
|
961
|
+
);
|
|
455
962
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
963
|
+
this.updateReleaseStatus(releaseId);
|
|
964
|
+
return deletedAction;
|
|
965
|
+
},
|
|
966
|
+
async updateReleaseStatus(releaseId) {
|
|
967
|
+
const [totalActions, invalidActions] = await Promise.all([
|
|
968
|
+
this.countActions({
|
|
969
|
+
filters: {
|
|
970
|
+
release: releaseId
|
|
971
|
+
}
|
|
972
|
+
}),
|
|
973
|
+
this.countActions({
|
|
974
|
+
filters: {
|
|
975
|
+
release: releaseId,
|
|
976
|
+
isEntryValid: false
|
|
977
|
+
}
|
|
978
|
+
})
|
|
979
|
+
]);
|
|
980
|
+
if (totalActions > 0) {
|
|
981
|
+
if (invalidActions > 0) {
|
|
982
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
983
|
+
where: {
|
|
984
|
+
id: releaseId
|
|
985
|
+
},
|
|
986
|
+
data: {
|
|
987
|
+
status: "blocked"
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
992
|
+
where: {
|
|
993
|
+
id: releaseId
|
|
994
|
+
},
|
|
995
|
+
data: {
|
|
996
|
+
status: "ready"
|
|
997
|
+
}
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
return strapi2.db.query(RELEASE_MODEL_UID).update({
|
|
1001
|
+
where: {
|
|
1002
|
+
id: releaseId
|
|
1003
|
+
},
|
|
1004
|
+
data: {
|
|
1005
|
+
status: "empty"
|
|
1006
|
+
}
|
|
1007
|
+
});
|
|
461
1008
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
});
|
|
1009
|
+
};
|
|
1010
|
+
};
|
|
465
1011
|
const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
466
1012
|
async validateUniqueEntry(releaseId, releaseActionArgs) {
|
|
467
|
-
const release2 = await strapi2.
|
|
468
|
-
|
|
1013
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({
|
|
1014
|
+
where: {
|
|
1015
|
+
id: releaseId
|
|
1016
|
+
},
|
|
1017
|
+
populate: { actions: { populate: { entry: { select: ["id"] } } } }
|
|
469
1018
|
});
|
|
470
1019
|
if (!release2) {
|
|
471
1020
|
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
@@ -484,16 +1033,117 @@ const createReleaseValidationService = ({ strapi: strapi2 }) => ({
|
|
|
484
1033
|
if (!contentType) {
|
|
485
1034
|
throw new errors.NotFoundError(`No content type found for uid ${contentTypeUid}`);
|
|
486
1035
|
}
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
1036
|
+
},
|
|
1037
|
+
async validatePendingReleasesLimit() {
|
|
1038
|
+
const maximumPendingReleases = strapi2.ee.features.get("cms-content-releases")?.options?.maximumReleases || 3;
|
|
1039
|
+
const [, pendingReleasesCount] = await strapi2.db.query(RELEASE_MODEL_UID).findWithCount({
|
|
1040
|
+
filters: {
|
|
1041
|
+
releasedAt: {
|
|
1042
|
+
$null: true
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
});
|
|
1046
|
+
if (pendingReleasesCount >= maximumPendingReleases) {
|
|
1047
|
+
throw new errors.ValidationError("You have reached the maximum number of pending releases");
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
async validateUniqueNameForPendingRelease(name, id) {
|
|
1051
|
+
const pendingReleases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1052
|
+
where: {
|
|
1053
|
+
releasedAt: {
|
|
1054
|
+
$null: true
|
|
1055
|
+
},
|
|
1056
|
+
name,
|
|
1057
|
+
...id && { id: { $ne: id } }
|
|
1058
|
+
}
|
|
1059
|
+
});
|
|
1060
|
+
const isNameUnique = pendingReleases.length === 0;
|
|
1061
|
+
if (!isNameUnique) {
|
|
1062
|
+
throw new errors.ValidationError(`Release with name ${name} already exists`);
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
async validateScheduledAtIsLaterThanNow(scheduledAt) {
|
|
1066
|
+
if (scheduledAt && new Date(scheduledAt) <= /* @__PURE__ */ new Date()) {
|
|
1067
|
+
throw new errors.ValidationError("Scheduled at must be later than now");
|
|
491
1068
|
}
|
|
492
1069
|
}
|
|
493
1070
|
});
|
|
494
|
-
const
|
|
1071
|
+
const createSchedulingService = ({ strapi: strapi2 }) => {
|
|
1072
|
+
const scheduledJobs = /* @__PURE__ */ new Map();
|
|
1073
|
+
return {
|
|
1074
|
+
async set(releaseId, scheduleDate) {
|
|
1075
|
+
const release2 = await strapi2.db.query(RELEASE_MODEL_UID).findOne({ where: { id: releaseId, releasedAt: null } });
|
|
1076
|
+
if (!release2) {
|
|
1077
|
+
throw new errors.NotFoundError(`No release found for id ${releaseId}`);
|
|
1078
|
+
}
|
|
1079
|
+
const job = scheduleJob(scheduleDate, async () => {
|
|
1080
|
+
try {
|
|
1081
|
+
await getService("release").publish(releaseId);
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
}
|
|
1084
|
+
this.cancel(releaseId);
|
|
1085
|
+
});
|
|
1086
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1087
|
+
this.cancel(releaseId);
|
|
1088
|
+
}
|
|
1089
|
+
scheduledJobs.set(releaseId, job);
|
|
1090
|
+
return scheduledJobs;
|
|
1091
|
+
},
|
|
1092
|
+
cancel(releaseId) {
|
|
1093
|
+
if (scheduledJobs.has(releaseId)) {
|
|
1094
|
+
scheduledJobs.get(releaseId).cancel();
|
|
1095
|
+
scheduledJobs.delete(releaseId);
|
|
1096
|
+
}
|
|
1097
|
+
return scheduledJobs;
|
|
1098
|
+
},
|
|
1099
|
+
getAll() {
|
|
1100
|
+
return scheduledJobs;
|
|
1101
|
+
},
|
|
1102
|
+
/**
|
|
1103
|
+
* On bootstrap, we can use this function to make sure to sync the scheduled jobs from the database that are not yet released
|
|
1104
|
+
* This is useful in case the server was restarted and the scheduled jobs were lost
|
|
1105
|
+
* This also could be used to sync different Strapi instances in case of a cluster
|
|
1106
|
+
*/
|
|
1107
|
+
async syncFromDatabase() {
|
|
1108
|
+
const releases = await strapi2.db.query(RELEASE_MODEL_UID).findMany({
|
|
1109
|
+
where: {
|
|
1110
|
+
scheduledAt: {
|
|
1111
|
+
$gte: /* @__PURE__ */ new Date()
|
|
1112
|
+
},
|
|
1113
|
+
releasedAt: null
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
for (const release2 of releases) {
|
|
1117
|
+
this.set(release2.id, release2.scheduledAt);
|
|
1118
|
+
}
|
|
1119
|
+
return scheduledJobs;
|
|
1120
|
+
}
|
|
1121
|
+
};
|
|
1122
|
+
};
|
|
1123
|
+
const services = {
|
|
1124
|
+
release: createReleaseService,
|
|
1125
|
+
"release-validation": createReleaseValidationService,
|
|
1126
|
+
...strapi.features.future.isEnabled("contentReleasesScheduling") ? { scheduling: createSchedulingService } : {}
|
|
1127
|
+
};
|
|
495
1128
|
const RELEASE_SCHEMA = yup.object().shape({
|
|
496
|
-
name: yup.string().trim().required()
|
|
1129
|
+
name: yup.string().trim().required(),
|
|
1130
|
+
scheduledAt: yup.string().nullable(),
|
|
1131
|
+
isScheduled: yup.boolean().optional(),
|
|
1132
|
+
time: yup.string().when("isScheduled", {
|
|
1133
|
+
is: true,
|
|
1134
|
+
then: yup.string().trim().required(),
|
|
1135
|
+
otherwise: yup.string().nullable()
|
|
1136
|
+
}),
|
|
1137
|
+
timezone: yup.string().when("isScheduled", {
|
|
1138
|
+
is: true,
|
|
1139
|
+
then: yup.string().required().nullable(),
|
|
1140
|
+
otherwise: yup.string().nullable()
|
|
1141
|
+
}),
|
|
1142
|
+
date: yup.string().when("isScheduled", {
|
|
1143
|
+
is: true,
|
|
1144
|
+
then: yup.string().required().nullable(),
|
|
1145
|
+
otherwise: yup.string().nullable()
|
|
1146
|
+
})
|
|
497
1147
|
}).required().noUnknown();
|
|
498
1148
|
const validateRelease = validateYupSchema(RELEASE_SCHEMA);
|
|
499
1149
|
const releaseController = {
|
|
@@ -510,9 +1160,7 @@ const releaseController = {
|
|
|
510
1160
|
const contentTypeUid = query.contentTypeUid;
|
|
511
1161
|
const entryId = query.entryId;
|
|
512
1162
|
const hasEntryAttached = typeof query.hasEntryAttached === "string" ? JSON.parse(query.hasEntryAttached) : false;
|
|
513
|
-
const data = await releaseService.
|
|
514
|
-
hasEntryAttached
|
|
515
|
-
});
|
|
1163
|
+
const data = hasEntryAttached ? await releaseService.findManyWithContentTypeEntryAttached(contentTypeUid, entryId) : await releaseService.findManyWithoutContentTypeEntryAttached(contentTypeUid, entryId);
|
|
516
1164
|
ctx.body = { data };
|
|
517
1165
|
} else {
|
|
518
1166
|
const query = await permissionsManager.sanitizeQuery(ctx.query);
|
|
@@ -528,26 +1176,30 @@ const releaseController = {
|
|
|
528
1176
|
}
|
|
529
1177
|
};
|
|
530
1178
|
});
|
|
531
|
-
|
|
1179
|
+
const pendingReleasesCount = await strapi.db.query(RELEASE_MODEL_UID).count({
|
|
1180
|
+
where: {
|
|
1181
|
+
releasedAt: null
|
|
1182
|
+
}
|
|
1183
|
+
});
|
|
1184
|
+
ctx.body = { data, meta: { pagination, pendingReleasesCount } };
|
|
532
1185
|
}
|
|
533
1186
|
},
|
|
534
1187
|
async findOne(ctx) {
|
|
535
1188
|
const id = ctx.params.id;
|
|
536
1189
|
const releaseService = getService("release", { strapi });
|
|
537
1190
|
const release2 = await releaseService.findOne(id, { populate: ["createdBy"] });
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
});
|
|
542
|
-
const sanitizedRelease = await permissionsManager.sanitizeOutput(release2);
|
|
1191
|
+
if (!release2) {
|
|
1192
|
+
throw new errors.NotFoundError(`Release not found for id: ${id}`);
|
|
1193
|
+
}
|
|
543
1194
|
const count = await releaseService.countActions({
|
|
544
1195
|
filters: {
|
|
545
1196
|
release: id
|
|
546
1197
|
}
|
|
547
1198
|
});
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1199
|
+
const sanitizedRelease = {
|
|
1200
|
+
...release2,
|
|
1201
|
+
createdBy: release2.createdBy ? strapi.admin.services.user.sanitizeUser(release2.createdBy) : null
|
|
1202
|
+
};
|
|
551
1203
|
const data = {
|
|
552
1204
|
...sanitizedRelease,
|
|
553
1205
|
actions: {
|
|
@@ -600,8 +1252,27 @@ const releaseController = {
|
|
|
600
1252
|
const id = ctx.params.id;
|
|
601
1253
|
const releaseService = getService("release", { strapi });
|
|
602
1254
|
const release2 = await releaseService.publish(id, { user });
|
|
1255
|
+
const [countPublishActions, countUnpublishActions] = await Promise.all([
|
|
1256
|
+
releaseService.countActions({
|
|
1257
|
+
filters: {
|
|
1258
|
+
release: id,
|
|
1259
|
+
type: "publish"
|
|
1260
|
+
}
|
|
1261
|
+
}),
|
|
1262
|
+
releaseService.countActions({
|
|
1263
|
+
filters: {
|
|
1264
|
+
release: id,
|
|
1265
|
+
type: "unpublish"
|
|
1266
|
+
}
|
|
1267
|
+
})
|
|
1268
|
+
]);
|
|
603
1269
|
ctx.body = {
|
|
604
|
-
data: release2
|
|
1270
|
+
data: release2,
|
|
1271
|
+
meta: {
|
|
1272
|
+
totalEntries: countPublishActions + countUnpublishActions,
|
|
1273
|
+
totalPublishedEntries: countPublishActions,
|
|
1274
|
+
totalUnpublishedEntries: countUnpublishActions
|
|
1275
|
+
}
|
|
605
1276
|
};
|
|
606
1277
|
}
|
|
607
1278
|
};
|
|
@@ -640,11 +1311,30 @@ const releaseActionController = {
|
|
|
640
1311
|
sort: query.groupBy === "action" ? "type" : query.groupBy,
|
|
641
1312
|
...query
|
|
642
1313
|
});
|
|
643
|
-
const
|
|
1314
|
+
const contentTypeOutputSanitizers = results.reduce((acc, action) => {
|
|
1315
|
+
if (acc[action.contentType]) {
|
|
1316
|
+
return acc;
|
|
1317
|
+
}
|
|
1318
|
+
const contentTypePermissionsManager = strapi.admin.services.permission.createPermissionsManager({
|
|
1319
|
+
ability: ctx.state.userAbility,
|
|
1320
|
+
model: action.contentType
|
|
1321
|
+
});
|
|
1322
|
+
acc[action.contentType] = contentTypePermissionsManager.sanitizeOutput;
|
|
1323
|
+
return acc;
|
|
1324
|
+
}, {});
|
|
1325
|
+
const sanitizedResults = await async.map(results, async (action) => ({
|
|
1326
|
+
...action,
|
|
1327
|
+
entry: await contentTypeOutputSanitizers[action.contentType](action.entry)
|
|
1328
|
+
}));
|
|
1329
|
+
const groupedData = await releaseService.groupActions(sanitizedResults, query.groupBy);
|
|
1330
|
+
const contentTypes2 = releaseService.getContentTypeModelsFromActions(results);
|
|
1331
|
+
const components = await releaseService.getAllComponents();
|
|
644
1332
|
ctx.body = {
|
|
645
1333
|
data: groupedData,
|
|
646
1334
|
meta: {
|
|
647
|
-
pagination
|
|
1335
|
+
pagination,
|
|
1336
|
+
contentTypes: contentTypes2,
|
|
1337
|
+
components
|
|
648
1338
|
}
|
|
649
1339
|
};
|
|
650
1340
|
},
|
|
@@ -666,10 +1356,8 @@ const releaseActionController = {
|
|
|
666
1356
|
async delete(ctx) {
|
|
667
1357
|
const actionId = ctx.params.actionId;
|
|
668
1358
|
const releaseId = ctx.params.releaseId;
|
|
669
|
-
const
|
|
670
|
-
|
|
671
|
-
releaseId
|
|
672
|
-
);
|
|
1359
|
+
const releaseService = getService("release", { strapi });
|
|
1360
|
+
const deletedReleaseAction = await releaseService.deleteAction(actionId, releaseId);
|
|
673
1361
|
ctx.body = {
|
|
674
1362
|
data: deletedReleaseAction
|
|
675
1363
|
};
|
|
@@ -850,11 +1538,12 @@ const routes = {
|
|
|
850
1538
|
release,
|
|
851
1539
|
"release-action": releaseAction
|
|
852
1540
|
};
|
|
853
|
-
const { features } = require("@strapi/strapi/dist/utils/ee");
|
|
854
1541
|
const getPlugin = () => {
|
|
855
|
-
if (features.isEnabled("cms-content-releases")
|
|
1542
|
+
if (strapi.ee.features.isEnabled("cms-content-releases")) {
|
|
856
1543
|
return {
|
|
857
1544
|
register,
|
|
1545
|
+
bootstrap,
|
|
1546
|
+
destroy,
|
|
858
1547
|
contentTypes,
|
|
859
1548
|
services,
|
|
860
1549
|
controllers,
|