a a vibe-coded abomination experiment of a fragrance review platform built on the atmosphere. drydown.social

another attempt at fixing meta tag stuff

+53 -57
+53 -57
functions/[[path]].ts
··· 56 56 let dataFetchSucceeded = false; 57 57 58 58 try { 59 - // Resolve Handle if needed 60 - let did = decodeURIComponent(handle); 61 - // Strip leading @ if present 62 - if (did.startsWith('@')) { 63 - did = did.slice(1); 64 - } 65 - 66 - if (!did.startsWith('did:')) { 67 - const resolveRes = await fetchWithTimeout( 68 - `${publicServiceUrl}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(did)}`, 69 - FETCH_TIMEOUT_MS 70 - ); 71 - if (resolveRes.ok) { 72 - const data = await resolveRes.json() as { did: string }; 73 - did = data.did; 74 - } 75 - } 59 + const actor = decodeURIComponent(handle); // Actor can be a handle or DID 60 + let did = actor.startsWith('@') ? actor.slice(1) : actor; 76 61 77 62 if (rkey) { 78 63 // --- Single Review Mode --- 79 - const reviewRes = await fetchWithTimeout( 64 + // We start fetching the review and the profile at the same time. 65 + const reviewPromise = fetchWithTimeout( 80 66 `${publicServiceUrl}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=social.drydown.review&rkey=${encodeURIComponent(rkey)}`, 81 67 FETCH_TIMEOUT_MS 82 - ); 68 + ).catch(() => null); 83 69 84 - if (reviewRes.ok) { 85 - const reviewData = await reviewRes.json() as any; 86 - const value = reviewData.value; 70 + const profilePromise = fetchWithTimeout( 71 + `${publicServiceUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`, 72 + FETCH_TIMEOUT_MS 73 + ).catch(() => null); 87 74 88 - // Fetch fragrance record and author profile in parallel — previously 89 - // these were sequential, which doubled the API latency for review pages. 90 - const fragPromise: Promise<Response | null> = value.fragrance 91 - ? (() => { 92 - const parts = (value.fragrance as string).split('/'); 93 - const fDid = parts[2]; 94 - const fTo = parts[3]; 95 - const fRkey = parts[4]; 96 - return fetchWithTimeout( 97 - `${publicServiceUrl}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(fDid)}&collection=${encodeURIComponent(fTo)}&rkey=${encodeURIComponent(fRkey)}`, 98 - FETCH_TIMEOUT_MS 99 - ).catch(() => null); 100 - })() 101 - : Promise.resolve(null); 75 + const [reviewRes, profileRes] = await Promise.all([reviewPromise, profilePromise]); 102 76 103 - const profilePromise: Promise<Response | null> = fetchWithTimeout( 104 - `${publicServiceUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`, 105 - FETCH_TIMEOUT_MS 106 - ).catch(() => null); 77 + let authorName = ''; 78 + let authorAvatar = ''; 79 + if (profileRes && profileRes.ok) { 80 + try { 81 + const profile = await profileRes.json() as any; 82 + authorName = profile.displayName || profile.handle || ''; 83 + authorAvatar = profile.avatar || ''; 84 + } catch (e) { 85 + // Ignore JSON parse errors 86 + } 87 + } 107 88 108 - const [fragRes, profileRes] = await Promise.all([fragPromise, profilePromise]); 89 + if (reviewRes && reviewRes.ok) { 90 + const reviewData = await reviewRes.json() as any; 91 + const value = reviewData.value; 92 + const sysUri = reviewData.uri; 93 + 94 + // If we resolved a handle, extract the proper DID from the returned URI 95 + if (sysUri && sysUri.startsWith('at://')) { 96 + did = sysUri.split('/')[2]; 97 + } 98 + 99 + // Fetch fragrance record 100 + let fragRes: Response | null = null; 101 + if (value.fragrance) { 102 + const parts = (value.fragrance as string).split('/'); 103 + const fDid = parts[2]; 104 + const fTo = parts[3]; 105 + const fRkey = parts[4]; 106 + fragRes = await fetchWithTimeout( 107 + `${publicServiceUrl}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(fDid)}&collection=${encodeURIComponent(fTo)}&rkey=${encodeURIComponent(fRkey)}`, 108 + FETCH_TIMEOUT_MS 109 + ).catch(() => null); 110 + } 109 111 110 112 let fragName = ''; 111 113 if (fragRes && fragRes.ok) { 112 - const fragData = await fragRes.json() as any; 113 - fragName = fragData.value?.name || ''; 114 - } 115 - 116 - let authorName = ''; 117 - let authorAvatar = ''; 118 - if (profileRes && profileRes.ok) { 119 - const profile = await profileRes.json() as any; 120 - authorName = profile.displayName || profile.handle || ''; 121 - authorAvatar = profile.avatar || ''; 114 + try { 115 + const fragData = await fragRes.json() as any; 116 + fragName = fragData.value?.name || ''; 117 + } catch (e) { 118 + // Ignore 119 + } 122 120 } 123 121 124 122 title = fragName ? `${fragName} Review` : 'Fragrance Review'; 125 123 if (authorName) title = `${title} by ${authorName}`; 126 124 127 - // Clamp rating to 0-5 to avoid RangeError in String.repeat() 125 + // Clamp rating to 0-5 128 126 const rawRating = typeof value.overallRating === 'number' ? value.overallRating : 0; 129 127 const rating = Math.min(5, Math.max(0, Math.round(rawRating))); 130 128 const stars = '★'.repeat(rating) + '☆'.repeat(5 - rating); 131 129 const text = (value.text as string) || ''; 132 130 description = `${stars} ${text.substring(0, 150)}${text.length > 150 ? '...' : ''}`.trim(); 133 131 134 - // Use author avatar for review cards — previously this was commented out, 135 - // causing every review share to fall back to the missing default-og.png. 136 132 if (authorAvatar) { 137 133 image = authorAvatar; 138 134 } ··· 144 140 const profileRes = await fetchWithTimeout( 145 141 `${publicServiceUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(did)}`, 146 142 FETCH_TIMEOUT_MS 147 - ); 143 + ).catch(() => null); 148 144 149 - if (profileRes.ok) { 145 + if (profileRes && profileRes.ok) { 150 146 const profile = await profileRes.json() as any; 151 147 title = `${profile.displayName || profile.handle} (@${profile.handle})`; 152 148 description = `Read fragrance reviews by ${profile.displayName || profile.handle} on Drydown.`; ··· 158 154 } 159 155 } catch (e) { 160 156 console.error('SEO Injection Failed', e); 161 - // Falls back to defaults — Cache-Control below will use short TTL 157 + // Falls back to defaults 162 158 } 163 159 164 160 // 3. Rewrite HTML
public/default-og.png

This is a binary file and will not be displayed.