Skip to content

Certificate verification is broken: lookup uses row id, not the printed certificate number #1148

Description

@jeromehardaway

Problem

Certificate verification is broken end-to-end. The VWC-YYYY-XXXXXX number printed on every certificate cannot be verified because the lookup queries the wrong column.

Certificates are issued with a human-readable certificateNumber (src/lib/certificates.ts:7-28), stored in a dedicated unique column (prisma/schema.prisma:360). The PDF prints it:

  • src/lib/pdf-certificate.ts:201Certificate No: ${data.certificateNumber} (e.g. VWC-2026-000123)
  • src/lib/pdf-certificate.ts:210Verify at: vetswhocode.io/verify/${data.certificateNumber}

But every verification path looks up by the Prisma row id (a cuid), not certificateNumber:

  • src/pages/api/certificates/verify.ts:30findUnique({ where: { id: number } })
  • src/lib/certificates.ts:115 (getCertificateByNumber) → where: { id: certificateNumber }
  • src/lib/certificates.ts:145 (formatCertificateData) → returns certificateNumber: certificate.id (exposes the cuid, contradicting the PDF)

So an employer entering the printed number gets:

{ valid: false, message: "Certificate not found. This certificate number is not valid." }

The certificate page itself instructs employers to verify via /api/certificates/verify?number={certificate.certificateNumber} (src/pages/certificates/[certificateId].tsx:238) — passing the VWC-... number the endpoint then looks up by id.

Secondary bug: the printed verify URL vetswhocode.io/verify/{number} has no matching route — only /api/certificates/verify and /certificates/[certificateId] exist. The printed link 404s.

Impact

High. Verification is the core trust feature of a veteran credential — it is how an employer confirms a graduate's accomplishment. Today every verification of a printed certificate number fails and the printed link is dead.

Proposed fix

  1. Query the certificateNumber column instead of id:
    • src/pages/api/certificates/verify.ts:30where: { certificateNumber: number }
    • src/lib/certificates.ts:115where: { certificateNumber }
    • src/lib/certificates.ts:145certificateNumber: certificate.certificateNumber
    • src/pages/api/certificates/verify.ts:64 → return certificate.certificateNumber
  2. Handle legacy/null certificateNumber rows (column is nullable) — backfill or fall back gracefully.
  3. Reconcile the printed verify URL: add a public /verify/[number] page that calls the API, or change src/lib/pdf-certificate.ts:210 to a URL that exists.
  4. Add a regression test: generate cert → verify by its VWC-YYYY-XXXXXX number → valid: true (complements [Task]: TEST-005: Test Certificate Generation Logic #823).

Acceptance criteria

  • Verifying with the printed VWC-YYYY-XXXXXX number returns valid: true with correct student/course.
  • The verify URL printed on the PDF resolves to a working page.
  • Regression test passes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugintermediateRequires moderate codebase familiarity; multi-file or design judgment

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions