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