Skip to content

Commit

Permalink
fix: cron permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
Balint Dolla committed Nov 9, 2023
1 parent 16d211b commit 35fa651
Show file tree
Hide file tree
Showing 14 changed files with 126 additions and 64 deletions.
7 changes: 7 additions & 0 deletions packages/core/types/BaseEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { z } from "zod";

export const BaseEvent = z.object({
eventId: z.string().optional(),
});

export type BaseEvent = z.infer<typeof BaseEvent>;
4 changes: 4 additions & 0 deletions packages/core/types/cron/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const iceBreaker = "iceBreaker";
export const daily = "daily";

export type ScheduledEventType = typeof iceBreaker | typeof daily;
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { z } from "zod";

const BaseEvent = z.object({
eventId: z.string().optional(),
});
import { BaseEvent } from "@/types/BaseEvent";

const Events = z.object({
memberJoinedChannel: BaseEvent.extend({
Expand Down
2 changes: 1 addition & 1 deletion packages/functions/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"baseUrl": ".",
"paths": {
"@/db/*": ["../core/db/*"],
"@/events": ["../core/events/index.ts"],
"@/events": ["../core/types/events/index.ts"],
"@/services/*": ["../core/services/*"],
"@/utils/*": ["utils/*"],
"@/types/*": ["../core/types/*"]
Expand Down
7 changes: 5 additions & 2 deletions packages/functions/utils/lambda/cronHandler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import type { APIGatewayProxyEventV2, EventBridgeEvent } from "aws-lambda";

import type { BaseEvent } from "@/types/BaseEvent";
import type { ScheduledEventType } from "@/types/cron";

type Event =
| APIGatewayProxyEventV2
| EventBridgeEvent<"Scheduled Event", unknown>;
| EventBridgeEvent<ScheduledEventType, BaseEvent>;

const isApiGatewayProxyEventV2 = (
event: Event,
Expand All @@ -14,7 +17,7 @@ export const cronHandler =
try {
const eventId = isApiGatewayProxyEventV2(request)
? request.queryStringParameters?.eventId
: undefined;
: request.detail.eventId;

await handler(eventId);
} catch (error) {
Expand Down
8 changes: 7 additions & 1 deletion sst.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { SSTConfig } from "sst";

import { ConfigStack } from "./stacks/ConfigStack";
import { CronStack } from "./stacks/CronStack";
import { EventBusStack } from "./stacks/EventBusStack";
import { MyStack } from "./stacks/MyStack";
import { StorageStack } from "./stacks/StorageStack";

Expand All @@ -13,6 +14,11 @@ export default {
};
},
stacks(app) {
app.stack(ConfigStack).stack(StorageStack).stack(CronStack).stack(MyStack);
app
.stack(ConfigStack)
.stack(StorageStack)
.stack(EventBusStack)
.stack(MyStack)
.stack(CronStack);
},
} satisfies SSTConfig;
40 changes: 23 additions & 17 deletions stacks/CronStack.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,46 @@
import type { StackContext } from "sst/constructs";
import { Cron, use } from "sst/constructs";
import { Cron } from "sst/constructs";

import { ConfigStack } from "./ConfigStack";
import { daily, iceBreaker } from "@/types/cron";

export function CronStack({ stack }: StackContext) {
const secrets = use(ConfigStack);
import { getFunctionProps } from "./getFunctionProps";

const environment = {
DB_URL: process.env.DB_URL || "",
};
export function CronStack({ stack }: StackContext) {
const functionProps = getFunctionProps();

const icebreakerCron = new Cron(stack, "Cron", {
new Cron(stack, "IceBreakerCron", {
job: {
function: {
environment,
handler: "packages/functions/cron/iceBreakerQuestions.handler",
runtime: "nodejs18.x",
...functionProps,
},
},
cdk: {
rule: {
eventPattern: {
detailType: [iceBreaker],
},
},
},
// Every first Tuesday of the month at 11:00 UTC
schedule: "cron(0 11 ? * 3#1 *)",
});

icebreakerCron.bind(secrets);

const dailyCron = new Cron(stack, "DailyCron", {
new Cron(stack, "DailyCron", {
job: {
function: {
environment,
handler: "packages/functions/cron/daily.handler",
runtime: "nodejs18.x",
...functionProps,
},
},
cdk: {
rule: {
eventPattern: {
detailType: [daily],
},
},
},
// Every day at 11:00 UTC
schedule: "cron(0 11 ? * * *)",
});

dailyCron.bind(secrets);
}
13 changes: 13 additions & 0 deletions stacks/EventBusStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { EventBus, type StackContext } from "sst/constructs";

export function EventBusStack({ stack }: StackContext) {
const eventBus = new EventBus(stack, "EventBus", {});

stack.addOutputs({
eventBusName: eventBus.eventBusName,
});

return {
eventBus,
};
}
37 changes: 8 additions & 29 deletions stacks/MyStack.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
import type { StackContext } from "sst/constructs";
import { Api, EventBus, Function, Queue, use } from "sst/constructs";
import { Api, Function, Queue, use } from "sst/constructs";

import { eventTypes } from "@/events";

import { ConfigStack } from "./ConfigStack";
import { StorageStack } from "./StorageStack";
import { EventBusStack } from "./EventBusStack";
import { getFunctionProps } from "./getFunctionProps";

export function MyStack({ stack }: StackContext) {
const secrets = use(ConfigStack);
const { db } = use(StorageStack);

const bind = [...secrets, ...(db ? [db] : [])];

const eventBus = new EventBus(stack, "Bus", {});
const { eventBus } = use(EventBusStack);
const functionProps = getFunctionProps();

eventBus.addRules(
stack,
Expand All @@ -26,13 +22,7 @@ export function MyStack({ stack }: StackContext) {
consumer: {
function: {
handler: `packages/functions/events/${eventType}.handler`,
permissions: [eventBus],
environment: {
EVENT_BUS_NAME: eventBus.eventBusName,
DB_URL: process.env.DB_URL || "",
},
bind,
runtime: "nodejs18.x",
...functionProps,
},
},
}),
Expand All @@ -46,12 +36,7 @@ export function MyStack({ stack }: StackContext) {
const api = new Api(stack, "Api", {
defaults: {
function: {
bind,
environment: {
EVENT_BUS_NAME: eventBus.eventBusName,
DB_URL: process.env.DB_URL || "",
},
runtime: "nodejs18.x",
...functionProps,
},
},
routes: {
Expand All @@ -70,21 +55,15 @@ export function MyStack({ stack }: StackContext) {
});
}

api.attachPermissions([eventBus]);

const migrationFn = new Function(stack, "MigrateDb", {
handler: "packages/functions/lambdas/migrateDb.handler",
bind,
environment: {
DB_URL: process.env.DB_URL || "",
},
copyFiles: [
{
from: "packages/core/db/migrations",
},
],
timeout: "60 seconds",
runtime: "nodejs18.x",
...functionProps,
});

stack.addOutputs({
Expand Down
23 changes: 23 additions & 0 deletions stacks/getFunctionProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { use } from "sst/constructs";

import { ConfigStack } from "./ConfigStack";
import { EventBusStack } from "./EventBusStack";
import { StorageStack } from "./StorageStack";

export const getFunctionProps = () => {
const secrets = use(ConfigStack);
const { db } = use(StorageStack);
const { eventBus } = use(EventBusStack);

const bind = [...secrets, ...(db ? [db] : [])];

return {
permissions: [eventBus],
environment: {
EVENT_BUS_NAME: eventBus.eventBusName,
DB_URL: process.env.DB_URL || "",
},
bind,
runtime: "nodejs18.x" as const,
};
};
3 changes: 2 additions & 1 deletion tests/integration/askPresentIdeas.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { presentIdeas, testItems, users } from "@/db/schema";
import { constructPresentIdeaSavedMessage } from "@/services/slack/constructPresentIdeaSavedMessage";
import { pollInterval, timeout, waitTimeout } from "@/testUtils/constants";
import { deleteLastDm } from "@/testUtils/integration/deleteLastDm";
import { sendCronEvent } from "@/testUtils/integration/sendCronEvent";
import { sendSlackInteraction } from "@/testUtils/integration/sendSlackInteraction";
import { waitForDm } from "@/testUtils/integration/waitForDm";
import { testDb, waitForTestItem } from "@/testUtils/testDb";
Expand Down Expand Up @@ -57,7 +58,7 @@ describe("Present ideas", () => {

const eventId = "PI1_" + Date.now().toString();

await fetch(`${import.meta.env.VITE_API_URL}/daily?eventId=${eventId}`);
await sendCronEvent("daily", eventId);

const message = await waitForDm(eventId);

Expand Down
13 changes: 4 additions & 9 deletions tests/integration/iceBreakerQuestions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
usersInWindow,
usersOutsideWindow,
} from "@/testUtils/generateIceBreakerTestUsers";
import { sendCronEvent } from "@/testUtils/integration/sendCronEvent";
import { app } from "@/testUtils/integration/testSlackApp";
import { waitForPostInRandom } from "@/testUtils/integration/waitForPostInRandom";
import { testDb } from "@/testUtils/testDb";
Expand Down Expand Up @@ -53,9 +54,7 @@ describe("Icebreaker questions", () => {
async () => {
const eventId = "IB1_" + Date.now().toString();

await fetch(
`${import.meta.env.VITE_API_URL}/icebreaker?eventId=${eventId}`,
);
await sendCronEvent("iceBreaker", eventId);

const message = await waitForPostInRandom(eventId);

Expand All @@ -74,9 +73,7 @@ describe("Icebreaker questions", () => {

const eventId = "IB2_" + Date.now().toString();

await fetch(
`${import.meta.env.VITE_API_URL}/icebreaker?eventId=${eventId}`,
);
await sendCronEvent("iceBreaker", eventId);

const message = await waitForPostInRandom(eventId);

Expand Down Expand Up @@ -109,9 +106,7 @@ describe("Icebreaker questions", () => {

const eventId = "IB3_" + Date.now().toString();

await fetch(
`${import.meta.env.VITE_API_URL}/icebreaker?eventId=${eventId}`,
);
await sendCronEvent("iceBreaker", eventId);

const threads = await vi.waitFor(
async () => {
Expand Down
27 changes: 27 additions & 0 deletions tests/utils/integration/sendCronEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
EventBridgeClient,
PutEventsCommand,
} from "@aws-sdk/client-eventbridge";

import type { ScheduledEventType } from "@/types/cron";

const eventBridge = new EventBridgeClient();

export const sendCronEvent = async (
type: ScheduledEventType,
eventId: string,
) => {
await eventBridge.send(
new PutEventsCommand({
Entries: [
{
Detail: JSON.stringify({
eventId,
}),
DetailType: type,
Source: "sst",
},
],
}),
);
};
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"baseUrl": ".",
"paths": {
"@/db/*": ["packages/core/db/*"],
"@/events": ["packages/core/events/index.ts"],
"@/events": ["packages/core/types/events/index.ts"],
"@/functions/*": ["packages/functions/*"],
"@/services/*": ["packages/core/services/*"],
"@/types/*": ["packages/core/types/*"],
Expand Down

0 comments on commit 35fa651

Please sign in to comment.