@strapi/admin 4.9.0 → 4.10.0-beta.0
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/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/PublicationState.js +26 -0
- package/admin/src/content-manager/components/DynamicTable/CellContent/PublicationState/index.js +1 -0
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/ReviewWorkflowsStage.js +15 -0
- package/admin/src/content-manager/components/DynamicTable/CellContent/ReviewWorkflowsStage/index.js +1 -0
- package/admin/src/content-manager/components/DynamicTable/index.js +43 -49
- package/admin/src/content-manager/components/DynamicZone/components/DynamicComponent.js +2 -0
- package/admin/src/content-manager/components/EditViewDataManagerProvider/reducer.js +1 -3
- package/admin/src/content-manager/components/EditViewDataManagerProvider/utils/findAllAndReplace.js +3 -10
- package/admin/src/content-manager/components/InputUID/endActionStyle.js +13 -4
- package/admin/src/content-manager/components/InputUID/index.js +71 -94
- package/admin/src/content-manager/pages/EditView/Information/index.js +77 -53
- package/admin/src/content-manager/pages/EditView/InformationBox/InformationBoxCE.js +13 -0
- package/admin/src/content-manager/pages/EditView/InformationBox/index.js +3 -0
- package/admin/src/content-manager/pages/EditView/index.js +3 -4
- package/admin/src/content-manager/pages/ListView/index.js +6 -9
- package/admin/src/hooks/useRegenerate/index.js +7 -12
- package/admin/src/index.js +1 -0
- package/admin/src/pages/AuthPage/components/Register/index.js +38 -46
- package/admin/src/pages/SettingsPage/components/Tokens/FormHead/index.js +0 -4
- package/admin/src/pages/SettingsPage/components/Tokens/Regenerate/index.js +3 -5
- package/admin/src/pages/SettingsPage/components/Tokens/TokenTypeSelect/index.js +5 -7
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/components/FormTransferTokenContainer/index.js +0 -41
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/index.js +9 -53
- package/admin/src/pages/SettingsPage/pages/TransferTokens/EditView/utils/schema.js +0 -1
- package/admin/src/pages/SettingsPage/pages/TransferTokens/ListView/index.js +5 -27
- package/admin/src/translations/en.json +6 -1
- package/build/2263.4c5916f9.chunk.js +98 -0
- package/build/4049.64715f20.chunk.js +1 -0
- package/build/4649.213b8a3b.chunk.js +30 -0
- package/build/6985.66cca29c.chunk.js +1 -0
- package/build/7259.aefb51e8.chunk.js +1 -0
- package/build/8469.853c822b.chunk.js +1 -0
- package/build/9505.dbe702ab.chunk.js +14 -0
- package/build/9816.01ee964f.chunk.js +2 -0
- package/build/Admin-authenticatedApp.f50ad423.chunk.js +79 -0
- package/build/Admin_InternalErrorPage.4ad8b0df.chunk.js +1 -0
- package/build/Admin_homePage.1411fb7c.chunk.js +68 -0
- package/build/Admin_marketplace.02608d56.chunk.js +22 -0
- package/build/Admin_pluginsPage.15e3b0fd.chunk.js +1 -0
- package/build/Admin_profilePage.76afeca0.chunk.js +15 -0
- package/build/Admin_settingsPage.147755cd.chunk.js +9 -0
- package/build/Upload_ConfigureTheView.34dde278.chunk.js +1 -0
- package/build/admin-app.55dd7921.chunk.js +112 -0
- package/build/admin-edit-roles-page.cf543488.chunk.js +216 -0
- package/build/admin-edit-users.31c20712.chunk.js +10 -0
- package/build/admin-roles-list.489c501f.chunk.js +2 -0
- package/build/admin-users.3e111a7d.chunk.js +11 -0
- package/build/api-tokens-create-page.4328b852.chunk.js +1 -0
- package/build/api-tokens-edit-page.bce5050f.chunk.js +1 -0
- package/build/api-tokens-list-page.93f24348.chunk.js +16 -0
- package/build/audit-logs-settings-page.7be97e82.chunk.js +1 -0
- package/build/content-manager.4480ae88.chunk.js +1137 -0
- package/build/content-type-builder-list-view.cf38fe2f.chunk.js +191 -0
- package/build/content-type-builder-translation-en-json.7961593e.chunk.js +1 -0
- package/build/content-type-builder.af9abf1e.chunk.js +126 -0
- package/build/email-settings-page.4bdbef9a.chunk.js +3 -0
- package/build/en-json.697b4bcf.chunk.js +1 -0
- package/build/{highlight.js.28a1547e.chunk.js → highlight.js.26ef649f.chunk.js} +2 -2
- package/build/i18n-settings-page.2bb5be96.chunk.js +1 -0
- package/build/index.html +1 -1
- package/build/main.af8c0f31.js +3790 -0
- package/build/review-workflows-settings.7a7dc773.chunk.js +57 -0
- package/build/runtime~main.5a95bee6.js +2 -0
- package/build/sso-settings-page.272b87c8.chunk.js +1 -0
- package/build/transfer-tokens-create-page.a1f14bb1.chunk.js +1 -0
- package/build/transfer-tokens-edit-page.00ee1c74.chunk.js +1 -0
- package/build/transfer-tokens-list-page.ce37354b.chunk.js +16 -0
- package/build/upload-settings.0875e973.chunk.js +1 -0
- package/build/{upload-translation-th-json.98d35574.chunk.js → upload-translation-th-json.3847dae0.chunk.js} +1 -1
- package/build/upload.c7da1611.chunk.js +13 -0
- package/build/users-advanced-settings-page.1d3c14c7.chunk.js +1 -0
- package/build/users-email-settings-page.e8db68c4.chunk.js +1 -0
- package/build/users-providers-settings-page.14cac425.chunk.js +1 -0
- package/build/users-roles-settings-page.2ea4de84.chunk.js +30 -0
- package/build/webhook-edit-page.329141a5.chunk.js +23 -0
- package/build/webhook-list-page.029957a4.chunk.js +1 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/InformationBoxEE.js +92 -0
- package/ee/admin/content-manager/pages/EditView/InformationBox/index.js +3 -0
- package/ee/admin/hooks/useSettingsMenu/utils/customAdminLinks.js +12 -12
- package/ee/admin/hooks/useSettingsMenu/utils/customGlobalLinks.js +21 -13
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/ReviewWorkflows.js +195 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/actions/index.js +42 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/AddStage.js +87 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/AddStage/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/Stage.js +90 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stage/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/Stages.js +92 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/components/Stages/index.js +1 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/constants.js +6 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/hooks/useReviewWorkflows.js +35 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/index.js +3 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/reducer/index.js +121 -0
- package/ee/admin/pages/SettingsPage/pages/ReviewWorkflows/utils/getWorkflowValidationSchema.js +25 -0
- package/ee/admin/pages/SettingsPage/utils/customRoutes.js +16 -2
- package/ee/admin/permissions/customPermissions.js +3 -0
- package/ee/server/bootstrap.js +13 -0
- package/ee/server/config/admin-actions.js +10 -0
- package/ee/server/constants/default-stages.json +14 -0
- package/ee/server/constants/default-workflow.json +1 -0
- package/ee/server/constants/workflows.js +8 -0
- package/ee/server/content-types/index.js +9 -0
- package/ee/server/content-types/workflow/index.js +34 -0
- package/ee/server/content-types/workflow-stage/index.js +41 -0
- package/ee/server/controllers/index.js +2 -0
- package/ee/server/controllers/workflows/index.js +36 -0
- package/ee/server/controllers/workflows/stages/index.js +95 -0
- package/ee/server/index.js +1 -0
- package/ee/server/middlewares/review-workflows.js +40 -0
- package/ee/server/migrations/review-workflows.js +39 -0
- package/ee/server/register.js +9 -3
- package/ee/server/routes/index.js +104 -0
- package/ee/server/services/audit-logs.js +16 -75
- package/ee/server/services/index.js +4 -0
- package/ee/server/services/review-workflows/entity-service-decorator.js +42 -0
- package/ee/server/services/review-workflows/review-workflows.js +175 -0
- package/ee/server/services/review-workflows/stages.js +148 -0
- package/ee/server/services/review-workflows/workflows.js +25 -0
- package/ee/server/utils/index.js +8 -0
- package/ee/server/utils/review-workflows.js +25 -0
- package/ee/server/utils/test.js +11 -0
- package/ee/server/validation/review-workflows.js +24 -0
- package/jest.config.front.js +6 -1
- package/package.json +15 -17
- package/server/controllers/transfer/runner.js +2 -4
- package/server/middlewares/data-transfer.js +1 -4
- package/server/routes/transfer.js +4 -13
- package/server/services/constants.js +0 -4
- package/server/services/transfer/permission.js +1 -1
- package/server/services/transfer/token.js +31 -33
- package/server/validation/transfer/token.js +2 -10
- package/webpack.config.js +1 -1
- package/.eslintignore +0 -4
- package/.eslintrc.js +0 -14
- package/admin/src/components/LocalesProvider/__mocks__/useLocalesProvider.js +0 -7
- package/admin/src/hooks/useConfigurations/__mocks__/index.js +0 -7
- package/build/1387.84b454d3.chunk.js +0 -1
- package/build/1657.45231968.chunk.js +0 -168
- package/build/3081.bcf9a12f.chunk.js +0 -108
- package/build/462.8fff7f3b.chunk.js +0 -71
- package/build/4628.20631dd1.chunk.js +0 -1
- package/build/5542.b8240e3f.chunk.js +0 -70
- package/build/5563.905daa13.chunk.js +0 -79
- package/build/6404.68405699.chunk.js +0 -100
- package/build/7259.b7d00cea.chunk.js +0 -1
- package/build/8694.6522968d.chunk.js +0 -247
- package/build/9347.058ddb22.chunk.js +0 -1
- package/build/Admin-authenticatedApp.31bf88ef.chunk.js +0 -79
- package/build/Admin_InternalErrorPage.15c6bf07.chunk.js +0 -1
- package/build/Admin_homePage.da2181fe.chunk.js +0 -73
- package/build/Admin_marketplace.d99044eb.chunk.js +0 -31
- package/build/Admin_pluginsPage.f6b52ee9.chunk.js +0 -6
- package/build/Admin_profilePage.9112cffc.chunk.js +0 -15
- package/build/Admin_settingsPage.cb63220f.chunk.js +0 -79
- package/build/Upload_ConfigureTheView.eaaec495.chunk.js +0 -1
- package/build/admin-app.8cde5b22.chunk.js +0 -110
- package/build/admin-edit-roles-page.4f1858e9.chunk.js +0 -280
- package/build/admin-edit-users.7e14d85f.chunk.js +0 -10
- package/build/admin-roles-list.97e198f9.chunk.js +0 -31
- package/build/admin-users.d02de059.chunk.js +0 -34
- package/build/api-tokens-create-page.97595e12.chunk.js +0 -1
- package/build/api-tokens-edit-page.cd36e30e.chunk.js +0 -1
- package/build/api-tokens-list-page.6757c7b9.chunk.js +0 -16
- package/build/audit-logs-settings-page.ca9a3c46.chunk.js +0 -76
- package/build/content-manager.de0ee3e5.chunk.js +0 -1132
- package/build/content-type-builder-list-view.9c2c020c.chunk.js +0 -214
- package/build/content-type-builder-translation-en-json.e577d595.chunk.js +0 -1
- package/build/content-type-builder.ec5ac7ab.chunk.js +0 -126
- package/build/email-settings-page.1095e1ab.chunk.js +0 -10
- package/build/en-json.b052667a.chunk.js +0 -1
- package/build/i18n-settings-page.7d80aae0.chunk.js +0 -60
- package/build/main.d40f9ca1.js +0 -2280
- package/build/runtime~main.7cdc9956.js +0 -2
- package/build/sso-settings-page.1dd4886e.chunk.js +0 -1
- package/build/transfer-tokens-create-page.ec2ca215.chunk.js +0 -1
- package/build/transfer-tokens-edit-page.22bf28e5.chunk.js +0 -1
- package/build/transfer-tokens-list-page.cf8c77f2.chunk.js +0 -16
- package/build/upload-settings.945fdcfa.chunk.js +0 -13
- package/build/upload.a86b1054.chunk.js +0 -33
- package/build/users-advanced-settings-page.5b5a9baa.chunk.js +0 -8
- package/build/users-email-settings-page.e5506eb4.chunk.js +0 -23
- package/build/users-providers-settings-page.e32089c2.chunk.js +0 -28
- package/build/users-roles-settings-page.a5c5b0df.chunk.js +0 -30
- package/build/webhook-edit-page.213f0075.chunk.js +0 -75
- package/build/webhook-list-page.5beb2a5c.chunk.js +0 -71
|
@@ -122,4 +122,108 @@ module.exports = [
|
|
|
122
122
|
],
|
|
123
123
|
},
|
|
124
124
|
},
|
|
125
|
+
|
|
126
|
+
// Review workflow
|
|
127
|
+
{
|
|
128
|
+
method: 'GET',
|
|
129
|
+
path: '/review-workflows/workflows',
|
|
130
|
+
handler: 'workflows.find',
|
|
131
|
+
config: {
|
|
132
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
133
|
+
policies: [
|
|
134
|
+
'admin::isAuthenticatedAdmin',
|
|
135
|
+
{
|
|
136
|
+
name: 'admin::hasPermissions',
|
|
137
|
+
config: {
|
|
138
|
+
actions: ['admin::review-workflows.read'],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
method: 'GET',
|
|
146
|
+
path: '/review-workflows/workflows/:id',
|
|
147
|
+
handler: 'workflows.findById',
|
|
148
|
+
config: {
|
|
149
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
150
|
+
policies: [
|
|
151
|
+
'admin::isAuthenticatedAdmin',
|
|
152
|
+
{
|
|
153
|
+
name: 'admin::hasPermissions',
|
|
154
|
+
config: {
|
|
155
|
+
actions: ['admin::review-workflows.read'],
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
method: 'GET',
|
|
163
|
+
path: '/review-workflows/workflows/:workflow_id/stages',
|
|
164
|
+
handler: 'stages.find',
|
|
165
|
+
config: {
|
|
166
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
167
|
+
policies: [
|
|
168
|
+
'admin::isAuthenticatedAdmin',
|
|
169
|
+
{
|
|
170
|
+
name: 'admin::hasPermissions',
|
|
171
|
+
config: {
|
|
172
|
+
actions: ['admin::review-workflows.read'],
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
method: 'PUT',
|
|
180
|
+
path: '/review-workflows/workflows/:workflow_id/stages',
|
|
181
|
+
handler: 'stages.replace',
|
|
182
|
+
config: {
|
|
183
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
184
|
+
policies: [
|
|
185
|
+
'admin::isAuthenticatedAdmin',
|
|
186
|
+
{
|
|
187
|
+
name: 'admin::hasPermissions',
|
|
188
|
+
config: {
|
|
189
|
+
actions: ['admin::review-workflows.read'],
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
method: 'GET',
|
|
197
|
+
path: '/review-workflows/workflows/:workflow_id/stages/:id',
|
|
198
|
+
handler: 'stages.findById',
|
|
199
|
+
config: {
|
|
200
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
201
|
+
policies: [
|
|
202
|
+
'admin::isAuthenticatedAdmin',
|
|
203
|
+
{
|
|
204
|
+
name: 'admin::hasPermissions',
|
|
205
|
+
config: {
|
|
206
|
+
actions: ['admin::review-workflows.read'],
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
method: 'PUT',
|
|
214
|
+
path: '/content-manager/(collection|single)-types/:model_uid/:id/stage',
|
|
215
|
+
handler: 'stages.updateEntity',
|
|
216
|
+
config: {
|
|
217
|
+
middlewares: [enableFeatureMiddleware('review-workflows')],
|
|
218
|
+
policies: [
|
|
219
|
+
'admin::isAuthenticatedAdmin',
|
|
220
|
+
{
|
|
221
|
+
name: 'admin::hasPermissions',
|
|
222
|
+
config: {
|
|
223
|
+
actions: ['admin::review-workflows.read'],
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
},
|
|
125
229
|
];
|
|
@@ -63,37 +63,16 @@ const getEventMap = (defaultEvents) => {
|
|
|
63
63
|
}, {});
|
|
64
64
|
};
|
|
65
65
|
|
|
66
|
-
const getRetentionDays = (strapi) => {
|
|
67
|
-
const licenseRetentionDays = features.get('audit-logs')?.options.retentionDays;
|
|
68
|
-
const userRetentionDays = strapi.config.get('admin.auditLogs.retentionDays');
|
|
69
|
-
|
|
70
|
-
// For enterprise plans, use 90 days by default, but allow users to override it
|
|
71
|
-
if (licenseRetentionDays == null) {
|
|
72
|
-
return userRetentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Allow users to override the license retention days, but not to increase it
|
|
76
|
-
if (userRetentionDays && userRetentionDays < licenseRetentionDays) {
|
|
77
|
-
return userRetentionDays;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// User didn't provide a retention days value, use the license one
|
|
81
|
-
return licenseRetentionDays;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
66
|
const createAuditLogsService = (strapi) => {
|
|
85
|
-
// Manage internal service state privately
|
|
86
|
-
const state = {};
|
|
87
|
-
|
|
88
67
|
// NOTE: providers should be able to replace getEventMap to add or remove events
|
|
89
68
|
const eventMap = getEventMap(defaultEvents);
|
|
90
69
|
|
|
91
70
|
const processEvent = (name, ...args) => {
|
|
92
|
-
const
|
|
71
|
+
const state = strapi.requestContext.get()?.state;
|
|
93
72
|
|
|
94
73
|
// Ignore events with auth strategies different from admin
|
|
95
|
-
const isUsingAdminAuth =
|
|
96
|
-
const user =
|
|
74
|
+
const isUsingAdminAuth = state?.auth?.strategy.name === 'admin';
|
|
75
|
+
const user = state?.user;
|
|
97
76
|
|
|
98
77
|
if (!isUsingAdminAuth || !user) {
|
|
99
78
|
return null;
|
|
@@ -124,60 +103,26 @@ const createAuditLogsService = (strapi) => {
|
|
|
124
103
|
const processedEvent = processEvent(name, ...args);
|
|
125
104
|
|
|
126
105
|
if (processedEvent) {
|
|
127
|
-
await
|
|
106
|
+
await this._provider.saveEvent(processedEvent);
|
|
128
107
|
}
|
|
129
108
|
}
|
|
130
109
|
|
|
131
110
|
return {
|
|
132
111
|
async register() {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
this.register();
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Handle license being updated
|
|
143
|
-
if (!state.eeUpdateUnsubscribe) {
|
|
144
|
-
state.eeUpdateUnsubscribe = strapi.eventHub.on('ee.update', () => {
|
|
145
|
-
// Recreate the service to use the new license info
|
|
146
|
-
this.destroy();
|
|
147
|
-
this.register();
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// Handle license being disabled
|
|
152
|
-
state.eeDisableUnsubscribe = strapi.eventHub.on('ee.disable', () => {
|
|
153
|
-
// Turn off service when the license gets disabled
|
|
154
|
-
// Only ee.enable and ee.update listeners remain active to recreate the service
|
|
155
|
-
this.destroy();
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
// Register the provider now because collections can't be added later at runtime
|
|
159
|
-
state.provider = await localProvider.register({ strapi });
|
|
160
|
-
|
|
161
|
-
// Check current state of license
|
|
162
|
-
if (!features.isEnabled('audit-logs')) {
|
|
163
|
-
return this;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Start saving events
|
|
167
|
-
state.eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
|
|
168
|
-
|
|
169
|
-
// Manage audit logs auto deletion
|
|
170
|
-
const retentionDays = getRetentionDays(strapi);
|
|
171
|
-
state.deleteExpiredJob = scheduleJob('0 0 * * *', () => {
|
|
112
|
+
const retentionDays =
|
|
113
|
+
features.get('audit-logs')?.options.retentionDays ?? DEFAULT_RETENTION_DAYS;
|
|
114
|
+
this._provider = await localProvider.register({ strapi });
|
|
115
|
+
this._eventHubUnsubscribe = strapi.eventHub.subscribe(handleEvent.bind(this));
|
|
116
|
+
this._deleteExpiredJob = scheduleJob('0 0 * * *', () => {
|
|
172
117
|
const expirationDate = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000);
|
|
173
|
-
|
|
118
|
+
this._provider.deleteExpiredEvents(expirationDate);
|
|
174
119
|
});
|
|
175
120
|
|
|
176
121
|
return this;
|
|
177
122
|
},
|
|
178
123
|
|
|
179
124
|
async findMany(query) {
|
|
180
|
-
const { results, pagination } = await
|
|
125
|
+
const { results, pagination } = await this._provider.findMany(query);
|
|
181
126
|
|
|
182
127
|
const sanitizedResults = results.map((result) => {
|
|
183
128
|
const { user, ...rest } = result;
|
|
@@ -194,7 +139,7 @@ const createAuditLogsService = (strapi) => {
|
|
|
194
139
|
},
|
|
195
140
|
|
|
196
141
|
async findOne(id) {
|
|
197
|
-
const result = await
|
|
142
|
+
const result = await this._provider.findOne(id);
|
|
198
143
|
|
|
199
144
|
if (!result) {
|
|
200
145
|
return null;
|
|
@@ -208,16 +153,12 @@ const createAuditLogsService = (strapi) => {
|
|
|
208
153
|
},
|
|
209
154
|
|
|
210
155
|
unsubscribe() {
|
|
211
|
-
if (
|
|
212
|
-
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (state.eventHubUnsubscribe) {
|
|
216
|
-
state.eventHubUnsubscribe();
|
|
156
|
+
if (this._eventHubUnsubscribe) {
|
|
157
|
+
this._eventHubUnsubscribe();
|
|
217
158
|
}
|
|
218
159
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
160
|
+
if (this._deleteExpiredJob) {
|
|
161
|
+
this._deleteExpiredJob.cancel();
|
|
221
162
|
}
|
|
222
163
|
|
|
223
164
|
return this;
|
|
@@ -5,4 +5,8 @@ module.exports = {
|
|
|
5
5
|
role: require('./role'),
|
|
6
6
|
user: require('./user'),
|
|
7
7
|
'seat-enforcement': require('./seat-enforcement'),
|
|
8
|
+
workflows: require('./review-workflows/workflows'),
|
|
9
|
+
stages: require('./review-workflows/stages'),
|
|
10
|
+
'review-workflows': require('./review-workflows/review-workflows'),
|
|
11
|
+
'review-workflows-decorator': require('./review-workflows/entity-service-decorator'),
|
|
8
12
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isNil } = require('lodash/fp');
|
|
4
|
+
const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
5
|
+
const { hasReviewWorkflow, getDefaultWorkflow } = require('../../utils/review-workflows');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Assigns the entity data to the default workflow stage if no stage is present in the data
|
|
9
|
+
* @param {Object} data
|
|
10
|
+
* @returns
|
|
11
|
+
*/
|
|
12
|
+
const getDataWithStage = async (data) => {
|
|
13
|
+
if (!isNil(ENTITY_STAGE_ATTRIBUTE, data)) {
|
|
14
|
+
const defaultWorkflow = await getDefaultWorkflow({ strapi });
|
|
15
|
+
return { ...data, [ENTITY_STAGE_ATTRIBUTE]: defaultWorkflow.stages[0].id };
|
|
16
|
+
}
|
|
17
|
+
return data;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Decorates the entity service with RW business logic
|
|
22
|
+
* @param {object} service - entity service
|
|
23
|
+
*/
|
|
24
|
+
const decorator = (service) => ({
|
|
25
|
+
async create(uid, opts = {}) {
|
|
26
|
+
const hasRW = hasReviewWorkflow({ strapi }, uid);
|
|
27
|
+
|
|
28
|
+
if (!hasRW) {
|
|
29
|
+
return service.create.call(this, uid, opts);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const data = await getDataWithStage(opts.data);
|
|
33
|
+
return service.create.call(this, uid, {
|
|
34
|
+
...opts,
|
|
35
|
+
data,
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
module.exports = () => ({
|
|
41
|
+
decorator,
|
|
42
|
+
});
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { set, get, forEach, keys, pickBy, pipe } = require('lodash/fp');
|
|
4
|
+
const { mapAsync } = require('@strapi/utils');
|
|
5
|
+
const { getService } = require('../../utils');
|
|
6
|
+
|
|
7
|
+
const defaultStages = require('../../constants/default-stages.json');
|
|
8
|
+
const defaultWorkflow = require('../../constants/default-workflow.json');
|
|
9
|
+
const { ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
disableOnContentTypes: disableReviewWorkflows,
|
|
13
|
+
} = require('../../migrations/review-workflows');
|
|
14
|
+
const { getDefaultWorkflow } = require('../../utils/review-workflows');
|
|
15
|
+
|
|
16
|
+
const getContentTypeUIDsWithActivatedReviewWorkflows = pipe([
|
|
17
|
+
// Pick only content-types with reviewWorkflows options set to true
|
|
18
|
+
pickBy(get('options.reviewWorkflows')),
|
|
19
|
+
// Get UIDs
|
|
20
|
+
keys,
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Map every stage in the array to be ordered in the relation
|
|
25
|
+
* @param {Object[]} stages
|
|
26
|
+
* @param {number} stages.id
|
|
27
|
+
* @return {Object[]}
|
|
28
|
+
*/
|
|
29
|
+
function buildStagesConnectArray(stages) {
|
|
30
|
+
return stages.map((stage, index) => {
|
|
31
|
+
const connect = {
|
|
32
|
+
id: stage.id,
|
|
33
|
+
position: {},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (index === 0) {
|
|
37
|
+
connect.position.start = true;
|
|
38
|
+
} else {
|
|
39
|
+
connect.position.after = stages[index - 1].id;
|
|
40
|
+
}
|
|
41
|
+
return connect;
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function initDefaultWorkflow({ workflowsService, stagesService, strapi }) {
|
|
46
|
+
const wfCount = await workflowsService.count();
|
|
47
|
+
const stagesCount = await stagesService.count();
|
|
48
|
+
|
|
49
|
+
// Check if there is nothing about review-workflow in DB
|
|
50
|
+
// If any, the feature has already been initialized with a workflow and stages
|
|
51
|
+
if (wfCount === 0 && stagesCount === 0) {
|
|
52
|
+
const stages = await stagesService.createMany(defaultStages, { fields: ['id'] });
|
|
53
|
+
const workflow = {
|
|
54
|
+
...defaultWorkflow,
|
|
55
|
+
stages: {
|
|
56
|
+
connect: buildStagesConnectArray(stages),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
await workflowsService.create(workflow);
|
|
61
|
+
// If there is any manually activated RW on content-types, we want to migrate the related entities
|
|
62
|
+
await enableReviewWorkflow({ strapi })({ contentTypes: strapi.contentTypes });
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const setStageAttribute = set(`attributes.${ENTITY_STAGE_ATTRIBUTE}`, {
|
|
67
|
+
writable: true,
|
|
68
|
+
private: false,
|
|
69
|
+
configurable: false,
|
|
70
|
+
visible: false,
|
|
71
|
+
type: 'relation',
|
|
72
|
+
relation: 'morphOne',
|
|
73
|
+
target: 'admin::workflow-stage',
|
|
74
|
+
morphBy: 'related',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function extendReviewWorkflowContentTypes({ strapi }) {
|
|
78
|
+
const extendContentType = (contentTypeUID) => {
|
|
79
|
+
strapi.container.get('content-types').extend(contentTypeUID, setStageAttribute);
|
|
80
|
+
};
|
|
81
|
+
pipe([
|
|
82
|
+
getContentTypeUIDsWithActivatedReviewWorkflows,
|
|
83
|
+
// Iterate over UIDs to extend the content-type
|
|
84
|
+
forEach(extendContentType),
|
|
85
|
+
])(strapi.contentTypes);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Enables the review workflow for the given content types.
|
|
90
|
+
* @param {Object} strapi - Strapi instance
|
|
91
|
+
*/
|
|
92
|
+
function enableReviewWorkflow({ strapi }) {
|
|
93
|
+
/**
|
|
94
|
+
* @param {Array<string>} contentTypes - Content type UIDs to enable the review workflow for.
|
|
95
|
+
* @returns {Promise<void>} - Promise that resolves when the review workflow is enabled.
|
|
96
|
+
*/
|
|
97
|
+
return async ({ contentTypes }) => {
|
|
98
|
+
const defaultWorkflow = await getDefaultWorkflow({ strapi });
|
|
99
|
+
// This is possible if this is the first start of EE, there won't be any workflow in DB before bootstrap
|
|
100
|
+
if (!defaultWorkflow) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const firstStage = defaultWorkflow.stages[0];
|
|
104
|
+
|
|
105
|
+
const up = async (contentTypeUID) => {
|
|
106
|
+
const contentTypeMetadata = strapi.db.metadata.get(contentTypeUID);
|
|
107
|
+
const { target, morphBy } = contentTypeMetadata.attributes[ENTITY_STAGE_ATTRIBUTE];
|
|
108
|
+
const { joinTable } = strapi.db.metadata.get(target).attributes[morphBy];
|
|
109
|
+
const { idColumn, typeColumn } = joinTable.morphColumn;
|
|
110
|
+
|
|
111
|
+
// Execute an SQL query to insert records into the join table mapping the specified content type with the first stage of the default workflow.
|
|
112
|
+
// Only entities that do not have a record in the join table yet are selected.
|
|
113
|
+
const selectStatement = strapi.db
|
|
114
|
+
.getConnection()
|
|
115
|
+
.select({
|
|
116
|
+
[idColumn.name]: 'entity.id',
|
|
117
|
+
field: strapi.db.connection.raw('?', [ENTITY_STAGE_ATTRIBUTE]),
|
|
118
|
+
order: 1,
|
|
119
|
+
[joinTable.joinColumn.name]: firstStage.id,
|
|
120
|
+
[typeColumn.name]: strapi.db.connection.raw('?', [contentTypeUID]),
|
|
121
|
+
})
|
|
122
|
+
.leftJoin(`${joinTable.name} AS jointable`, function joinFunc() {
|
|
123
|
+
this.on('entity.id', '=', `jointable.${idColumn.name}`).andOn(
|
|
124
|
+
`jointable.${typeColumn.name}`,
|
|
125
|
+
'=',
|
|
126
|
+
strapi.db.connection.raw('?', [contentTypeUID])
|
|
127
|
+
);
|
|
128
|
+
})
|
|
129
|
+
.where(`jointable.${idColumn.name}`, null)
|
|
130
|
+
.from(`${contentTypeMetadata.tableName} AS entity`)
|
|
131
|
+
.toSQL();
|
|
132
|
+
|
|
133
|
+
const columnsToInsert = [
|
|
134
|
+
idColumn.name,
|
|
135
|
+
'field',
|
|
136
|
+
strapi.db.connection.raw('??', ['order']),
|
|
137
|
+
joinTable.joinColumn.name,
|
|
138
|
+
typeColumn.name,
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
// Insert rows for all entries of the content type that do not have a
|
|
142
|
+
// default stage
|
|
143
|
+
await strapi.db
|
|
144
|
+
.getConnection(joinTable.name)
|
|
145
|
+
.insert(
|
|
146
|
+
strapi.db.connection.raw(
|
|
147
|
+
`(${columnsToInsert.join(',')}) ${selectStatement.sql}`,
|
|
148
|
+
selectStatement.bindings
|
|
149
|
+
)
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return pipe([
|
|
154
|
+
getContentTypeUIDsWithActivatedReviewWorkflows,
|
|
155
|
+
// Iterate over UIDs to extend the content-type
|
|
156
|
+
(contentTypesUIDs) => mapAsync(contentTypesUIDs, up),
|
|
157
|
+
])(contentTypes);
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = ({ strapi }) => {
|
|
162
|
+
const workflowsService = getService('workflows', { strapi });
|
|
163
|
+
const stagesService = getService('stages', { strapi });
|
|
164
|
+
|
|
165
|
+
return {
|
|
166
|
+
async bootstrap() {
|
|
167
|
+
await initDefaultWorkflow({ workflowsService, stagesService, strapi });
|
|
168
|
+
},
|
|
169
|
+
async register() {
|
|
170
|
+
extendReviewWorkflowContentTypes({ strapi });
|
|
171
|
+
strapi.hook('strapi::content-types.afterSync').register(enableReviewWorkflow({ strapi }));
|
|
172
|
+
strapi.hook('strapi::content-types.afterSync').register(disableReviewWorkflows);
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
mapAsync,
|
|
5
|
+
errors: { ApplicationError },
|
|
6
|
+
} = require('@strapi/utils');
|
|
7
|
+
|
|
8
|
+
const { STAGE_MODEL_UID, ENTITY_STAGE_ATTRIBUTE } = require('../../constants/workflows');
|
|
9
|
+
const { getService } = require('../../utils');
|
|
10
|
+
|
|
11
|
+
module.exports = ({ strapi }) => {
|
|
12
|
+
const workflowsService = getService('workflows', { strapi });
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
find({ workflowId, populate }) {
|
|
16
|
+
const params = {
|
|
17
|
+
filters: { workflow: workflowId },
|
|
18
|
+
populate,
|
|
19
|
+
};
|
|
20
|
+
return strapi.entityService.findMany(STAGE_MODEL_UID, params);
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
findById(id, { workflowId, populate }) {
|
|
24
|
+
const params = {
|
|
25
|
+
filters: { workflow: workflowId },
|
|
26
|
+
populate,
|
|
27
|
+
};
|
|
28
|
+
return strapi.entityService.findOne(STAGE_MODEL_UID, id, params);
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
createMany(stagesList, { fields }) {
|
|
32
|
+
const params = {
|
|
33
|
+
select: fields,
|
|
34
|
+
};
|
|
35
|
+
return Promise.all(
|
|
36
|
+
stagesList.map((stage) =>
|
|
37
|
+
strapi.entityService.create(STAGE_MODEL_UID, { data: stage, ...params })
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
update(stageId, stageData) {
|
|
43
|
+
return strapi.entityService.update(STAGE_MODEL_UID, stageId, { data: stageData });
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
delete(stageId) {
|
|
47
|
+
return strapi.entityService.delete(STAGE_MODEL_UID, stageId);
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
count() {
|
|
51
|
+
return strapi.entityService.count(STAGE_MODEL_UID);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
async replaceWorkflowStages(workflowId, stages) {
|
|
55
|
+
const workflow = await workflowsService.findById(workflowId, { populate: ['stages'] });
|
|
56
|
+
|
|
57
|
+
const { created, updated, deleted } = getDiffBetweenStages(workflow.stages, stages);
|
|
58
|
+
|
|
59
|
+
assertAtLeastOneStageRemain(workflow.stages, { created, deleted });
|
|
60
|
+
|
|
61
|
+
return strapi.db.transaction(async () => {
|
|
62
|
+
const newStages = await this.createMany(created, { fields: ['id'] });
|
|
63
|
+
const stagesIds = stages.map((stage) => stage.id ?? newStages.shift().id);
|
|
64
|
+
|
|
65
|
+
await mapAsync(updated, (stage) => this.update(stage.id, stage));
|
|
66
|
+
await mapAsync(deleted, (stage) => this.delete(stage.id));
|
|
67
|
+
return workflowsService.update(workflowId, {
|
|
68
|
+
stages: stagesIds,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Update the stage of an entity
|
|
75
|
+
*
|
|
76
|
+
* @param {object} entityInfo
|
|
77
|
+
* @param {number} entityInfo.id - Entity id
|
|
78
|
+
* @param {string} entityInfo.modelUID - the content-type of the entity
|
|
79
|
+
* @param {number} stageId - The id of the stage to assign to the entity
|
|
80
|
+
*/
|
|
81
|
+
updateEntity(entityInfo, stageId) {
|
|
82
|
+
return strapi.entityService.update(entityInfo.modelUID, entityInfo.id, {
|
|
83
|
+
data: { [ENTITY_STAGE_ATTRIBUTE]: stageId },
|
|
84
|
+
populate: [ENTITY_STAGE_ATTRIBUTE],
|
|
85
|
+
});
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Compares two arrays of stages and returns an object indicating the differences.
|
|
92
|
+
*
|
|
93
|
+
* The function compares the `id` properties of each stage in `sourceStages` and `comparisonStages` to determine if the stage is present in both arrays.
|
|
94
|
+
* If a stage with the same `id` is found in both arrays but the `name` property is different, the stage is considered updated.
|
|
95
|
+
* If a stage with a particular `id` is only found in `comparisonStages`, it is considered created.
|
|
96
|
+
* If a stage with a particular `id` is only found in `sourceStages`, it is considered deleted.
|
|
97
|
+
*
|
|
98
|
+
* @typedef {{id: Number, name: String, workflow: Number}} Stage
|
|
99
|
+
* @typedef {{created: Stage[], updated: Stage[], deleted: Stage[]}} DiffStages
|
|
100
|
+
*
|
|
101
|
+
* The DiffStages object has three properties: `created`, `updated`, and `deleted`.
|
|
102
|
+
* `created` is an array of stages that are in `comparisonStages` but not in `sourceStages`.
|
|
103
|
+
* `updated` is an array of stages that have different names in `comparisonStages` and `sourceStages`.
|
|
104
|
+
* `deleted` is an array of stages that are in `sourceStages` but not in `comparisonStages`.
|
|
105
|
+
*
|
|
106
|
+
* @param {Stage[]} sourceStages
|
|
107
|
+
* @param {Stage[]} comparisonStages
|
|
108
|
+
* @returns { DiffStages }
|
|
109
|
+
*/
|
|
110
|
+
function getDiffBetweenStages(sourceStages, comparisonStages) {
|
|
111
|
+
const result = comparisonStages.reduce(
|
|
112
|
+
(acc, stageToCompare) => {
|
|
113
|
+
const srcStage = sourceStages.find((stage) => stage.id === stageToCompare.id);
|
|
114
|
+
|
|
115
|
+
if (!srcStage) {
|
|
116
|
+
acc.created.push(stageToCompare);
|
|
117
|
+
} else if (srcStage.name !== stageToCompare.name) {
|
|
118
|
+
acc.updated.push(stageToCompare);
|
|
119
|
+
}
|
|
120
|
+
return acc;
|
|
121
|
+
},
|
|
122
|
+
{ created: [], updated: [] }
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
result.deleted = sourceStages.filter(
|
|
126
|
+
(srcStage) => !comparisonStages.some((cmpStage) => cmpStage.id === srcStage.id)
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Asserts that at least one stage remains in the workflow after applying deletions and additions.
|
|
134
|
+
*
|
|
135
|
+
* @param {Array} workflowStages - An array of stages in the current workflow.
|
|
136
|
+
* @param {Object} diffStages - An object containing the stages to be deleted and created.
|
|
137
|
+
* @param {Array} diffStages.deleted - An array of stages that are planned to be deleted from the workflow.
|
|
138
|
+
* @param {Array} diffStages.created - An array of stages that are planned to be created in the workflow.
|
|
139
|
+
*
|
|
140
|
+
* @throws {ApplicationError} If the number of remaining stages in the workflow after applying deletions and additions is less than 1.
|
|
141
|
+
*/
|
|
142
|
+
function assertAtLeastOneStageRemain(workflowStages, diffStages) {
|
|
143
|
+
const remainingStagesCount =
|
|
144
|
+
workflowStages.length - diffStages.deleted.length + diffStages.created.length;
|
|
145
|
+
if (remainingStagesCount < 1) {
|
|
146
|
+
throw new ApplicationError('At least one stage must remain in the workflow.');
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { WORKFLOW_MODEL_UID } = require('../../constants/workflows');
|
|
4
|
+
|
|
5
|
+
module.exports = ({ strapi }) => ({
|
|
6
|
+
find(opts) {
|
|
7
|
+
return strapi.entityService.findMany(WORKFLOW_MODEL_UID, opts);
|
|
8
|
+
},
|
|
9
|
+
|
|
10
|
+
findById(id, opts) {
|
|
11
|
+
return strapi.entityService.findOne(WORKFLOW_MODEL_UID, id, opts);
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
create(workflowData) {
|
|
15
|
+
return strapi.entityService.create(WORKFLOW_MODEL_UID, { data: workflowData });
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
count() {
|
|
19
|
+
return strapi.entityService.count(WORKFLOW_MODEL_UID);
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
update(id, workflowData) {
|
|
23
|
+
return strapi.entityService.update(WORKFLOW_MODEL_UID, id, { data: workflowData });
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { WORKFLOW_MODEL_UID } = require('../constants/workflows');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Checks if a content type has review workflows enabled.
|
|
7
|
+
* @param {string|Object} contentType - Either the modelUID of the content type, or the content type object.
|
|
8
|
+
* @returns {boolean} Whether review workflows are enabled for the specified content type.
|
|
9
|
+
*/
|
|
10
|
+
function hasReviewWorkflow({ strapi }, contentType) {
|
|
11
|
+
if (typeof contentType === 'string') {
|
|
12
|
+
// If the input is a string, assume it's the modelUID of the content type and retrieve the corresponding object.
|
|
13
|
+
return hasReviewWorkflow({ strapi }, strapi.getModel(contentType));
|
|
14
|
+
}
|
|
15
|
+
// Otherwise, assume it's the content type object itself and return its `reviewWorkflows` option if it exists.
|
|
16
|
+
return contentType?.options?.reviewWorkflows || false;
|
|
17
|
+
}
|
|
18
|
+
// TODO To be refactored when multiple workflows are added
|
|
19
|
+
const getDefaultWorkflow = async ({ strapi }) =>
|
|
20
|
+
strapi.query(WORKFLOW_MODEL_UID).findOne({ populate: ['stages'] });
|
|
21
|
+
|
|
22
|
+
module.exports = {
|
|
23
|
+
hasReviewWorkflow,
|
|
24
|
+
getDefaultWorkflow,
|
|
25
|
+
};
|