Skip to content

Commit

Permalink
Merge pull request #689 from pennlabs/alert-2.0-status-update
Browse files Browse the repository at this point in the history
Alert 2.0 Proper Status Update Handling
  • Loading branch information
esinx authored Nov 19, 2024
2 parents 01dd582 + 334e9c8 commit f7890cd
Show file tree
Hide file tree
Showing 8 changed files with 939 additions and 9 deletions.
4 changes: 2 additions & 2 deletions frontend/alert-2.0/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ dist-ssr
*.sln
*.sw?


*.env

.terraform
*.tfstate
*.tfstate.backup
*.tfstate.backup
>>>>>>> alert-2.0
1 change: 1 addition & 0 deletions services/alert/backend/src/core/db/schema/course/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './auth.user.model'
export * from './course.model'
export * from './section.model'
export * from "./statusupdate.model"
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {
bigint,
bigserial,
boolean,
doublePrecision,
pgTable,
text,
timestamp,
varchar,
} from 'drizzle-orm/pg-core'

import { $section } from '@/core/db/schema/course/section.model'

/**
* This model was autogenerated / edited using drizzle-kit.
* The source of truth is the model defined in the Django backend,
* so be sure to sync changes with the backend.
* DO NOT EXPORT/SYNC THIS MODEL.
*/
export const $statusUpdate = pgTable("courses_statusupdate", {
id: bigserial({ mode: "bigint" }).primaryKey().notNull(),
oldStatus: varchar("old_status", { length: 16 }).notNull(),
newStatus: varchar("new_status", { length: 16 }).notNull(),
createdAt: timestamp("created_at", { withTimezone: true, mode: 'string' }).notNull(),
alertSent: boolean("alert_sent").notNull(),
requestBody: text("request_body").notNull(),
// You can use { mode: "bigint" } if numbers are exceeding js number limitations
sectionId: bigint("section_id", { mode: "number" })
.notNull()
.references(() => $section.id),
inAddDropPeriod: boolean("in_add_drop_period").notNull(),
percentThroughAddDropPeriod: doublePrecision("percent_through_add_drop_period"),
})
5 changes: 4 additions & 1 deletion services/alert/webhook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@
"main": "index.js",
"license": "MIT",
"type": "module",
"prettier": "@esinx/prettier-config",
"scripts": {
"build:lambda": "tsx ./scripts/build.lambda"
},
"devDependencies": {
"@esinx/eslint-config": "^2.0.1",
"@esinx/prettier-config": "^1.0.0-3",
"@types/aws-lambda": "^8.10.145",
"@types/node": "^22.5.1",
"@types/pg": "^8.11.10",
"aws-lambda": "^1.0.7",
"esbuild": "^0.24.0",
"tsconfig-paths": "^4.2.0",
"tsx": "^4.19.1",
"typescript": "^5.5.4",
"typescript": "^5.6.3",
"vite-tsconfig-paths": "^5.1.0",
"vitest": "^2.1.4"
},
Expand Down
51 changes: 51 additions & 0 deletions services/alert/webhook/src/core/save.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import db, { eq, and } from '@pennlabs/pca-backend/db'
import {
$section,
$statusUpdate,
$course,
} from '@pennlabs/pca-backend/db/schema/course'
import { Status, type WebhookPayload } from '@/types/alert'
import { ENV } from '@/core/env'

export const updateCourseStatus = async (
payload: WebhookPayload,
requestBody: string,
) => {
const sectionQuery = await db
.select({
id: $section.id,
status: $section.status,
})
.from($section)
.where(
and(
eq($section.fullCode, payload.section_id_normalized),
eq($course.semester, ENV.CURRENT_SEMESTER),
),
)
.innerJoin($course, eq($section.courseId, $course.id))
const previousStatus = sectionQuery[0]?.status
const sectionId = sectionQuery[0]?.id
const typedPreviousStatus = Status[previousStatus as keyof typeof Status]
if (typedPreviousStatus !== payload.previous_status) {
return false
}
await db.transaction(async tx => {
await tx
.update($section)
.set({
status: payload.status,
})
.where(eq($section.id, sectionId))
await tx.insert($statusUpdate).values({
oldStatus: payload.previous_status,
newStatus: payload.status,
createdAt: new Date().toISOString(),
alertSent: true,
requestBody: requestBody,
sectionId: Number(sectionId),
inAddDropPeriod: false,
})
})
return true
}
3 changes: 3 additions & 0 deletions services/alert/webhook/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { z, ZodError } from "zod"
import { Status } from "@/types/alert"
import { sendCourseAlertEmail } from "@/core/ses"
import { getRegisteredUsers } from "@/core/query"
import { updateCourseStatus } from "./core/save"

const statusSchema = z.nativeEnum(Status)

Expand All @@ -26,7 +27,9 @@ export const handler: APIGatewayProxyHandlerV2 = async (event) => {
if (!event.body) throw new BodyMissingError()
const payload = webhookPayloadSchema.parse(JSON.parse(event.body))
console.log("[org.pennlabs.courses.alert.webhook]: Incoming alert", payload)
const isValidStatusUpdate = await updateCourseStatus(payload, event.body);
if (
isValidStatusUpdate &&
payload.previous_status !== Status.OPEN &&
payload.status == Status.OPEN
) {
Expand Down
8 changes: 8 additions & 0 deletions services/alert/webhook/src/types/alert.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,11 @@ export enum Status {
CANCELLED = "X",
UNLISTED = "",
}

export type WebhookPayload = {
previous_status: Status,
section_id: string;
section_id_normalized: string;
status: Status;
term: string;
};
Loading

0 comments on commit f7890cd

Please sign in to comment.