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