adding - #314
Conversation
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Visit the preview URL for this PR (updated for commit d9fd083): https://hacklytics2027--pr-314-gccciz25.web.app (expires Thu, 13 Aug 2026 03:26:30 GMT) 🔥 via Firebase Hosting GitHub Action 🌎 Sign: c48ba34db61581e25fe2978355160b5eefe0e83f |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit d9fd083. Configure here.
| .update(hackathonParticipants) | ||
| .set({ acceptanceEmailSentAt: new Date() }) | ||
| .where(eq(hackathonParticipants.id, participant.id)); | ||
| emailed++; |
There was a problem hiding this comment.
Retry resends acceptance emails
Medium Severity
sendMassAcceptanceEmails stamps acceptanceEmailSentAt after a successful send but never skips recipients who already have that timestamp. Retrying the same batch after a timeout or partial failure sends duplicate acceptance emails to people who already received one.
Reviewed by Cursor Bugbot for commit d9fd083. Configure here.
| // admin here would render the whole admin nav for someone every one of | ||
| // those pages rejects. | ||
| isAdmin: isStaffRole(admin?.role), | ||
| isScanner: !!admin, |
There was a problem hiding this comment.
Volunteers flagged as project leaders
Medium Severity
Portal context sets isProjectLeader with isProjectLeader || !!admin, so any active admin row counts—including volunteers. Volunteers are not full staff, but they still get isProjectLeader: true without a project_leader row, which drives UI such as the “My Initiatives” nav entry they cannot use.
Reviewed by Cursor Bugbot for commit d9fd083. Configure here.
| voteCount: sql`excluded.vote_count`, | ||
| computedAt: sql`now()`, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Recompute duplicates result rows
Medium Severity
computeResults upserts on (hackathonId, projectId, track) while always inserting track: null. On PostgreSQL, unique constraints treat each NULL in track as distinct, so a second compute before publish can insert duplicate rows instead of updating existing drafts.
Reviewed by Cursor Bugbot for commit d9fd083. Configure here.
|
| Filename | Overview |
|---|---|
| packages/api/src/routers/hackathon/admin.ts | Adds scalable attendee administration and scanner operations, but acceptance retries ignore the newly recorded delivery marker and can resend completed emails. |
| packages/db/src/schemas/judge.ts | Replaces map storage with judging results and adds judging constraints without preserving existing map records through a migration. |
| packages/api/src/routers/admin.ts | Retains administrative provisioning but omits the new volunteer role from the creation contract. |
| packages/api/src/middleware/procedures.ts | Separates volunteer scanner permissions from full staff permissions with appropriately scoped middleware. |
| packages/api/src/routers/hackathon/announce.ts | Adds scoped, deduplicated, batched announcement delivery with escaped message content. |
| packages/auth/src/email.ts | Adds reusable escaped HTML email templates for announcements and acceptances. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Organizer selects participants] --> B[Query participants by IDs and hackathon]
B --> C[Approve matching rows]
C --> D[Send acceptance email sequentially]
D --> E[Write acceptanceEmailSentAt]
D -->|timeout or failure| F[Organizer retries selection]
F --> B
B -->|marker not checked| G[Previously emailed participants selected again]
G --> D
Comments Outside Diff (2)
-
packages/api/src/routers/hackathon/admin.ts, line 281-289 (link)Acceptance retries resend emails
When an organizer retries a partially completed mass-accept operation, this query selects participants whose
acceptanceEmailSentAtis already set, and the send loop emails them again before overwriting the marker, causing duplicate acceptance messages. -
packages/api/src/routers/admin.ts, line 148 (link)Volunteer role cannot be provisioned
When a super administrator attempts to provision a volunteer scanner,
admin.createrejects the newvolunteerrole even though scanner access requires an active admin row, forcing volunteer accounts to be created through direct database changes.
Reviews (1): Last reviewed commit: "adding" | Re-trigger Greptile
| ], | ||
| ); | ||
|
|
||
| // Map images for hackathon venues | ||
| export const hackathonMaps = pgTable( | ||
| "hackathon_map", | ||
| /** | ||
| * A frozen placing, computed once when judging closes. | ||
| * | ||
| * getRankings recomputes the whole ordering on every call, and its z-score | ||
| * normalisation runs over the entire vote set — so one late vote silently | ||
| * changes every project's score, including ones already announced. The | ||
| * ordering existed only inside an HTTP response; nothing in the product could | ||
| * say who won yesterday. | ||
| * | ||
| * A snapshot instead: computed deliberately, reviewable while unpublished, and | ||
| * unchanged by anything that happens to the votes afterwards. | ||
| */ | ||
| export const hackathonResults = pgTable( | ||
| "hackathon_result", | ||
| { | ||
| id: uuid("id").defaultRandom().primaryKey(), | ||
| hackathonId: uuid("hackathon_id") | ||
| .notNull() | ||
| .references(() => hackathons.id, { onDelete: "cascade" }), | ||
| imageUrl: text("image_url").notNull(), | ||
| name: text("name"), | ||
| order: integer("order").notNull().default(0), | ||
| createdAt: timestamp("created_at").defaultNow().notNull(), | ||
| projectId: uuid("project_id") | ||
| .notNull() | ||
| .references(() => judgingProjects.id, { onDelete: "cascade" }), | ||
| /** Carried across at compute time so results survive the judging tables | ||
| * and can name the team that actually built the thing. */ | ||
| sourceProjectId: uuid("source_project_id").references( | ||
| () => hackathonProjects.id, | ||
| { onDelete: "set null" }, | ||
| ), | ||
| /** Which prize this placing is for. Null is the overall ranking. */ | ||
| track: text("track"), | ||
| placement: integer("placement").notNull(), | ||
| /** The blended score at the moment of computation. `numeric` because the | ||
| * pipeline produces a float — hackathon_project.score is an integer and | ||
| * could never have held this value. */ | ||
| weightedScore: numeric("weighted_score", { precision: 6, scale: 2 }), | ||
| voteCount: integer("vote_count").notNull().default(0), | ||
| /** Null while the snapshot is a draft. Set on publish; cleared on | ||
| * unpublish, which is what makes publishing reversible. */ | ||
| publishedAt: timestamp("published_at"), | ||
| computedAt: timestamp("computed_at").defaultNow().notNull(), | ||
| }, | ||
| (table) => [index("map_hackathon_id_idx").on(table.hackathonId)], | ||
| (table) => [ |


Note
High Risk
Touches authorization tiers, judging/results integrity, mass email, and destructive hackathon operations across API and schema—high blast radius even with extensive tests.
Overview
This PR is a large operational pass on hackathon admin, judging, and infrastructure so a full-venue event does not melt the API or mis-report outcomes.
Staffing and check-in: A new
volunteeradmin role can scan badges viaisScannerwhileisAdminrejects volunteers for destructive or roster-wide actions. Portal context exposesisScannerseparately fromisAdmin. Event staff can list and undo event check-ins; hackathon delete now requires typing the exact name, and delete can fail on FK when club memberships still reference the edition.Participants and email: Attendee roster and analytics move to DB-side filtering, paging, and aggregates instead of shipping full PII to the browser. Mass acceptance reports
approved/emailed/failedEmails, batches updates, stampsacceptanceEmailSentAt, and uses a pooled SMTP transporter. A separate announcement router emails interested/registered/approved/checked-in audiences in chunks.Judging lifecycle: CSV bulk import and
hackathon_mapare removed.promoteSubmissionslinks judgeable projects to submissions (sourceProjectId), with idempotent table numbering. Queue assignment/rebuild andforceSkipOvertimeget safer candidate selection, cross-hackathon guards,startedAtclaims, and chunked queue inserts.computeRankingis shared;hackathon_resultstores computed placings with publish/unpublish; publicgetResultsreads published rows only. Admin can fix or withdraw projects;isCreateXflows from team submission.Scale and safety: DDoS keys on user id when signed in; deploy env sets burst thresholds. Cache eviction is narrowed (no blanket
hackathon*on every scan). Auth session callback drops per-request judge lookup. Schema adds indexes,hackathon_result, and drops maps; README documents conditional pre-migration forproject_leader.Reviewed by Cursor Bugbot for commit d9fd083. Bugbot is set up for automated code reviews on this repo. Configure here.