@strapi/review-workflows 0.0.0-experimental.9df68962083938acba06546a7901c68a63266aec → 0.0.0-experimental.a13c58eec89ab119f0e381fb79c0252979e9c125

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.
Files changed (81) hide show
  1. package/LICENSE +12 -17
  2. package/dist/_chunks/{Layout-D292CXD6.mjs → Layout-BIIxkAtf.mjs} +9 -10
  3. package/dist/_chunks/Layout-BIIxkAtf.mjs.map +1 -0
  4. package/dist/_chunks/{Layout-BWzD-Tfe.js → Layout-BWNbQOes.js} +9 -10
  5. package/dist/_chunks/Layout-BWNbQOes.js.map +1 -0
  6. package/dist/_chunks/{en-xcewH2pC.js → en-CYgjfSep.js} +5 -2
  7. package/dist/_chunks/en-CYgjfSep.js.map +1 -0
  8. package/dist/_chunks/{en-D9ZrQAV6.mjs → en-D9dxziEb.mjs} +5 -2
  9. package/dist/_chunks/en-D9dxziEb.mjs.map +1 -0
  10. package/dist/_chunks/{_id-CoX9yljN.js → id-CaHzMdxL.js} +103 -80
  11. package/dist/_chunks/id-CaHzMdxL.js.map +1 -0
  12. package/dist/_chunks/{_id-DqEUzU_u.mjs → id-CtcCl3zh.mjs} +103 -80
  13. package/dist/_chunks/id-CtcCl3zh.mjs.map +1 -0
  14. package/dist/_chunks/{index-D-KMrml_.js → index-BX5MyocW.js} +79 -68
  15. package/dist/_chunks/index-BX5MyocW.js.map +1 -0
  16. package/dist/_chunks/{index-udedGkii.mjs → index-ByTEmYbc.mjs} +80 -69
  17. package/dist/_chunks/index-ByTEmYbc.mjs.map +1 -0
  18. package/dist/_chunks/{index-2nkLt-AE.mjs → index-CGmh3cED.mjs} +11 -31
  19. package/dist/_chunks/index-CGmh3cED.mjs.map +1 -0
  20. package/dist/_chunks/{index-CDMcvtW9.js → index-DKLk-Z5E.js} +9 -29
  21. package/dist/_chunks/index-DKLk-Z5E.js.map +1 -0
  22. package/dist/_chunks/{router-C19H1Rju.js → router-Bt6JHY-e.js} +3 -3
  23. package/dist/_chunks/router-Bt6JHY-e.js.map +1 -0
  24. package/dist/_chunks/{router-bmjk-Tpf.mjs → router-Cr3nulh9.mjs} +3 -3
  25. package/dist/_chunks/router-Cr3nulh9.mjs.map +1 -0
  26. package/dist/admin/index.js +1 -1
  27. package/dist/admin/index.mjs +1 -1
  28. package/dist/admin/src/services/admin.d.ts +1 -1
  29. package/dist/admin/src/services/api.d.ts +1 -1
  30. package/dist/admin/src/services/content-manager.d.ts +4 -4
  31. package/dist/admin/src/services/settings.d.ts +1739 -9
  32. package/dist/server/index.js +165 -84
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/server/index.mjs +165 -84
  35. package/dist/server/index.mjs.map +1 -1
  36. package/dist/server/src/bootstrap.d.ts.map +1 -1
  37. package/dist/server/src/constants/workflows.d.ts +1 -0
  38. package/dist/server/src/constants/workflows.d.ts.map +1 -1
  39. package/dist/server/src/content-types/index.d.ts +6 -0
  40. package/dist/server/src/content-types/index.d.ts.map +1 -1
  41. package/dist/server/src/content-types/workflow/index.d.ts +6 -0
  42. package/dist/server/src/content-types/workflow/index.d.ts.map +1 -1
  43. package/dist/server/src/controllers/assignees.d.ts.map +1 -1
  44. package/dist/server/src/controllers/index.d.ts +0 -1
  45. package/dist/server/src/controllers/index.d.ts.map +1 -1
  46. package/dist/server/src/controllers/stages.d.ts.map +1 -1
  47. package/dist/server/src/controllers/workflows.d.ts +0 -7
  48. package/dist/server/src/controllers/workflows.d.ts.map +1 -1
  49. package/dist/server/src/index.d.ts +16 -4
  50. package/dist/server/src/index.d.ts.map +1 -1
  51. package/dist/server/src/register.d.ts.map +1 -1
  52. package/dist/server/src/routes/review-workflows.d.ts.map +1 -1
  53. package/dist/server/src/services/document-service-middleware.d.ts +1 -0
  54. package/dist/server/src/services/document-service-middleware.d.ts.map +1 -1
  55. package/dist/server/src/services/index.d.ts +4 -3
  56. package/dist/server/src/services/index.d.ts.map +1 -1
  57. package/dist/server/src/services/metrics/index.d.ts +4 -4
  58. package/dist/server/src/services/metrics/index.d.ts.map +1 -1
  59. package/dist/server/src/services/metrics/weekly-metrics.d.ts.map +1 -1
  60. package/dist/server/src/services/stages.d.ts +2 -7
  61. package/dist/server/src/services/stages.d.ts.map +1 -1
  62. package/dist/server/src/services/workflows.d.ts.map +1 -1
  63. package/dist/server/src/validation/review-workflows.d.ts +4 -0
  64. package/dist/server/src/validation/review-workflows.d.ts.map +1 -1
  65. package/dist/shared/contracts/review-workflows.d.ts +9 -17
  66. package/dist/shared/contracts/review-workflows.d.ts.map +1 -1
  67. package/package.json +10 -10
  68. package/dist/_chunks/Layout-BWzD-Tfe.js.map +0 -1
  69. package/dist/_chunks/Layout-D292CXD6.mjs.map +0 -1
  70. package/dist/_chunks/_id-CoX9yljN.js.map +0 -1
  71. package/dist/_chunks/_id-DqEUzU_u.mjs.map +0 -1
  72. package/dist/_chunks/en-D9ZrQAV6.mjs.map +0 -1
  73. package/dist/_chunks/en-xcewH2pC.js.map +0 -1
  74. package/dist/_chunks/index-2nkLt-AE.mjs.map +0 -1
  75. package/dist/_chunks/index-CDMcvtW9.js.map +0 -1
  76. package/dist/_chunks/index-D-KMrml_.js.map +0 -1
  77. package/dist/_chunks/index-udedGkii.mjs.map +0 -1
  78. package/dist/_chunks/router-C19H1Rju.js.map +0 -1
  79. package/dist/_chunks/router-bmjk-Tpf.mjs.map +0 -1
  80. package/strapi-server.js +0 -3
  81. /package/dist/admin/src/routes/settings/{:id.d.ts → id.d.ts} +0 -0
@@ -6387,7 +6387,8 @@ const WORKFLOW_POPULATE = {
6387
6387
  }
6388
6388
  }
6389
6389
  }
6390
- }
6390
+ },
6391
+ stageRequiredToPublish: true
6391
6392
  };
6392
6393
  function checkVersionThreshold(startVersion, currentVersion, thresholdVersion) {
6393
6394
  return semver$1.gte(currentVersion, thresholdVersion) && semver$1.lt(startVersion, thresholdVersion);
@@ -6562,8 +6563,40 @@ function extendReviewWorkflowContentTypes({ strapi: strapi2 }) {
6562
6563
  });
6563
6564
  }
6564
6565
  }
6566
+ function persistRWOnDowngrade({ strapi: strapi2 }) {
6567
+ const { removePersistedTablesWithSuffix, persistTables } = getAdminService("persist-tables");
6568
+ return async ({ contentTypes: contentTypes2 }) => {
6569
+ const getStageTableToPersist = (contentTypeUID) => {
6570
+ const { attributes, tableName } = strapi2.db.metadata.get(contentTypeUID);
6571
+ const joinTableName = attributes[ENTITY_STAGE_ATTRIBUTE].joinTable.name;
6572
+ return {
6573
+ name: joinTableName,
6574
+ dependsOn: [{ name: tableName }]
6575
+ };
6576
+ };
6577
+ const getAssigneeTableToPersist = (contentTypeUID) => {
6578
+ const { attributes, tableName } = strapi2.db.metadata.get(contentTypeUID);
6579
+ const joinTableName = attributes[ENTITY_ASSIGNEE_ATTRIBUTE].joinTable.name;
6580
+ return {
6581
+ name: joinTableName,
6582
+ dependsOn: [{ name: tableName }]
6583
+ };
6584
+ };
6585
+ const enabledRWContentTypes = fp.pipe([
6586
+ getVisibleContentTypesUID,
6587
+ fp.filter((uid) => hasStageAttribute(contentTypes2[uid]))
6588
+ ])(contentTypes2);
6589
+ const stageJoinTablesToPersist = enabledRWContentTypes.map(getStageTableToPersist);
6590
+ await removePersistedTablesWithSuffix("_strapi_stage_lnk");
6591
+ await persistTables(stageJoinTablesToPersist);
6592
+ const assigneeJoinTablesToPersist = enabledRWContentTypes.map(getAssigneeTableToPersist);
6593
+ await removePersistedTablesWithSuffix("_strapi_assignee_lnk");
6594
+ await persistTables(assigneeJoinTablesToPersist);
6595
+ };
6596
+ }
6565
6597
  const register = async ({ strapi: strapi2 }) => {
6566
6598
  strapi2.hook("strapi::content-types.beforeSync").register(migrateStageAttribute);
6599
+ strapi2.hook("strapi::content-types.afterSync").register(persistRWOnDowngrade({ strapi: strapi2 }));
6567
6600
  strapi2.hook("strapi::content-types.afterSync").register(migrateReviewWorkflowStagesColor).register(migrateReviewWorkflowStagesRoles).register(migrateReviewWorkflowName).register(migrateWorkflowsContentTypes).register(migrateDeletedCTInWorkflows);
6568
6601
  reviewWorkflowsMiddlewares.contentTypeMiddleware(strapi2);
6569
6602
  extendReviewWorkflowContentTypes({ strapi: strapi2 });
@@ -6608,6 +6641,12 @@ const workflow = {
6608
6641
  relation: "oneToMany",
6609
6642
  mappedBy: "workflow"
6610
6643
  },
6644
+ stageRequiredToPublish: {
6645
+ type: "relation",
6646
+ target: "plugin::review-workflows.workflow-stage",
6647
+ relation: "oneToOne",
6648
+ required: false
6649
+ },
6611
6650
  contentTypes: {
6612
6651
  type: "json",
6613
6652
  required: true,
@@ -6757,6 +6796,7 @@ const bootstrap = async (args) => {
6757
6796
  const docsMiddlewares = getService("document-service-middlewares");
6758
6797
  strapi.documents.use(docsMiddlewares.assignStageOnCreate);
6759
6798
  strapi.documents.use(docsMiddlewares.handleStageOnUpdate);
6799
+ strapi.documents.use(docsMiddlewares.checkStageBeforePublish);
6760
6800
  };
6761
6801
  const destroy = async ({ strapi: strapi2 }) => {
6762
6802
  };
@@ -6838,23 +6878,6 @@ const reviewWorkflows = {
6838
6878
  ]
6839
6879
  }
6840
6880
  },
6841
- {
6842
- method: "GET",
6843
- path: "/workflows/:id",
6844
- handler: "workflows.findById",
6845
- config: {
6846
- middlewares: [enableFeatureMiddleware("review-workflows")],
6847
- policies: [
6848
- "admin::isAuthenticatedAdmin",
6849
- {
6850
- name: "admin::hasPermissions",
6851
- config: {
6852
- actions: ["admin::review-workflows.read"]
6853
- }
6854
- }
6855
- ]
6856
- }
6857
- },
6858
6881
  {
6859
6882
  method: "GET",
6860
6883
  path: "/workflows/:workflow_id/stages",
@@ -6918,7 +6941,7 @@ const reviewWorkflows = {
6918
6941
  {
6919
6942
  name: "admin::hasPermissions",
6920
6943
  config: {
6921
- actions: ["admin::users.read", "admin::review-workflows.read"]
6944
+ actions: ["admin::users.read"]
6922
6945
  }
6923
6946
  }
6924
6947
  ]
@@ -7006,9 +7029,9 @@ const processFilters = ({ strapi: strapi2 }, filters = {}) => {
7006
7029
  };
7007
7030
  const processPopulate = (populate) => {
7008
7031
  if (!populate) {
7009
- return populate;
7032
+ return WORKFLOW_POPULATE;
7010
7033
  }
7011
- return WORKFLOW_POPULATE;
7034
+ return populate;
7012
7035
  };
7013
7036
  const workflows$1 = ({ strapi: strapi2 }) => {
7014
7037
  const workflowsContentTypes = workflowsContentTypesFactory({ strapi: strapi2 });
@@ -7059,14 +7082,27 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7059
7082
  const stages2 = await getService("stages", { strapi: strapi2 }).createMany(opts.data.stages);
7060
7083
  const mapIds = fp.map(fp.get("id"));
7061
7084
  createOpts = fp.set("data.stages", mapIds(stages2), createOpts);
7085
+ if (opts.data.stageRequiredToPublishName) {
7086
+ const stageRequiredToPublish = stages2.find(
7087
+ (stage) => stage.name === opts.data.stageRequiredToPublishName
7088
+ );
7089
+ if (!stageRequiredToPublish) {
7090
+ throw new utils.errors.ApplicationError("Stage required to publish does not exist");
7091
+ }
7092
+ createOpts = fp.set("data.stageRequiredToPublish", stageRequiredToPublish.id, createOpts);
7093
+ }
7062
7094
  if (opts.data.contentTypes) {
7063
7095
  await workflowsContentTypes.migrate({
7064
7096
  destContentTypes: opts.data.contentTypes,
7065
7097
  stageId: stages2[0].id
7066
7098
  });
7067
7099
  }
7068
- metrics.sendDidCreateWorkflow();
7069
- return strapi2.db.query(WORKFLOW_MODEL_UID).create(strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, createOpts));
7100
+ const createdWorkflow = await strapi2.db.query(WORKFLOW_MODEL_UID).create(strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, createOpts));
7101
+ metrics.sendDidCreateWorkflow(createdWorkflow.id, !!opts.data.stageRequiredToPublishName);
7102
+ if (opts.data.stageRequiredToPublishName) {
7103
+ await strapi2.plugin("content-releases").service("release-action").validateActionsByContentTypes(opts.data.contentTypes);
7104
+ }
7105
+ return createdWorkflow;
7070
7106
  });
7071
7107
  },
7072
7108
  /**
@@ -7079,6 +7115,7 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7079
7115
  async update(workflow2, opts) {
7080
7116
  const stageService = getService("stages", { strapi: strapi2 });
7081
7117
  let updateOpts = { ...opts, populate: { ...WORKFLOW_POPULATE } };
7118
+ let updatedStages = [];
7082
7119
  let updatedStageIds;
7083
7120
  await workflowValidator.validateWorkflowCount();
7084
7121
  return strapi2.db.transaction(async () => {
@@ -7087,9 +7124,28 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7087
7124
  opts.data.stages.forEach(
7088
7125
  (stage) => this.assertStageBelongsToWorkflow(stage.id, workflow2)
7089
7126
  );
7090
- updatedStageIds = await stageService.replaceStages(workflow2.stages, opts.data.stages, workflow2.contentTypes).then((stages2) => stages2.map((stage) => stage.id));
7127
+ updatedStages = await stageService.replaceStages(
7128
+ workflow2.stages,
7129
+ opts.data.stages,
7130
+ workflow2.contentTypes
7131
+ );
7132
+ updatedStageIds = updatedStages.map((stage) => stage.id);
7091
7133
  updateOpts = fp.set("data.stages", updatedStageIds, updateOpts);
7092
7134
  }
7135
+ if (opts.data.stageRequiredToPublishName !== void 0) {
7136
+ const stages2 = updatedStages ?? workflow2.stages;
7137
+ if (opts.data.stageRequiredToPublishName === null) {
7138
+ updateOpts = fp.set("data.stageRequiredToPublish", null, updateOpts);
7139
+ } else {
7140
+ const stageRequiredToPublish = stages2.find(
7141
+ (stage) => stage.name === opts.data.stageRequiredToPublishName
7142
+ );
7143
+ if (!stageRequiredToPublish) {
7144
+ throw new utils.errors.ApplicationError("Stage required to publish does not exist");
7145
+ }
7146
+ updateOpts = fp.set("data.stageRequiredToPublish", stageRequiredToPublish.id, updateOpts);
7147
+ }
7148
+ }
7093
7149
  if (opts.data.contentTypes) {
7094
7150
  await workflowsContentTypes.migrate({
7095
7151
  srcContentTypes: workflow2.contentTypes,
@@ -7097,12 +7153,17 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7097
7153
  stageId: updatedStageIds ? updatedStageIds[0] : workflow2.stages[0].id
7098
7154
  });
7099
7155
  }
7100
- metrics.sendDidEditWorkflow();
7156
+ metrics.sendDidEditWorkflow(workflow2.id, !!opts.data.stageRequiredToPublishName);
7101
7157
  const query = strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, updateOpts);
7102
- return strapi2.db.query(WORKFLOW_MODEL_UID).update({
7158
+ const updatedWorkflow = await strapi2.db.query(WORKFLOW_MODEL_UID).update({
7103
7159
  ...query,
7104
7160
  where: { id: workflow2.id }
7105
7161
  });
7162
+ await strapi2.plugin("content-releases").service("release-action").validateActionsByContentTypes([
7163
+ ...workflow2.contentTypes,
7164
+ ...opts.data.contentTypes || []
7165
+ ]);
7166
+ return updatedWorkflow;
7106
7167
  });
7107
7168
  },
7108
7169
  /**
@@ -7125,10 +7186,12 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7125
7186
  destContentTypes: []
7126
7187
  });
7127
7188
  const query = strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, opts);
7128
- return strapi2.db.query(WORKFLOW_MODEL_UID).delete({
7189
+ const deletedWorkflow = await strapi2.db.query(WORKFLOW_MODEL_UID).delete({
7129
7190
  ...query,
7130
7191
  where: { id: workflow2.id }
7131
7192
  });
7193
+ await strapi2.plugin("content-releases").service("release-action").validateActionsByContentTypes(workflow2.contentTypes);
7194
+ return deletedWorkflow;
7132
7195
  });
7133
7196
  },
7134
7197
  /**
@@ -7339,23 +7402,19 @@ const stages$1 = ({ strapi: strapi2 }) => {
7339
7402
  },
7340
7403
  /**
7341
7404
  * Update the stage of an entity
7342
- *
7343
- * @param {object} entityInfo
7344
- * @param {number} entityInfo.id - Entity id
7345
- * @param {string} entityInfo.modelUID - the content-type of the entity
7346
- * @param {number} stageId - The id of the stage to assign to the entity
7347
7405
  */
7348
- async updateEntity(entityInfo, stageId) {
7406
+ async updateEntity(documentId, locale, model, stageId) {
7349
7407
  const stage = await this.findById(stageId);
7350
7408
  await workflowValidator.validateWorkflowCount();
7351
7409
  if (!stage) {
7352
7410
  throw new ApplicationError$2(`Selected stage does not exist`);
7353
7411
  }
7354
- const entity = await strapi2.db.query(entityInfo.modelUID).update({
7355
- where: {
7356
- id: entityInfo.id
7357
- },
7358
- data: { [ENTITY_STAGE_ATTRIBUTE]: stageId },
7412
+ const entity = await strapi2.documents(model).update({
7413
+ documentId,
7414
+ locale,
7415
+ // Stage doesn't have DP or i18n enabled, connecting it through the `id`
7416
+ // will be safer than relying on the `documentId` + `locale` + `status` transformation
7417
+ data: { [ENTITY_STAGE_ATTRIBUTE]: fp.pick(["id"], stage) },
7359
7418
  populate: [ENTITY_STAGE_ATTRIBUTE]
7360
7419
  });
7361
7420
  metrics.sendDidChangeEntryStage();
@@ -7602,11 +7661,11 @@ const sendDidDeleteStage = async () => {
7602
7661
  const sendDidChangeEntryStage = async () => {
7603
7662
  strapi.telemetry.send("didChangeEntryStage", {});
7604
7663
  };
7605
- const sendDidCreateWorkflow = async () => {
7606
- strapi.telemetry.send("didCreateWorkflow", {});
7664
+ const sendDidCreateWorkflow = async (workflowId, hasRequiredStageToPublish) => {
7665
+ strapi.telemetry.send("didCreateWorkflow", { workflowId, hasRequiredStageToPublish });
7607
7666
  };
7608
- const sendDidEditWorkflow = async () => {
7609
- strapi.telemetry.send("didEditWorkflow", {});
7667
+ const sendDidEditWorkflow = async (workflowId, hasRequiredStageToPublish) => {
7668
+ strapi.telemetry.send("didEditWorkflow", { workflowId, hasRequiredStageToPublish });
7610
7669
  };
7611
7670
  const sendDidEditAssignee = async (fromId, toId) => {
7612
7671
  strapi.telemetry.send("didEditAssignee", { from: fromId, to: toId });
@@ -7631,13 +7690,13 @@ const reviewWorkflowsMetrics = {
7631
7690
  sendDidSendReviewWorkflowPropertiesOnceAWeek,
7632
7691
  sendDidEditAssignee
7633
7692
  };
7634
- function _typeof(obj) {
7693
+ function _typeof(o) {
7635
7694
  "@babel/helpers - typeof";
7636
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj2) {
7637
- return typeof obj2;
7638
- } : function(obj2) {
7639
- return obj2 && "function" == typeof Symbol && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
7640
- }, _typeof(obj);
7695
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o2) {
7696
+ return typeof o2;
7697
+ } : function(o2) {
7698
+ return o2 && "function" == typeof Symbol && o2.constructor === Symbol && o2 !== Symbol.prototype ? "symbol" : typeof o2;
7699
+ }, _typeof(o);
7641
7700
  }
7642
7701
  function toInteger(dirtyNumber) {
7643
7702
  if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
@@ -7772,7 +7831,12 @@ const reviewWorkflowsWeeklyMetrics = ({ strapi: strapi2 }) => {
7772
7831
  },
7773
7832
  async registerCron() {
7774
7833
  const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
7775
- strapi2.cron.add({ [weeklySchedule]: this.sendMetrics.bind(this) });
7834
+ strapi2.cron.add({
7835
+ reviewWorkflowsWeekly: {
7836
+ task: this.sendMetrics.bind(this),
7837
+ options: weeklySchedule
7838
+ }
7839
+ });
7776
7840
  }
7777
7841
  };
7778
7842
  };
@@ -7828,6 +7892,7 @@ const handleStageOnUpdate = async (ctx, next) => {
7828
7892
  strapi.eventHub.emit(WORKFLOW_UPDATE_STAGE, {
7829
7893
  model: model.modelName,
7830
7894
  uid: model.uid,
7895
+ // TODO v6: Rename to "entry", which is what is used for regular CRUD updates
7831
7896
  entity: {
7832
7897
  // @ts-expect-error
7833
7898
  id: result?.id,
@@ -7853,9 +7918,27 @@ const handleStageOnUpdate = async (ctx, next) => {
7853
7918
  }
7854
7919
  return next();
7855
7920
  };
7921
+ const checkStageBeforePublish = async (ctx, next) => {
7922
+ if (ctx.action !== "publish") {
7923
+ return next();
7924
+ }
7925
+ const workflow2 = await getService("workflows").getAssignedWorkflow(ctx.contentType.uid, {
7926
+ populate: "stageRequiredToPublish"
7927
+ });
7928
+ if (!workflow2 || !workflow2.stageRequiredToPublish) {
7929
+ return next();
7930
+ }
7931
+ const { documentId } = ctx.params;
7932
+ const entryStage = await getEntityStage(ctx.contentType.uid, documentId, ctx.params);
7933
+ if (entryStage.id !== workflow2.stageRequiredToPublish.id) {
7934
+ throw new utils.errors.ValidationError("Entry is not at the required stage to publish");
7935
+ }
7936
+ return next();
7937
+ };
7856
7938
  const documentServiceMiddleware = () => ({
7857
7939
  assignStageOnCreate,
7858
- handleStageOnUpdate
7940
+ handleStageOnUpdate,
7941
+ checkStageBeforePublish
7859
7942
  });
7860
7943
  const services = {
7861
7944
  workflows: workflows$1,
@@ -7905,12 +7988,14 @@ const validateContentTypes = utils.yup.array().of(
7905
7988
  const validateWorkflowCreateSchema = utils.yup.object().shape({
7906
7989
  name: utils.yup.string().max(255).min(1, "Workflow name can not be empty").required(),
7907
7990
  stages: utils.yup.array().of(stageObject).uniqueProperty("name", "Stage name must be unique").min(1, "Can not create a workflow without stages").max(200, "Can not have more than 200 stages").required("Can not create a workflow without stages"),
7908
- contentTypes: validateContentTypes
7991
+ contentTypes: validateContentTypes,
7992
+ stageRequiredToPublishName: utils.yup.string().min(1).nullable()
7909
7993
  });
7910
7994
  const validateWorkflowUpdateSchema = utils.yup.object().shape({
7911
7995
  name: utils.yup.string().max(255).min(1, "Workflow name can not be empty"),
7912
7996
  stages: utils.yup.array().of(stageObject).uniqueProperty("name", "Stage name must be unique").min(1, "Can not update a workflow without stages").max(200, "Can not have more than 200 stages"),
7913
- contentTypes: validateContentTypes
7997
+ contentTypes: validateContentTypes,
7998
+ stageRequiredToPublishName: utils.yup.string().min(1).nullable()
7914
7999
  });
7915
8000
  const validateUpdateAssigneeOnEntitySchema = utils.yup.object().shape({
7916
8001
  id: utils.yup.number().integer().min(1).nullable()
@@ -8029,30 +8114,6 @@ const workflows = {
8029
8114
  workflowCount
8030
8115
  }
8031
8116
  };
8032
- },
8033
- /**
8034
- * Get one workflow based on its id contained in request parameters
8035
- * Returns count of workflows in meta, used to prevent workflow edition when
8036
- * max workflow count is reached for the current plan
8037
- * @param {import('koa').BaseContext} ctx - koa context
8038
- */
8039
- async findById(ctx) {
8040
- const { id } = ctx.params;
8041
- const { query } = ctx.request;
8042
- const { sanitizeOutput, sanitizedQuery } = getWorkflowsPermissionChecker(
8043
- { strapi },
8044
- ctx.state.userAbility
8045
- );
8046
- const { populate } = await sanitizedQuery.read(query);
8047
- const workflowService = getService("workflows");
8048
- const [workflow2, workflowCount] = await Promise.all([
8049
- workflowService.findById(id, { populate }).then(formatWorkflowToAdmin),
8050
- workflowService.count()
8051
- ]);
8052
- ctx.body = {
8053
- data: await sanitizeOutput(workflow2),
8054
- meta: { workflowCount }
8055
- };
8056
8117
  }
8057
8118
  };
8058
8119
  function sanitizeStage({ strapi: strapi2 }, userAbility) {
@@ -8137,7 +8198,12 @@ const stages = {
8137
8198
  );
8138
8199
  const workflow2 = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
8139
8200
  workflowService.assertStageBelongsToWorkflow(stageId, workflow2);
8140
- const updatedEntity = await stagesService.updateEntity({ id: entity.id, modelUID }, stageId);
8201
+ const updatedEntity = await stagesService.updateEntity(
8202
+ entity.documentId,
8203
+ entity.locale,
8204
+ modelUID,
8205
+ stageId
8206
+ );
8141
8207
  ctx.body = { data: await sanitizeOutput(updatedEntity) };
8142
8208
  },
8143
8209
  /**
@@ -8157,10 +8223,9 @@ const stages = {
8157
8223
  if (strapi.plugin("content-manager").service("permission-checker").create({ userAbility: ctx.state.userAbility, model: modelUID }).cannot.read()) {
8158
8224
  return ctx.forbidden();
8159
8225
  }
8160
- const locale = await validateLocale(query?.locale);
8226
+ const locale = await validateLocale(query?.locale) ?? void 0;
8161
8227
  const entity = await strapi.documents(modelUID).findOne({
8162
8228
  documentId,
8163
- // @ts-expect-error - locale should be also null in the doc service types
8164
8229
  locale,
8165
8230
  populate: [ENTITY_STAGE_ATTRIBUTE]
8166
8231
  });
@@ -8169,12 +8234,13 @@ const stages = {
8169
8234
  }
8170
8235
  const entityStageId = entity[ENTITY_STAGE_ATTRIBUTE]?.id;
8171
8236
  const canTransition = stagePermissions2.can(STAGE_TRANSITION_UID, entityStageId);
8172
- const [workflowCount, { stages: workflowStages }] = await Promise.all([
8237
+ const [workflowCount, workflowResult] = await Promise.all([
8173
8238
  workflowService.count(),
8174
8239
  workflowService.getAssignedWorkflow(modelUID, {
8175
8240
  populate: "stages"
8176
8241
  })
8177
8242
  ]);
8243
+ const workflowStages = workflowResult ? workflowResult.stages : [];
8178
8244
  const meta = {
8179
8245
  stageCount: workflowStages.length,
8180
8246
  workflowCount
@@ -8210,22 +8276,37 @@ const assignees = {
8210
8276
  async updateEntity(ctx) {
8211
8277
  const assigneeService = getService("assignees");
8212
8278
  const workflowService = getService("workflows");
8279
+ const stagePermissions2 = getService("stage-permissions");
8213
8280
  const { model_uid: model, id: documentId } = ctx.params;
8214
- const { locale } = ctx.request.query || {};
8281
+ const locale = await validateLocale(ctx.request.query?.locale) ?? void 0;
8215
8282
  const { sanitizeOutput } = strapi.plugin("content-manager").service("permission-checker").create({ userAbility: ctx.state.userAbility, model });
8283
+ const entity = await strapi.documents(model).findOne({
8284
+ documentId,
8285
+ locale,
8286
+ populate: [ENTITY_STAGE_ATTRIBUTE]
8287
+ });
8288
+ if (!entity) {
8289
+ ctx.throw(404, "Entity not found");
8290
+ }
8291
+ const canTransitionStage = stagePermissions2.can(
8292
+ STAGE_TRANSITION_UID,
8293
+ entity[ENTITY_STAGE_ATTRIBUTE]?.id
8294
+ );
8295
+ if (!canTransitionStage) {
8296
+ ctx.throw(403, "Stage transition permission is required");
8297
+ }
8216
8298
  const { id: assigneeId } = await validateUpdateAssigneeOnEntity(
8217
8299
  ctx.request?.body?.data,
8218
8300
  "You should pass a valid id to the body of the put request."
8219
8301
  );
8220
- await validateLocale(locale);
8221
8302
  await workflowService.assertContentTypeBelongsToWorkflow(model);
8222
- const entity = await assigneeService.updateEntityAssignee(
8303
+ const updatedEntity = await assigneeService.updateEntityAssignee(
8223
8304
  documentId,
8224
8305
  locale || null,
8225
8306
  model,
8226
8307
  assigneeId
8227
8308
  );
8228
- ctx.body = { data: await sanitizeOutput(entity) };
8309
+ ctx.body = { data: await sanitizeOutput(updatedEntity) };
8229
8310
  }
8230
8311
  };
8231
8312
  const controllers = {