Skip to content

fix(certificates): Verify certificates by printed certificate number - #1161

Open
jeromehardaway wants to merge 2 commits into
masterfrom
fix/1148-certificate-verification
Open

fix(certificates): Verify certificates by printed certificate number#1161
jeromehardaway wants to merge 2 commits into
masterfrom
fix/1148-certificate-verification

Conversation

@jeromehardaway

Copy link
Copy Markdown
Contributor

Problem

Certificate verification was broken end to end:

  • Certificates are issued with a human-readable certificateNumber (VWC-YYYY-XXXXXX), and the PDF prints "Certificate No: " plus "Verify at: vetswhocode.io/verify/".
  • But every verification path queried by the Prisma row id: /api/certificates/verify used findUnique({ where: { id: number } }), getCertificateByNumber in src/lib/certificates.ts did the same, and formatCertificateData returned the cuid as the certificate number.
  • The printed /verify/<number> URL had no matching route at all, so every printed link 404'd.

Solution

  • getCertificateByNumber now queries the certificateNumber column first. If that misses and the input matches the cuid shape of row ids, it falls back to an id lookup so legacy certificates (null certificateNumber, printed the cuid) keep verifying.
  • /api/certificates/verify reuses the lib lookup instead of its own query, returns certificate.certificateNumber (falling back to the id for legacy rows), and now has a @swagger block.
  • formatCertificateData returns the real certificate number.
  • New public page src/pages/verify/[number].tsx resolves the printed URL: it calls /api/certificates/verify?number=... and renders the verified state (student, course, completion date, certificate number) or a clear invalid state, following the visual conventions of src/pages/certificates/[certificateId].tsx.
  • The URL printed in pdf-certificate.ts is unchanged — already-printed certificates keep working.

Verification

  • npm run typecheck — no errors in changed files; the 7 reported errors are pre-existing in untouched __tests__/**/j0di3/* files (verified identical on a clean master checkout).
  • npm run lint — changed files have 0 errors (2 pre-existing any warnings on existing signatures in certificates.ts); repo-wide errors are pre-existing in untouched files.
  • npm test — 42 files / 389 tests pass, including new __tests__/api/certificates/verify.test.ts (printed number resolves valid with correct student/course, unknown number returns valid:false with no id fallback, legacy cuid id falls back to id lookup, missing param 400, non-GET 405).
  • npm run build — succeeds; /verify/[number] and /api/certificates/verify present in route output.

Closes #1148

Verification looked up certificates by Prisma row id instead of the
certificateNumber column, so the VWC-YYYY-XXXXXX number printed on PDFs
never resolved, and the printed vetswhocode.io/verify/<number> URL had
no matching route.

- getCertificateByNumber now queries the certificateNumber column and
  falls back to a row-id lookup when the input matches the cuid shape,
  so legacy certificates that printed the id keep verifying
- formatCertificateData and the verify API response return the real
  certificate number instead of exposing the cuid
- /api/certificates/verify reuses the lib lookup and documents the
  endpoint with a swagger block
- New public page /verify/[number] renders the verified certificate
  (student, course, completion date) or a clear invalid state
- Regression tests cover printed-number lookup, unknown numbers, the
  legacy id fallback, missing param, and method handling
@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
vets-who-code-app Ready Ready Preview, Comment Jun 11, 2026 4:47pm

Request Review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes broken certificate verification by switching lookups to use the printed certificateNumber (VWC-YYYY-XXXXXX) while preserving backward compatibility for legacy certificates that printed the Prisma cuid row id. This also adds a public /verify/[number] page that matches the URL printed on PDFs and introduces regression tests for the verification API.

Changes:

  • Update certificate lookup logic to query certificateNumber first with legacy cuid fallback.
  • Refactor /api/certificates/verify to reuse the shared lookup and return the printed certificate number.
  • Add a public /verify/[number] page plus API regression tests and Swagger spec updates.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/pages/verify/[number].tsx Adds public verification page for printed /verify/<number> URLs.
src/pages/api/certificates/verify.ts Updates verification API to verify by printed certificate number via shared library function; adds Swagger docs.
src/lib/certificates.ts Fixes getCertificateByNumber + formatCertificateData to use certificateNumber with legacy fallback.
public/swagger-spec.json Adds OpenAPI entry for the verify endpoint.
tests/api/certificates/verify.test.ts Adds regression tests for printed-number verification and legacy fallback behavior.
Comments suppressed due to low confidence (1)

src/pages/api/certificates/verify.ts:76

  • This is a public verification endpoint, but it currently returns the student’s email address. Since the verification UI doesn’t use it and the printed certificate verification use-case doesn’t require it, consider omitting email to reduce unnecessary PII exposure.
            certificate: {
                certificateNumber: certificate.certificateNumber ?? certificate.id,
                student: {
                    name: certificate.user.name || "Unknown",
                    email: certificate.user.email,
                },

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +42 to +63
useEffect(() => {
if (!number || typeof number !== "string") return;

const verifyCertificate = async () => {
try {
const response = await fetch(
`/api/certificates/verify?number=${encodeURIComponent(number)}`
);
const data = await response.json();

if (response.ok && data.valid && data.certificate) {
setCertificate(data.certificate);
}
} catch {
// Treated as invalid below
} finally {
setLoading(false);
}
};

verifyCertificate();
}, [number]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants