Skip to content

Commit 81dc51b

Browse files
committed
fix(api-headless-cms-scheduler): lifecycle hooks
1 parent 4af56eb commit 81dc51b

File tree

20 files changed

+224
-54
lines changed

20 files changed

+224
-54
lines changed

packages/api-headless-cms-scheduler/src/context.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { NotFoundError } from "@webiny/handler-graphql";
1313
import { SCHEDULE_MODEL_ID } from "./constants.js";
1414
import { isHeadlessCmsReady } from "@webiny/api-headless-cms";
1515
import type { DynamoDBDocument } from "@webiny/aws-sdk/client-dynamodb/index.js";
16+
import { attachLifecycleHooks } from "~/hooks/index.js";
1617

1718
export interface ICreateHeadlessCmsSchedulerContextParams {
1819
getClient(config?: SchedulerClientConfig): Pick<SchedulerClient, "send">;
@@ -48,9 +49,9 @@ export const createHeadlessCmsScheduleContext = (
4849
}
4950
});
5051

51-
let scheduleModel: CmsModel;
52+
let schedulerModel: CmsModel;
5253
try {
53-
scheduleModel = await context.cms.getModel(SCHEDULE_MODEL_ID);
54+
schedulerModel = await context.cms.getModel(SCHEDULE_MODEL_ID);
5455
} catch (ex) {
5556
if (ex.code === "NOT_FOUND" || ex instanceof NotFoundError) {
5657
console.error(`Schedule model "${SCHEDULE_MODEL_ID}" not found.`);
@@ -61,11 +62,16 @@ export const createHeadlessCmsScheduleContext = (
6162
return;
6263
}
6364

65+
attachLifecycleHooks({
66+
cms: context.cms,
67+
schedulerModel
68+
});
69+
6470
context.cms.scheduler = await createScheduler({
6571
cms: context.cms,
6672
security: context.security,
6773
service,
68-
scheduleModel
74+
schedulerModel
6975
});
7076
});
7177
};
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { CmsModel } from "@webiny/api-headless-cms/types/model.js";
2+
import type { CmsEntry } from "@webiny/api-headless-cms/types/types.js";
3+
import type { ScheduleContext } from "~/types.js";
4+
5+
export interface IAttachLifecycleHookParams {
6+
cms: ScheduleContext["cms"];
7+
schedulerModel: Pick<CmsModel, "modelId">;
8+
}
9+
10+
export const attachLifecycleHooks = (params: IAttachLifecycleHookParams): void => {
11+
const { cms, schedulerModel } = params;
12+
13+
const shouldContinue = (model: Pick<CmsModel, "modelId" | "isPrivate">): boolean => {
14+
if (model.modelId === schedulerModel.modelId) {
15+
return false;
16+
}
17+
// TODO maybe change with a list of private models which are allowed?
18+
else if (model.isPrivate) {
19+
return false;
20+
}
21+
22+
return true;
23+
};
24+
25+
const cancel = async (model: CmsModel, target: Pick<CmsEntry, "id">): Promise<void> => {
26+
if (shouldContinue(model) === false) {
27+
return;
28+
}
29+
const scheduler = cms.scheduler(model);
30+
31+
const entry = await scheduler.getScheduled(target.id);
32+
if (!entry) {
33+
return;
34+
}
35+
try {
36+
await scheduler.cancel(entry.id);
37+
} catch {
38+
// does not matter
39+
}
40+
};
41+
42+
cms.onEntryAfterPublish.subscribe(async ({ entry, model }) => {
43+
return cancel(model, entry);
44+
});
45+
cms.onEntryAfterUnpublish.subscribe(async ({ entry, model }) => {
46+
return cancel(model, entry);
47+
});
48+
cms.onEntryAfterDelete.subscribe(async ({ entry, model }) => {
49+
return cancel(model, entry);
50+
});
51+
};

packages/api-headless-cms-scheduler/src/scheduler/ScheduleFetcher.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ export type ScheduleFetcherCms = Pick<HeadlessCms, "getEntryById" | "listLatestE
1616
export interface IScheduleFetcherParams {
1717
cms: ScheduleFetcherCms;
1818
targetModel: CmsModel;
19-
scheduleModel: CmsModel;
19+
schedulerModel: CmsModel;
2020
}
2121

2222
export class ScheduleFetcher implements IScheduleFetcher {
2323
private readonly cms: ScheduleFetcherCms;
2424
private readonly targetModel: CmsModel;
25-
private readonly scheduleModel: CmsModel;
25+
private readonly schedulerModel: CmsModel;
2626

2727
constructor(params: IScheduleFetcherParams) {
2828
this.cms = params.cms;
29-
this.scheduleModel = params.scheduleModel;
29+
this.schedulerModel = params.schedulerModel;
3030
this.targetModel = params.targetModel;
3131
}
3232

3333
public async getScheduled(targetId: string): Promise<IScheduleRecord | null> {
3434
const scheduleRecordId = createScheduleRecordIdWithVersion(targetId);
3535
try {
3636
const entry = await this.cms.getEntryById<IScheduleEntryValues>(
37-
this.scheduleModel,
37+
this.schedulerModel,
3838
scheduleRecordId
3939
);
4040
return transformScheduleEntry(this.targetModel, entry);
@@ -50,7 +50,7 @@ export class ScheduleFetcher implements IScheduleFetcher {
5050

5151
public async listScheduled(params: ISchedulerListParams): Promise<ISchedulerListResponse> {
5252
const [data, meta] = await this.cms.listLatestEntries<IScheduleEntryValues>(
53-
this.scheduleModel,
53+
this.schedulerModel,
5454
{
5555
sort: params.sort,
5656
limit: params.limit,

packages/api-headless-cms-scheduler/src/scheduler/Scheduler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export class Scheduler implements IScheduler {
1717
private readonly fetcher: IScheduleFetcher;
1818
private readonly executor: IScheduleExecutor;
1919

20-
constructor(params: ISchedulerParams) {
20+
public constructor(params: ISchedulerParams) {
2121
this.fetcher = params.fetcher;
2222
this.executor = params.executor;
2323
}

packages/api-headless-cms-scheduler/src/scheduler/actions/PublishScheduleAction.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface IPublishScheduleActionParams {
3131
service: ISchedulerService;
3232
cms: PublishScheduleActionCms;
3333
targetModel: CmsModel;
34-
scheduleModel: CmsModel;
34+
schedulerModel: CmsModel;
3535
getIdentity: () => CmsIdentity;
3636
fetcher: IScheduleFetcher;
3737
}
@@ -40,15 +40,15 @@ export class PublishScheduleAction implements IScheduleAction {
4040
private readonly service: ISchedulerService;
4141
private readonly cms: PublishScheduleActionCms;
4242
private readonly targetModel: CmsModel;
43-
private readonly scheduleModel: CmsModel;
43+
private readonly schedulerModel: CmsModel;
4444
private readonly getIdentity: () => CmsIdentity;
4545
private readonly fetcher: IScheduleFetcher;
4646

4747
public constructor(params: IPublishScheduleActionParams) {
4848
this.service = params.service;
4949
this.cms = params.cms;
5050
this.targetModel = params.targetModel;
51-
this.scheduleModel = params.scheduleModel;
51+
this.schedulerModel = params.schedulerModel;
5252
this.getIdentity = params.getIdentity;
5353
this.fetcher = params.fetcher;
5454
}
@@ -111,15 +111,18 @@ export class PublishScheduleAction implements IScheduleAction {
111111
}
112112

113113
const { id: scheduleEntryId } = parseIdentifier(scheduleRecordId);
114-
const scheduleEntry = await this.cms.createEntry<IScheduleEntryValues>(this.scheduleModel, {
115-
id: scheduleEntryId,
116-
targetId,
117-
targetModelId: this.targetModel.modelId,
118-
title,
119-
type: ScheduleType.publish,
120-
scheduledOn: dateToISOString(input.scheduleOn),
121-
scheduledBy: identity
122-
});
114+
const scheduleEntry = await this.cms.createEntry<IScheduleEntryValues>(
115+
this.schedulerModel,
116+
{
117+
id: scheduleEntryId,
118+
targetId,
119+
targetModelId: this.targetModel.modelId,
120+
title,
121+
type: ScheduleType.publish,
122+
scheduledOn: dateToISOString(input.scheduleOn),
123+
scheduledBy: identity
124+
}
125+
);
123126

124127
try {
125128
await this.service.create({
@@ -132,7 +135,7 @@ export class PublishScheduleAction implements IScheduleAction {
132135
);
133136
console.log(convertException(ex));
134137
try {
135-
await this.cms.deleteEntry(this.scheduleModel, scheduleRecordId);
138+
await this.cms.deleteEntry(this.schedulerModel, scheduleRecordId);
136139
} catch (err) {
137140
console.error(`Error while deleting schedule entry: ${scheduleRecordId}.`);
138141
console.log(convertException(err));
@@ -181,7 +184,7 @@ export class PublishScheduleAction implements IScheduleAction {
181184
}
182185

183186
await this.cms.updateEntry<Pick<IScheduleEntryValues, "scheduledOn" | "scheduledBy">>(
184-
this.scheduleModel,
187+
this.schedulerModel,
185188
original.id,
186189
{
187190
scheduledBy: this.getIdentity(),
@@ -219,7 +222,7 @@ export class PublishScheduleAction implements IScheduleAction {
219222
}
220223

221224
try {
222-
await this.cms.deleteEntry(this.scheduleModel, scheduleEntry.id);
225+
await this.cms.deleteEntry(this.schedulerModel, scheduleEntry.id);
223226
} catch (ex) {
224227
if (ex.code === "NOT_FOUND" || ex instanceof NotFoundError) {
225228
return;

packages/api-headless-cms-scheduler/src/scheduler/actions/UnpublishScheduleAction.ts

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export interface IUnpublishScheduleActionParams {
3131
service: ISchedulerService;
3232
cms: UnpublishScheduleActionCms;
3333
targetModel: CmsModel;
34-
scheduleModel: CmsModel;
34+
schedulerModel: CmsModel;
3535
getIdentity: () => CmsIdentity;
3636
fetcher: IScheduleFetcher;
3737
}
@@ -40,15 +40,15 @@ export class UnpublishScheduleAction implements IScheduleAction {
4040
private readonly service: ISchedulerService;
4141
private readonly cms: UnpublishScheduleActionCms;
4242
private readonly targetModel: CmsModel;
43-
private readonly scheduleModel: CmsModel;
43+
private readonly schedulerModel: CmsModel;
4444
private readonly getIdentity: () => CmsIdentity;
4545
private readonly fetcher: IScheduleFetcher;
4646

4747
public constructor(params: IUnpublishScheduleActionParams) {
4848
this.service = params.service;
4949
this.cms = params.cms;
5050
this.targetModel = params.targetModel;
51-
this.scheduleModel = params.scheduleModel;
51+
this.schedulerModel = params.schedulerModel;
5252
this.getIdentity = params.getIdentity;
5353
this.fetcher = params.fetcher;
5454
}
@@ -104,16 +104,19 @@ export class UnpublishScheduleAction implements IScheduleAction {
104104
*/
105105

106106
const { id: scheduleEntryId } = parseIdentifier(scheduleRecordId);
107-
const scheduleEntry = await this.cms.createEntry<IScheduleEntryValues>(this.scheduleModel, {
108-
id: scheduleEntryId,
109-
targetId,
110-
targetModelId: this.targetModel.modelId,
111-
title,
112-
type: ScheduleType.unpublish,
113-
// dateOn: input.dateOn ? dateToISOString(input.dateOn) : undefined,
114-
scheduledBy: identity,
115-
scheduledOn: dateToISOString(input.scheduleOn)
116-
});
107+
const scheduleEntry = await this.cms.createEntry<IScheduleEntryValues>(
108+
this.schedulerModel,
109+
{
110+
id: scheduleEntryId,
111+
targetId,
112+
targetModelId: this.targetModel.modelId,
113+
title,
114+
type: ScheduleType.unpublish,
115+
// dateOn: input.dateOn ? dateToISOString(input.dateOn) : undefined,
116+
scheduledBy: identity,
117+
scheduledOn: dateToISOString(input.scheduleOn)
118+
}
119+
);
117120

118121
try {
119122
await this.service.create({
@@ -126,7 +129,7 @@ export class UnpublishScheduleAction implements IScheduleAction {
126129
);
127130
console.log(convertException(ex));
128131
try {
129-
await this.cms.deleteEntry(this.scheduleModel, scheduleRecordId);
132+
await this.cms.deleteEntry(this.schedulerModel, scheduleRecordId);
130133
} catch (err) {
131134
console.error(`Error while deleting schedule entry: ${scheduleRecordId}.`);
132135
console.log(convertException(err));
@@ -175,7 +178,7 @@ export class UnpublishScheduleAction implements IScheduleAction {
175178
}
176179

177180
await this.cms.updateEntry<Pick<IScheduleEntryValues, "scheduledOn">>(
178-
this.scheduleModel,
181+
this.schedulerModel,
179182
original.id,
180183
{
181184
scheduledOn: dateToISOString(input.scheduleOn)
@@ -217,7 +220,7 @@ export class UnpublishScheduleAction implements IScheduleAction {
217220
}
218221

219222
try {
220-
await this.cms.deleteEntry(this.scheduleModel, scheduleRecord.id);
223+
await this.cms.deleteEntry(this.schedulerModel, scheduleRecord.id);
221224
} catch (ex) {
222225
if (ex.code === "NOT_FOUND" || ex instanceof NotFoundError) {
223226
return;

packages/api-headless-cms-scheduler/src/scheduler/createScheduler.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,30 @@ import type { ScheduleExecutorCms } from "./ScheduleExecutor.js";
99
import { ScheduleExecutor } from "./ScheduleExecutor.js";
1010
import { PublishScheduleAction } from "~/scheduler/actions/PublishScheduleAction.js";
1111
import { UnpublishScheduleAction } from "~/scheduler/actions/UnpublishScheduleAction.js";
12+
import { WebinyError } from "@webiny/error";
1213

1314
export interface ICreateSchedulerParams {
1415
security: Pick<CmsContext["security"], "getIdentity">;
1516
cms: ScheduleExecutorCms & ScheduleFetcherCms;
1617
service: ISchedulerService;
17-
scheduleModel: CmsModel;
18+
schedulerModel: CmsModel;
1819
}
1920

2021
export const createScheduler = async (
2122
params: ICreateSchedulerParams
2223
): Promise<CmsScheduleCallable> => {
23-
const { cms, security, scheduleModel, service } = params;
24+
const { cms, security, schedulerModel, service } = params;
2425

2526
return (targetModel): IScheduler => {
27+
if (targetModel.isPrivate) {
28+
throw new WebinyError(
29+
"Cannot create a scheduler for private models.",
30+
"PRIVATE_MODEL_ERROR",
31+
{
32+
modelId: targetModel.modelId
33+
}
34+
);
35+
}
2636
const getIdentity = () => {
2737
const identity = security.getIdentity();
2838
if (!identity) {
@@ -33,22 +43,22 @@ export const createScheduler = async (
3343

3444
const fetcher = new ScheduleFetcher({
3545
targetModel,
36-
scheduleModel,
46+
schedulerModel,
3747
cms
3848
});
3949

4050
const actions = [
4151
new PublishScheduleAction({
4252
cms,
43-
scheduleModel,
53+
schedulerModel,
4454
targetModel,
4555
service,
4656
getIdentity,
4757
fetcher
4858
}),
4959
new UnpublishScheduleAction({
5060
cms,
51-
scheduleModel,
61+
schedulerModel,
5262
targetModel,
5363
service,
5464
getIdentity,

packages/app-headless-cms-scheduler/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"@webiny/react-composition": "0.0.0",
2323
"@webiny/react-properties": "0.0.0",
2424
"@webiny/ui": "0.0.0",
25+
"@webiny/utils": "0.0.0",
2526
"@webiny/validation": "0.0.0",
2627
"apollo-client": "^2.6.10",
2728
"lodash": "^4.17.21",

packages/app-headless-cms-scheduler/src/Presentation/SchedulerConfigs/SchedulerConfigs.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
CellScheduledBy,
66
CellScheduledOn,
77
CellTitle,
8-
CellType
8+
CellType,
9+
CellRevision
910
} from "~/Presentation/components/Cells";
1011
import { CancelItemAction } from "~/Presentation/components/Actions";
1112

@@ -23,6 +24,12 @@ export const SchedulerConfigs = () => {
2324
hideable={false}
2425
size={200}
2526
/>
27+
<Browser.Table.Column
28+
name={"revision"}
29+
header={"Revision"}
30+
cell={<CellRevision />}
31+
hideable={false}
32+
/>
2633
<Browser.Table.Column
2734
name={"scheduledBy"}
2835
header={"Author"}

0 commit comments

Comments
 (0)