From 250e9ad277a9c2ce7bcf6d7a84af861b227237fe Mon Sep 17 00:00:00 2001 From: "Alejandro E. Rendon" Date: Fri, 31 Jul 2026 06:11:39 -0500 Subject: [PATCH] Enhance certificate handling with improved Drive file ID extraction: Updated the certificate registry logic to support both raw Drive file IDs and various Drive URLs. Refactored the URL construction for certificate JSON retrieval to ensure proper handling of Drive links. Added error handling for invalid file IDs and improved response validation to prevent HTML responses from breaking the application. --- .env.example | 7 +++- src/lib/certificate-registry.ts | 69 ++++++++++++++++++++++++++++----- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/.env.example b/.env.example index 7be94ad..8a5d4b8 100644 --- a/.env.example +++ b/.env.example @@ -3,6 +3,9 @@ BASEPATH= # Local dev only — CI sets this per workflow (prod vs develop). NEXT_PUBLIC_APP_URL=https://2026.pycon.co # Certificates registry (build-time only; do not use NEXT_PUBLIC_*). -# Prefer a full URL, or set the Drive file id and the download URL is derived. -# CERTIFICATES_JSON_URL=https://drive.google.com/uc?export=download&id=YOUR_FILE_ID +# Either works: raw file id, OR a Drive share/view/open URL (id is extracted). +# CERTIFICATES_DRIVE_FILE_ID=1kcQ-CP2o-b_Xq4w323U3h_nSrn6FOGCD +# CERTIFICATES_DRIVE_FILE_ID=https://drive.google.com/file/d/1kcQ-CP2o-b_Xq4w323U3h_nSrn6FOGCD/view?usp=sharing +# Optional direct download URL (Drive view/share links are auto-rewritten): +# CERTIFICATES_JSON_URL=https://drive.usercontent.google.com/download?id=FILE_ID&export=download CERTIFICATES_DRIVE_FILE_ID= \ No newline at end of file diff --git a/src/lib/certificate-registry.ts b/src/lib/certificate-registry.ts index 8e817aa..3f5267e 100644 --- a/src/lib/certificate-registry.ts +++ b/src/lib/certificate-registry.ts @@ -20,17 +20,55 @@ const ROLE_ALIASES: Record = { organizador: "organizer", }; +const DRIVE_FILE_ID_PATTERNS = [ + /\/file\/d\/([a-zA-Z0-9_-]+)/, + /[?&]id=([a-zA-Z0-9_-]+)/, + /^([a-zA-Z0-9_-]{20,})$/, +]; + let registryPromise: Promise> | null = null; +/** Accepts a raw Drive file id or any common Drive share/view/open/download URL. */ +export function extractDriveFileId(value: string): string | undefined { + const trimmed = value.trim().replace(/^["']|["']$/g, ""); + if (!trimmed) { + return undefined; + } + + for (const pattern of DRIVE_FILE_ID_PATTERNS) { + const match = trimmed.match(pattern); + if (match?.[1]) { + return match[1]; + } + } + + return undefined; +} + +function buildDriveDownloadUrl(fileId: string): string { + return `https://drive.usercontent.google.com/download?id=${encodeURIComponent(fileId)}&export=download`; +} + function getCertificatesJsonUrl(): string | undefined { const explicit = process.env.CERTIFICATES_JSON_URL?.trim(); if (explicit) { - return explicit; + const driveId = extractDriveFileId(explicit); + // Sharing/view links return HTML; always rewrite Drive links to the download endpoint. + if (driveId) { + return buildDriveDownloadUrl(driveId); + } + return explicit.replace(/^["']|["']$/g, ""); } - const fileId = process.env.CERTIFICATES_DRIVE_FILE_ID?.trim(); - if (fileId) { - return `https://drive.google.com/uc?export=download&id=${encodeURIComponent(fileId)}`; + const fileIdRaw = process.env.CERTIFICATES_DRIVE_FILE_ID?.trim(); + if (fileIdRaw) { + const fileId = extractDriveFileId(fileIdRaw); + if (!fileId) { + throw new Error( + "CERTIFICATES_DRIVE_FILE_ID must be a Drive file id or Drive URL containing one.", + ); + } + return buildDriveDownloadUrl(fileId); } return undefined; @@ -86,6 +124,15 @@ function parseCertificateList(payload: unknown): Map { return registry; } +function isHtmlBody(contentType: string, body: string): boolean { + const trimmed = body.trimStart(); + return ( + contentType.includes("text/html") || + trimmed.startsWith("> { const url = getCertificatesJsonUrl(); @@ -98,6 +145,12 @@ async function fetchCertificateRegistry(): Promise> { const response = await fetch(url, { // Build-time only; avoid Next Data Cache surprises across rebuilds. cache: "no-store", + redirect: "follow", + headers: { + // Some Drive edges return an HTML interstitial without a browser-like UA. + Accept: "application/json,text/plain,*/*", + "User-Agent": "PyConColombiaWebsiteBuild/1.0", + }, }); if (!response.ok) { @@ -109,13 +162,9 @@ async function fetchCertificateRegistry(): Promise> { const contentType = response.headers.get("content-type") ?? ""; const body = await response.text(); - if ( - contentType.includes("text/html") || - body.trimStart().startsWith("