diff --git a/src/lib/intake-links/index.ts b/src/lib/intake-links/index.ts index a583da3..dad9e0c 100644 --- a/src/lib/intake-links/index.ts +++ b/src/lib/intake-links/index.ts @@ -1,7 +1,16 @@ import "server-only"; import { getAdminPb } from "@/lib/pb/admin"; -import type { IntakeLinkRecord } from "@/lib/pb/types"; +import type { + IntakeLinkRecord, + IntakeLinkSubmissionRecord, +} from "@/lib/pb/types"; +import { + computeDateForCounter, + defaultCapFor, + isDailyCapReached, + nextCount, +} from "@/lib/intake-links/submissions"; const DEFAULT_DAILY_CAP = 100; @@ -102,3 +111,69 @@ export async function bumpSubmissionCount(link: IntakeLinkRecord): Promise last_submission_at: new Date().toISOString(), }); } + +/** + * Phase 9: True when the (link, project) pair is still usable right now. + * + * Reads today's counter from the new `intake_link_submissions` collection. + * On any DB read error, returns false — Phase 8 used the same conservative + * default (a flaky read fails closed, never open). + */ +export async function isLinkProjectUsable( + link: IntakeLinkRecord, + projectId: string, + now: Date = new Date() +): Promise { + if (!link.active) return false; + if (link.expires_at) { + const expiry = link.expires_at.slice(0, 10); + if (computeDateForCounter(now) > expiry) return false; + } + const cap = defaultCapFor(link.max_submissions_per_day); + const today = computeDateForCounter(now); + + const pb = await getAdminPb(); + try { + const row = await pb + .collection("intake_link_submissions") + .getFirstListItem( + `link = "${link.id}" && project = "${projectId}" && date = "${today}"` + ); + return !isDailyCapReached(row.count, cap); + } catch { + // No row for today → 0 submissions → below the cap (unless cap=0). + return !isDailyCapReached(0, cap); + } +} + +/** + * Phase 9: Increment today's counter for the (link, project) pair. + * + * Uses the unique `(link, project, date)` index: a fresh row is created on + * first submission of the day, subsequent submissions update the count. + */ +export async function bumpDailyCapForProject( + link: IntakeLinkRecord, + projectId: string +): Promise { + const pb = await getAdminPb(); + const today = computeDateForCounter(); + + try { + const existing = await pb + .collection("intake_link_submissions") + .getFirstListItem( + `link = "${link.id}" && project = "${projectId}" && date = "${today}"` + ); + await pb.collection("intake_link_submissions").update(existing.id, { + count: nextCount(existing.count), + }); + } catch { + await pb.collection("intake_link_submissions").create({ + link: link.id, + project: projectId, + date: today, + count: 1, + }); + } +}