@strapi/review-workflows 0.0.0-experimental.abc → 0.0.0-experimental.af7e4e2471a04cc7f17b8ed3474530810efc02bc

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 (74) hide show
  1. package/dist/_chunks/{Layout-AREWDuuq.js → Layout-B4fpKB9J.js} +3 -3
  2. package/dist/_chunks/Layout-B4fpKB9J.js.map +1 -0
  3. package/dist/_chunks/{Layout-D5aa9iUm.mjs → Layout-Dko22Aly.mjs} +3 -3
  4. package/dist/_chunks/Layout-Dko22Aly.mjs.map +1 -0
  5. package/dist/_chunks/{en-xcewH2pC.js → en-CYgjfSep.js} +5 -2
  6. package/dist/_chunks/en-CYgjfSep.js.map +1 -0
  7. package/dist/_chunks/{en-D9ZrQAV6.mjs → en-D9dxziEb.mjs} +5 -2
  8. package/dist/_chunks/en-D9dxziEb.mjs.map +1 -0
  9. package/dist/_chunks/{_id-C7pCAzXV.js → id-BKq7JAqZ.js} +76 -17
  10. package/dist/_chunks/id-BKq7JAqZ.js.map +1 -0
  11. package/dist/_chunks/{_id-DDNHKV_W.mjs → id-DINFSsrh.mjs} +76 -17
  12. package/dist/_chunks/id-DINFSsrh.mjs.map +1 -0
  13. package/dist/_chunks/{index-Bv3cQ3c-.js → index-BpIILEs0.js} +8 -28
  14. package/dist/_chunks/index-BpIILEs0.js.map +1 -0
  15. package/dist/_chunks/{index-CIBLMG85.js → index-BpL7C1EG.js} +26 -23
  16. package/dist/_chunks/index-BpL7C1EG.js.map +1 -0
  17. package/dist/_chunks/{index-Cx5QECZI.mjs → index-DAcEdoqJ.mjs} +26 -23
  18. package/dist/_chunks/index-DAcEdoqJ.mjs.map +1 -0
  19. package/dist/_chunks/{index-CeaoNBIP.mjs → index-ZcZKwmTD.mjs} +10 -30
  20. package/dist/_chunks/index-ZcZKwmTD.mjs.map +1 -0
  21. package/dist/_chunks/{router-BEoNwQZ1.mjs → router-BPH_u176.mjs} +3 -3
  22. package/dist/_chunks/router-BPH_u176.mjs.map +1 -0
  23. package/dist/_chunks/{router-gRPIa2_c.js → router-DeEgX8Ao.js} +3 -3
  24. package/dist/_chunks/router-DeEgX8Ao.js.map +1 -0
  25. package/dist/admin/index.js +1 -1
  26. package/dist/admin/index.mjs +1 -1
  27. package/dist/admin/src/services/settings.d.ts +7 -3
  28. package/dist/server/index.js +180 -67
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/index.mjs +180 -67
  31. package/dist/server/index.mjs.map +1 -1
  32. package/dist/server/src/bootstrap.d.ts.map +1 -1
  33. package/dist/server/src/constants/workflows.d.ts +1 -0
  34. package/dist/server/src/constants/workflows.d.ts.map +1 -1
  35. package/dist/server/src/content-types/index.d.ts +6 -0
  36. package/dist/server/src/content-types/index.d.ts.map +1 -1
  37. package/dist/server/src/content-types/workflow/index.d.ts +6 -0
  38. package/dist/server/src/content-types/workflow/index.d.ts.map +1 -1
  39. package/dist/server/src/controllers/assignees.d.ts.map +1 -1
  40. package/dist/server/src/controllers/stages.d.ts.map +1 -1
  41. package/dist/server/src/index.d.ts +28 -6
  42. package/dist/server/src/index.d.ts.map +1 -1
  43. package/dist/server/src/register.d.ts.map +1 -1
  44. package/dist/server/src/services/assignees.d.ts +8 -4
  45. package/dist/server/src/services/assignees.d.ts.map +1 -1
  46. package/dist/server/src/services/document-service-middleware.d.ts +1 -0
  47. package/dist/server/src/services/document-service-middleware.d.ts.map +1 -1
  48. package/dist/server/src/services/index.d.ts +16 -6
  49. package/dist/server/src/services/index.d.ts.map +1 -1
  50. package/dist/server/src/services/metrics/index.d.ts +4 -4
  51. package/dist/server/src/services/metrics/index.d.ts.map +1 -1
  52. package/dist/server/src/services/metrics/weekly-metrics.d.ts.map +1 -1
  53. package/dist/server/src/services/stages.d.ts +7 -7
  54. package/dist/server/src/services/stages.d.ts.map +1 -1
  55. package/dist/server/src/services/workflows.d.ts.map +1 -1
  56. package/dist/server/src/validation/review-workflows.d.ts +4 -0
  57. package/dist/server/src/validation/review-workflows.d.ts.map +1 -1
  58. package/dist/shared/contracts/review-workflows.d.ts +8 -3
  59. package/dist/shared/contracts/review-workflows.d.ts.map +1 -1
  60. package/package.json +9 -9
  61. package/dist/_chunks/Layout-AREWDuuq.js.map +0 -1
  62. package/dist/_chunks/Layout-D5aa9iUm.mjs.map +0 -1
  63. package/dist/_chunks/_id-C7pCAzXV.js.map +0 -1
  64. package/dist/_chunks/_id-DDNHKV_W.mjs.map +0 -1
  65. package/dist/_chunks/en-D9ZrQAV6.mjs.map +0 -1
  66. package/dist/_chunks/en-xcewH2pC.js.map +0 -1
  67. package/dist/_chunks/index-Bv3cQ3c-.js.map +0 -1
  68. package/dist/_chunks/index-CIBLMG85.js.map +0 -1
  69. package/dist/_chunks/index-CeaoNBIP.mjs.map +0 -1
  70. package/dist/_chunks/index-Cx5QECZI.mjs.map +0 -1
  71. package/dist/_chunks/router-BEoNwQZ1.mjs.map +0 -1
  72. package/dist/_chunks/router-gRPIa2_c.js.map +0 -1
  73. package/strapi-server.js +0 -3
  74. /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
  };
@@ -6900,7 +6940,7 @@ const reviewWorkflows = {
6900
6940
  {
6901
6941
  name: "admin::hasPermissions",
6902
6942
  config: {
6903
- actions: ["admin::users.read", "admin::review-workflows.read"]
6943
+ actions: ["admin::users.read"]
6904
6944
  }
6905
6945
  }
6906
6946
  ]
@@ -6988,9 +7028,9 @@ const processFilters = ({ strapi: strapi2 }, filters = {}) => {
6988
7028
  };
6989
7029
  const processPopulate = (populate) => {
6990
7030
  if (!populate) {
6991
- return populate;
7031
+ return WORKFLOW_POPULATE;
6992
7032
  }
6993
- return WORKFLOW_POPULATE;
7033
+ return populate;
6994
7034
  };
6995
7035
  const workflows$1 = ({ strapi: strapi2 }) => {
6996
7036
  const workflowsContentTypes = workflowsContentTypesFactory({ strapi: strapi2 });
@@ -7041,14 +7081,27 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7041
7081
  const stages2 = await getService("stages", { strapi: strapi2 }).createMany(opts.data.stages);
7042
7082
  const mapIds = fp.map(fp.get("id"));
7043
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
+ }
7044
7093
  if (opts.data.contentTypes) {
7045
7094
  await workflowsContentTypes.migrate({
7046
7095
  destContentTypes: opts.data.contentTypes,
7047
7096
  stageId: stages2[0].id
7048
7097
  });
7049
7098
  }
7050
- metrics.sendDidCreateWorkflow();
7051
- 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;
7052
7105
  });
7053
7106
  },
7054
7107
  /**
@@ -7061,6 +7114,7 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7061
7114
  async update(workflow2, opts) {
7062
7115
  const stageService = getService("stages", { strapi: strapi2 });
7063
7116
  let updateOpts = { ...opts, populate: { ...WORKFLOW_POPULATE } };
7117
+ let updatedStages = [];
7064
7118
  let updatedStageIds;
7065
7119
  await workflowValidator.validateWorkflowCount();
7066
7120
  return strapi2.db.transaction(async () => {
@@ -7069,9 +7123,28 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7069
7123
  opts.data.stages.forEach(
7070
7124
  (stage) => this.assertStageBelongsToWorkflow(stage.id, workflow2)
7071
7125
  );
7072
- 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);
7073
7132
  updateOpts = fp.set("data.stages", updatedStageIds, updateOpts);
7074
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
+ }
7075
7148
  if (opts.data.contentTypes) {
7076
7149
  await workflowsContentTypes.migrate({
7077
7150
  srcContentTypes: workflow2.contentTypes,
@@ -7079,12 +7152,17 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7079
7152
  stageId: updatedStageIds ? updatedStageIds[0] : workflow2.stages[0].id
7080
7153
  });
7081
7154
  }
7082
- metrics.sendDidEditWorkflow();
7155
+ metrics.sendDidEditWorkflow(workflow2.id, !!opts.data.stageRequiredToPublishName);
7083
7156
  const query = strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, updateOpts);
7084
- return strapi2.db.query(WORKFLOW_MODEL_UID).update({
7157
+ const updatedWorkflow = await strapi2.db.query(WORKFLOW_MODEL_UID).update({
7085
7158
  ...query,
7086
7159
  where: { id: workflow2.id }
7087
7160
  });
7161
+ await strapi2.plugin("content-releases").service("release-action").validateActionsByContentTypes([
7162
+ ...workflow2.contentTypes,
7163
+ ...opts.data.contentTypes || []
7164
+ ]);
7165
+ return updatedWorkflow;
7088
7166
  });
7089
7167
  },
7090
7168
  /**
@@ -7107,10 +7185,12 @@ const workflows$1 = ({ strapi: strapi2 }) => {
7107
7185
  destContentTypes: []
7108
7186
  });
7109
7187
  const query = strapi2.get("query-params").transform(WORKFLOW_MODEL_UID, opts);
7110
- return strapi2.db.query(WORKFLOW_MODEL_UID).delete({
7188
+ const deletedWorkflow = await strapi2.db.query(WORKFLOW_MODEL_UID).delete({
7111
7189
  ...query,
7112
7190
  where: { id: workflow2.id }
7113
7191
  });
7192
+ await strapi2.plugin("content-releases").service("release-action").validateActionsByContentTypes(workflow2.contentTypes);
7193
+ return deletedWorkflow;
7114
7194
  });
7115
7195
  },
7116
7196
  /**
@@ -7321,25 +7401,26 @@ const stages$1 = ({ strapi: strapi2 }) => {
7321
7401
  },
7322
7402
  /**
7323
7403
  * Update the stage of an entity
7324
- *
7325
- * @param {object} entityInfo
7326
- * @param {number} entityInfo.id - Entity id
7327
- * @param {string} entityInfo.modelUID - the content-type of the entity
7328
- * @param {number} stageId - The id of the stage to assign to the entity
7329
7404
  */
7330
- async updateEntity(entityInfo, stageId) {
7405
+ async updateEntity(entityToUpdate, model, stageId) {
7331
7406
  const stage = await this.findById(stageId);
7407
+ const { documentId, locale } = entityToUpdate;
7332
7408
  await workflowValidator.validateWorkflowCount();
7333
7409
  if (!stage) {
7334
7410
  throw new ApplicationError$2(`Selected stage does not exist`);
7335
7411
  }
7336
- const entity = await strapi2.db.query(entityInfo.modelUID).update({
7337
- where: {
7338
- id: entityInfo.id
7339
- },
7340
- 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) },
7341
7418
  populate: [ENTITY_STAGE_ATTRIBUTE]
7342
7419
  });
7420
+ const { tableName } = strapi2.db.metadata.get(model);
7421
+ await strapi2.db.connection(tableName).where({ id: entityToUpdate.id }).update({
7422
+ updated_at: new Date(entityToUpdate.updatedAt)
7423
+ });
7343
7424
  metrics.sendDidChangeEntryStage();
7344
7425
  return entity;
7345
7426
  },
@@ -7486,32 +7567,28 @@ const assignees$1 = ({ strapi: strapi2 }) => {
7486
7567
  /**
7487
7568
  * Update the assignee of an entity
7488
7569
  */
7489
- async updateEntityAssignee(documentId, locale, model, assigneeId) {
7490
- if (fp.isNil(assigneeId)) {
7491
- return this.deleteEntityAssignee(documentId, locale, model);
7492
- }
7493
- const userExists = await getAdminService("user", { strapi: strapi2 }).exists({ id: assigneeId });
7494
- if (!userExists) {
7495
- throw new ApplicationError(`Selected user does not exist`);
7570
+ async updateEntityAssignee(entityToUpdate, model, assigneeId) {
7571
+ const { documentId, locale } = entityToUpdate;
7572
+ if (!fp.isNil(assigneeId)) {
7573
+ const userExists = await getAdminService("user", { strapi: strapi2 }).exists({ id: assigneeId });
7574
+ if (!userExists) {
7575
+ throw new ApplicationError(`Selected user does not exist`);
7576
+ }
7496
7577
  }
7497
- metrics.sendDidEditAssignee(await this.findEntityAssigneeId(documentId, model), assigneeId);
7498
- return strapi2.documents(model).update({
7578
+ const oldAssigneeId = await this.findEntityAssigneeId(entityToUpdate.id, model);
7579
+ metrics.sendDidEditAssignee(oldAssigneeId, assigneeId || null);
7580
+ const entity = await strapi2.documents(model).update({
7499
7581
  documentId,
7500
7582
  locale,
7501
- data: { [ENTITY_ASSIGNEE_ATTRIBUTE]: assigneeId },
7583
+ data: { [ENTITY_ASSIGNEE_ATTRIBUTE]: assigneeId || null },
7502
7584
  populate: [ENTITY_ASSIGNEE_ATTRIBUTE],
7503
7585
  fields: []
7504
7586
  });
7505
- },
7506
- async deleteEntityAssignee(documentId, locale, model) {
7507
- metrics.sendDidEditAssignee(await this.findEntityAssigneeId(documentId, model), null);
7508
- return strapi2.documents(model).update({
7509
- documentId,
7510
- locale,
7511
- data: { [ENTITY_ASSIGNEE_ATTRIBUTE]: null },
7512
- populate: [ENTITY_ASSIGNEE_ATTRIBUTE],
7513
- fields: []
7587
+ const { tableName } = strapi2.db.metadata.get(model);
7588
+ await strapi2.db.connection(tableName).where({ id: entityToUpdate.id }).update({
7589
+ updated_at: new Date(entityToUpdate.updatedAt)
7514
7590
  });
7591
+ return entity;
7515
7592
  }
7516
7593
  };
7517
7594
  };
@@ -7584,11 +7661,11 @@ const sendDidDeleteStage = async () => {
7584
7661
  const sendDidChangeEntryStage = async () => {
7585
7662
  strapi.telemetry.send("didChangeEntryStage", {});
7586
7663
  };
7587
- const sendDidCreateWorkflow = async () => {
7588
- strapi.telemetry.send("didCreateWorkflow", {});
7664
+ const sendDidCreateWorkflow = async (workflowId, hasRequiredStageToPublish) => {
7665
+ strapi.telemetry.send("didCreateWorkflow", { workflowId, hasRequiredStageToPublish });
7589
7666
  };
7590
- const sendDidEditWorkflow = async () => {
7591
- strapi.telemetry.send("didEditWorkflow", {});
7667
+ const sendDidEditWorkflow = async (workflowId, hasRequiredStageToPublish) => {
7668
+ strapi.telemetry.send("didEditWorkflow", { workflowId, hasRequiredStageToPublish });
7592
7669
  };
7593
7670
  const sendDidEditAssignee = async (fromId, toId) => {
7594
7671
  strapi.telemetry.send("didEditAssignee", { from: fromId, to: toId });
@@ -7613,13 +7690,13 @@ const reviewWorkflowsMetrics = {
7613
7690
  sendDidSendReviewWorkflowPropertiesOnceAWeek,
7614
7691
  sendDidEditAssignee
7615
7692
  };
7616
- function _typeof(obj) {
7693
+ function _typeof(o) {
7617
7694
  "@babel/helpers - typeof";
7618
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(obj2) {
7619
- return typeof obj2;
7620
- } : function(obj2) {
7621
- return obj2 && "function" == typeof Symbol && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2;
7622
- }, _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);
7623
7700
  }
7624
7701
  function toInteger(dirtyNumber) {
7625
7702
  if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
@@ -7754,7 +7831,12 @@ const reviewWorkflowsWeeklyMetrics = ({ strapi: strapi2 }) => {
7754
7831
  },
7755
7832
  async registerCron() {
7756
7833
  const weeklySchedule = await this.ensureWeeklyStoredCronSchedule();
7757
- 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
+ });
7758
7840
  }
7759
7841
  };
7760
7842
  };
@@ -7810,6 +7892,7 @@ const handleStageOnUpdate = async (ctx, next) => {
7810
7892
  strapi.eventHub.emit(WORKFLOW_UPDATE_STAGE, {
7811
7893
  model: model.modelName,
7812
7894
  uid: model.uid,
7895
+ // TODO v6: Rename to "entry", which is what is used for regular CRUD updates
7813
7896
  entity: {
7814
7897
  // @ts-expect-error
7815
7898
  id: result?.id,
@@ -7835,9 +7918,27 @@ const handleStageOnUpdate = async (ctx, next) => {
7835
7918
  }
7836
7919
  return next();
7837
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 errors.ValidationError("Entry is not at the required stage to publish");
7935
+ }
7936
+ return next();
7937
+ };
7838
7938
  const documentServiceMiddleware = () => ({
7839
7939
  assignStageOnCreate,
7840
- handleStageOnUpdate
7940
+ handleStageOnUpdate,
7941
+ checkStageBeforePublish
7841
7942
  });
7842
7943
  const services = {
7843
7944
  workflows: workflows$1,
@@ -7887,12 +7988,14 @@ const validateContentTypes = yup.array().of(
7887
7988
  const validateWorkflowCreateSchema = yup.object().shape({
7888
7989
  name: yup.string().max(255).min(1, "Workflow name can not be empty").required(),
7889
7990
  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"),
7890
- contentTypes: validateContentTypes
7991
+ contentTypes: validateContentTypes,
7992
+ stageRequiredToPublishName: yup.string().min(1).nullable()
7891
7993
  });
7892
7994
  const validateWorkflowUpdateSchema = yup.object().shape({
7893
7995
  name: yup.string().max(255).min(1, "Workflow name can not be empty"),
7894
7996
  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"),
7895
- contentTypes: validateContentTypes
7997
+ contentTypes: validateContentTypes,
7998
+ stageRequiredToPublishName: yup.string().min(1).nullable()
7896
7999
  });
7897
8000
  const validateUpdateAssigneeOnEntitySchema = yup.object().shape({
7898
8001
  id: yup.number().integer().min(1).nullable()
@@ -8095,7 +8198,7 @@ const stages = {
8095
8198
  );
8096
8199
  const workflow2 = await workflowService.assertContentTypeBelongsToWorkflow(modelUID);
8097
8200
  workflowService.assertStageBelongsToWorkflow(stageId, workflow2);
8098
- const updatedEntity = await stagesService.updateEntity({ id: entity.id, modelUID }, stageId);
8201
+ const updatedEntity = await stagesService.updateEntity(entity, modelUID, stageId);
8099
8202
  ctx.body = { data: await sanitizeOutput(updatedEntity) };
8100
8203
  },
8101
8204
  /**
@@ -8115,10 +8218,9 @@ const stages = {
8115
8218
  if (strapi.plugin("content-manager").service("permission-checker").create({ userAbility: ctx.state.userAbility, model: modelUID }).cannot.read()) {
8116
8219
  return ctx.forbidden();
8117
8220
  }
8118
- const locale = await validateLocale(query?.locale);
8221
+ const locale = await validateLocale(query?.locale) ?? void 0;
8119
8222
  const entity = await strapi.documents(modelUID).findOne({
8120
8223
  documentId,
8121
- // @ts-expect-error - locale should be also null in the doc service types
8122
8224
  locale,
8123
8225
  populate: [ENTITY_STAGE_ATTRIBUTE]
8124
8226
  });
@@ -8127,12 +8229,13 @@ const stages = {
8127
8229
  }
8128
8230
  const entityStageId = entity[ENTITY_STAGE_ATTRIBUTE]?.id;
8129
8231
  const canTransition = stagePermissions2.can(STAGE_TRANSITION_UID, entityStageId);
8130
- const [workflowCount, { stages: workflowStages }] = await Promise.all([
8232
+ const [workflowCount, workflowResult] = await Promise.all([
8131
8233
  workflowService.count(),
8132
8234
  workflowService.getAssignedWorkflow(modelUID, {
8133
8235
  populate: "stages"
8134
8236
  })
8135
8237
  ]);
8238
+ const workflowStages = workflowResult ? workflowResult.stages : [];
8136
8239
  const meta = {
8137
8240
  stageCount: workflowStages.length,
8138
8241
  workflowCount
@@ -8168,22 +8271,32 @@ const assignees = {
8168
8271
  async updateEntity(ctx) {
8169
8272
  const assigneeService = getService("assignees");
8170
8273
  const workflowService = getService("workflows");
8274
+ const stagePermissions2 = getService("stage-permissions");
8171
8275
  const { model_uid: model, id: documentId } = ctx.params;
8172
- const { locale } = ctx.request.query || {};
8276
+ const locale = await validateLocale(ctx.request.query?.locale) ?? void 0;
8173
8277
  const { sanitizeOutput } = strapi.plugin("content-manager").service("permission-checker").create({ userAbility: ctx.state.userAbility, model });
8278
+ const entity = await strapi.documents(model).findOne({
8279
+ documentId,
8280
+ locale,
8281
+ populate: [ENTITY_STAGE_ATTRIBUTE]
8282
+ });
8283
+ if (!entity) {
8284
+ ctx.throw(404, "Entity not found");
8285
+ }
8286
+ const canTransitionStage = stagePermissions2.can(
8287
+ STAGE_TRANSITION_UID,
8288
+ entity[ENTITY_STAGE_ATTRIBUTE]?.id
8289
+ );
8290
+ if (!canTransitionStage) {
8291
+ ctx.throw(403, "Stage transition permission is required");
8292
+ }
8174
8293
  const { id: assigneeId } = await validateUpdateAssigneeOnEntity(
8175
8294
  ctx.request?.body?.data,
8176
8295
  "You should pass a valid id to the body of the put request."
8177
8296
  );
8178
- await validateLocale(locale);
8179
8297
  await workflowService.assertContentTypeBelongsToWorkflow(model);
8180
- const entity = await assigneeService.updateEntityAssignee(
8181
- documentId,
8182
- locale || null,
8183
- model,
8184
- assigneeId
8185
- );
8186
- ctx.body = { data: await sanitizeOutput(entity) };
8298
+ const updatedEntity = await assigneeService.updateEntityAssignee(entity, model, assigneeId);
8299
+ ctx.body = { data: await sanitizeOutput(updatedEntity) };
8187
8300
  }
8188
8301
  };
8189
8302
  const controllers = {