-- AppView database schema 0005: profile metadata + per-post avatar refs. -- -- Why -- The Profile-View-Page and PostCard both render a user avatar. -- Fetching the live `app.bsky.actor.profile/self` record from every -- user's PDS on every render doesn't scale, and isn't always reachable -- (e.g. a did:web: user whose PDS is offline). We cache the -- denormalised profile fields the UI shows keyed by did, indexed by -- handle so the `/api/profile/` lookup is index-driven. -- -- Source of truth: the user's own PDS. The PDS pushes profile records -- via the existing `/internal/ingest-commit` path; this migration -- adds the `(collection, action, rkey) == ('app.bsky.actor.profile', -- 'create', 'self')` arm to the AppView's indexer to populate this -- table. -- -- All fields nullable: a profile record can omit displayName, -- description, avatar, banner independently. -- -- post_count / follower_count / following_count are denormalised -- counts populated only when the row is created/replaced; the -- Profile-View-Page reads them here so it doesn't have to issue a -- separate COUNT(*) over posts/follows. -- -- The avatar_cid on posts is the resolved profile-avatar blob ref -- (or NULL) for the post's author. The AppView fills it in at -- upsert_post-time from the profiles table; the PostCard reads it -- to inline an without a per-row -- PDS round-trip. CREATE TABLE profiles ( did TEXT PRIMARY KEY, handle TEXT NOT NULL, display_name TEXT, description TEXT, avatar_cid TEXT, banner_cid TEXT, post_count BIGINT NOT NULL DEFAULT 0, follower_count BIGINT NOT NULL DEFAULT 0, following_count BIGINT NOT NULL DEFAULT 0, indexed_at TIMESTAMPTZ NOT NULL DEFAULT now() ); CREATE INDEX profiles_indexed_at_idx ON profiles (indexed_at DESC); -- Backfill: seed a profile row for every handle we've already -- resolved through the posts.did → posts.handle mapping. The display -- fields stay NULL — they need the live PDS profile record. INSERT INTO profiles (did, handle) SELECT DISTINCT ON (did) did, handle FROM posts WHERE handle <> '' ORDER BY did, indexed_at DESC ON CONFLICT (did) DO NOTHING; ALTER TABLE posts ADD COLUMN IF NOT EXISTS avatar_cid TEXT;