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