diff --git a/backend/migrations/0049_profile_updated_at.sql b/backend/migrations/0049_profile_updated_at.sql new file mode 100644 index 0000000..c6580d7 --- /dev/null +++ b/backend/migrations/0049_profile_updated_at.sql @@ -0,0 +1,5 @@ +-- Add profile_updated_at to track when profile data was last refreshed from Bluesky +ALTER TABLE users ADD COLUMN profile_updated_at INTEGER; + +-- Backfill existing rows with updated_at value +UPDATE users SET profile_updated_at = updated_at WHERE profile_updated_at IS NULL; diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index 8172f4c..3f3b960 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -638,14 +638,15 @@ export async function handleAuthCallback( // Store/update user in D1 BEFORE storing session (sessions table has FK to users) await env.DB.prepare( ` - INSERT INTO users (did, handle, display_name, avatar_url, pds_url, updated_at, registered_at) - VALUES (?, ?, ?, ?, ?, unixepoch(), unixepoch()) + INSERT INTO users (did, handle, display_name, avatar_url, pds_url, updated_at, registered_at, profile_updated_at) + VALUES (?, ?, ?, ?, ?, unixepoch(), unixepoch(), unixepoch()) ON CONFLICT(did) DO UPDATE SET handle = excluded.handle, display_name = excluded.display_name, avatar_url = excluded.avatar_url, pds_url = excluded.pds_url, updated_at = unixepoch(), + profile_updated_at = unixepoch(), registered_at = COALESCE(users.registered_at, unixepoch()) ` ) @@ -700,6 +701,8 @@ export async function handleAuthCallback( } } +const PROFILE_REFRESH_INTERVAL = 24 * 60 * 60; // 24 hours in seconds + export async function handleAuthMe(request: Request, env: Env): Promise { const session = await getSessionFromRequest(request, env); @@ -713,12 +716,56 @@ export async function handleAuthMe(request: Request, env: Env): Promise(); + + const now = Math.floor(Date.now() / 1000); + const profileAge = user?.profile_updated_at ? now - user.profile_updated_at : Infinity; + + if (profileAge > PROFILE_REFRESH_INTERVAL) { + const profileUrl = `https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(session.did)}`; + const profileResponse = await fetch(profileUrl); + + if (profileResponse.ok) { + const profile = (await profileResponse.json()) as { + handle: string; + displayName?: string; + avatar?: string; + }; + + handle = profile.handle; + displayName = profile.displayName; + avatarUrl = profile.avatar; + + // Update users table and session + await env.DB.prepare( + `UPDATE users SET handle = ?, display_name = ?, avatar_url = ?, profile_updated_at = unixepoch(), updated_at = unixepoch() WHERE did = ?` + ) + .bind(handle, displayName || null, avatarUrl || null, session.did) + .run(); + + await env.DB.prepare( + `UPDATE sessions SET handle = ?, display_name = ?, avatar_url = ? WHERE did = ?` + ) + .bind(handle, displayName || null, avatarUrl || null, session.did) + .run(); + } + } + } catch (error) { + // Non-critical: if profile refresh fails, return cached data + console.error('Profile refresh error:', error); + } + return new Response( JSON.stringify({ did: session.did, - handle: session.handle, - displayName: session.displayName, - avatarUrl: session.avatarUrl, + handle, + displayName, + avatarUrl, pdsUrl: session.pdsUrl, tier, limits, diff --git a/frontend/src/lib/components/Sidebar.svelte b/frontend/src/lib/components/Sidebar.svelte index 6e43608..235bc3c 100644 --- a/frontend/src/lib/components/Sidebar.svelte +++ b/frontend/src/lib/components/Sidebar.svelte @@ -309,7 +309,17 @@