a tool for shared writing and social publishing
0

Configure Feed

Select the types of activity you want to include in your feed.

remove debug api and route

Jared Pereira (May 27, 2026, 5:33 PM EDT) b77d6696 63b5e6cd

-297
-47
src/identity/profileCache.ts
··· 105 105 return result; 106 106 }, 107 107 ); 108 - 109 - export type DebugCacheEntry = 110 - | { status: "hit"; profile: Profile | null; updatedAt: number; ageMs: number; expired: boolean } 111 - | { status: "miss" } 112 - | { status: "malformed"; raw: string } 113 - | { status: "disabled" }; 114 - 115 - export async function debugReadCache( 116 - dids: string[], 117 - ): Promise<Map<string, DebugCacheEntry>> { 118 - const out = new Map<string, DebugCacheEntry>(); 119 - if (!redisClient) { 120 - for (const did of dids) out.set(did, { status: "disabled" }); 121 - return out; 122 - } 123 - if (dids.length === 0) return out; 124 - 125 - const raw = await redisClient.mget(...dids.map(profileKey)); 126 - const now = Date.now(); 127 - raw.forEach((value, i) => { 128 - const did = dids[i]; 129 - if (!value) { 130 - out.set(did, { status: "miss" }); 131 - return; 132 - } 133 - try { 134 - const entry = JSON.parse(value) as CachedEntry; 135 - const ageMs = now - entry.updatedAt; 136 - out.set(did, { 137 - status: "hit", 138 - profile: entry.profile, 139 - updatedAt: entry.updatedAt, 140 - ageMs, 141 - expired: ageMs > MAX_TTL * 1000, 142 - }); 143 - } catch { 144 - out.set(did, { status: "malformed", raw: value }); 145 - } 146 - }); 147 - return out; 148 - } 149 - 150 - export async function debugFetchProfiles( 151 - dids: string[], 152 - ): Promise<Map<string, Profile | null>> { 153 - return fetchProfiles(dids); 154 - }
-199
app/debug/profile-cache/page.tsx
··· 1 - import { 2 - debugFetchProfiles, 3 - debugReadCache, 4 - type DebugCacheEntry, 5 - type Profile, 6 - } from "src/identity/profileCache"; 7 - 8 - export const dynamic = "force-dynamic"; 9 - export const runtime = "nodejs"; 10 - 11 - type SearchParams = Promise<{ dids?: string }>; 12 - 13 - export default async function ProfileCacheDebugPage({ 14 - searchParams, 15 - }: { 16 - searchParams: SearchParams; 17 - }) { 18 - const { dids: didsParam } = await searchParams; 19 - const dids = parseDids(didsParam); 20 - 21 - let rows: { 22 - did: string; 23 - cache: DebugCacheEntry; 24 - live: Profile | null; 25 - }[] = []; 26 - 27 - if (dids.length > 0) { 28 - const [cacheEntries, fetched] = await Promise.all([ 29 - debugReadCache(dids), 30 - debugFetchProfiles(dids), 31 - ]); 32 - rows = dids.map((did) => ({ 33 - did, 34 - cache: cacheEntries.get(did) ?? { status: "miss" }, 35 - live: fetched.get(did) ?? null, 36 - })); 37 - } 38 - 39 - return ( 40 - <div className="max-w-4xl mx-auto p-6 flex flex-col gap-4"> 41 - <h1 className="text-xl font-bold">Profile Cache Debug</h1> 42 - <p className="text-sm text-tertiary"> 43 - Shows what&apos;s in the Redis cache and what bsky&apos;s 44 - getProfiles returns right now. The cache is not updated. 45 - </p> 46 - 47 - <form method="get" className="flex flex-col gap-2"> 48 - <label htmlFor="dids" className="text-sm font-bold"> 49 - DIDs (comma- or whitespace-separated) 50 - </label> 51 - <textarea 52 - id="dids" 53 - name="dids" 54 - defaultValue={didsParam ?? ""} 55 - rows={4} 56 - className="border border-border rounded-md p-2 font-mono text-sm" 57 - placeholder="did:plc:..., did:plc:..." 58 - /> 59 - <button 60 - type="submit" 61 - className="self-start bg-accent-1 text-accent-2 font-bold px-3 py-1 rounded-md text-sm" 62 - > 63 - Inspect 64 - </button> 65 - </form> 66 - 67 - {dids.length === 0 ? ( 68 - <div className="text-sm italic text-tertiary"> 69 - Enter one or more DIDs above to inspect. 70 - </div> 71 - ) : ( 72 - <div className="flex flex-col gap-4"> 73 - {rows.map((row) => ( 74 - <ProfileRow key={row.did} {...row} /> 75 - ))} 76 - </div> 77 - )} 78 - </div> 79 - ); 80 - } 81 - 82 - function ProfileRow(props: { 83 - did: string; 84 - cache: DebugCacheEntry; 85 - live: Profile | null; 86 - }) { 87 - return ( 88 - <div className="border border-border rounded-md p-3 flex flex-col gap-2"> 89 - <div className="font-mono text-sm font-bold break-all">{props.did}</div> 90 - <div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> 91 - <Pane title={`Cache — ${cacheLabel(props.cache)}`}> 92 - <pre className="text-xs whitespace-pre-wrap break-all"> 93 - {JSON.stringify(props.cache, null, 2)} 94 - </pre> 95 - </Pane> 96 - <Pane title={`Live — ${props.live ? "found" : "null"}`}> 97 - <pre className="text-xs whitespace-pre-wrap break-all"> 98 - {JSON.stringify(props.live, null, 2)} 99 - </pre> 100 - </Pane> 101 - </div> 102 - <DiffSummary cache={props.cache} live={props.live} /> 103 - </div> 104 - ); 105 - } 106 - 107 - function Pane({ 108 - title, 109 - children, 110 - }: { 111 - title: string; 112 - children: React.ReactNode; 113 - }) { 114 - return ( 115 - <div className="flex flex-col gap-1"> 116 - <div className="text-xs font-bold text-tertiary uppercase">{title}</div> 117 - <div className="bg-border-light rounded-md p-2 overflow-auto"> 118 - {children} 119 - </div> 120 - </div> 121 - ); 122 - } 123 - 124 - function cacheLabel(entry: DebugCacheEntry) { 125 - if (entry.status !== "hit") return entry.status; 126 - const ageSec = Math.round(entry.ageMs / 1000); 127 - return entry.expired 128 - ? `hit (expired, age ${ageSec}s)` 129 - : `hit (age ${ageSec}s)`; 130 - } 131 - 132 - function DiffSummary({ 133 - cache, 134 - live, 135 - }: { 136 - cache: DebugCacheEntry; 137 - live: Profile | null; 138 - }) { 139 - if (cache.status !== "hit") { 140 - return ( 141 - <div className="text-xs italic text-tertiary"> 142 - Nothing in cache to compare. 143 - </div> 144 - ); 145 - } 146 - const cached = cache.profile; 147 - if (!cached && !live) { 148 - return ( 149 - <div className="text-xs italic text-tertiary"> 150 - Both cache and live are null (negative cache hit). 151 - </div> 152 - ); 153 - } 154 - if (!cached || !live) { 155 - return ( 156 - <div className="text-xs italic text-accent-contrast"> 157 - Mismatch: one side is null ({!cached ? "cache" : "live"}). 158 - </div> 159 - ); 160 - } 161 - const fields: (keyof Profile)[] = [ 162 - "handle", 163 - "displayName", 164 - "avatar", 165 - "description", 166 - ]; 167 - const diffs = fields.filter((f) => cached[f] !== live[f]); 168 - if (diffs.length === 0) { 169 - return ( 170 - <div className="text-xs italic text-tertiary"> 171 - Cache matches live for handle/displayName/avatar/description. 172 - </div> 173 - ); 174 - } 175 - return ( 176 - <div className="text-xs flex flex-col gap-0.5"> 177 - <div className="font-bold">Differs on: {diffs.join(", ")}</div> 178 - {diffs.map((f) => ( 179 - <div key={f} className="font-mono"> 180 - <span className="text-tertiary">{f}:</span>{" "} 181 - <span>cache={JSON.stringify(cached[f])}</span>{" "} 182 - <span>live={JSON.stringify(live[f])}</span> 183 - </div> 184 - ))} 185 - </div> 186 - ); 187 - } 188 - 189 - function parseDids(raw: string | undefined): string[] { 190 - if (!raw) return []; 191 - return Array.from( 192 - new Set( 193 - raw 194 - .split(/[\s,]+/) 195 - .map((s) => s.trim()) 196 - .filter(Boolean), 197 - ), 198 - ); 199 - }
-51
app/api/debug/profile-cache/route.ts
··· 1 - import { NextRequest } from "next/server"; 2 - import { 3 - debugFetchProfiles, 4 - debugReadCache, 5 - type DebugCacheEntry, 6 - type Profile, 7 - } from "src/identity/profileCache"; 8 - 9 - export const runtime = "nodejs"; 10 - export const dynamic = "force-dynamic"; 11 - 12 - // GET /api/debug/profile-cache?dids=did:plc:foo,did:plc:bar 13 - // Reports what's currently in Redis for each DID and what the upstream 14 - // Bluesky getProfiles call returns right now. Does not write to the cache. 15 - export async function GET(req: NextRequest) { 16 - const param = req.nextUrl.searchParams.get("dids"); 17 - if (!param) { 18 - return Response.json( 19 - { error: "`dids` query param required (comma-separated)" }, 20 - { status: 400 }, 21 - ); 22 - } 23 - 24 - const dids = Array.from( 25 - new Set( 26 - param 27 - .split(",") 28 - .map((s) => s.trim()) 29 - .filter(Boolean), 30 - ), 31 - ); 32 - if (dids.length === 0) { 33 - return Response.json({ error: "no DIDs provided" }, { status: 400 }); 34 - } 35 - 36 - const [cacheEntries, fetched] = await Promise.all([ 37 - debugReadCache(dids), 38 - debugFetchProfiles(dids), 39 - ]); 40 - 41 - const result = dids.map((did) => { 42 - const cache: DebugCacheEntry = cacheEntries.get(did) ?? { status: "miss" }; 43 - const live: Profile | null = fetched.get(did) ?? null; 44 - return { did, cache, live }; 45 - }); 46 - 47 - return Response.json( 48 - { dids, result }, 49 - { headers: { "Cache-Control": "no-store" } }, 50 - ); 51 - }