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