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