ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto
17
fork

Configure Feed

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

at master 151 lines 5.5 kB view raw
1-- ATlast Database Schema 2-- Migration Plan v2.0 - Phase 1 3-- Self-hosted PostgreSQL schema with fuzzy matching support 4 5-- Enable extensions 6CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; 7CREATE EXTENSION IF NOT EXISTS "pg_trgm"; -- For fuzzy matching 8 9-- OAuth state storage (transient) 10CREATE TABLE oauth_states ( 11 state TEXT PRIMARY KEY, 12 data JSONB NOT NULL, 13 created_at TIMESTAMP DEFAULT NOW() 14); 15CREATE INDEX idx_oauth_states_created ON oauth_states(created_at); 16 17-- OAuth sessions (transient) 18CREATE TABLE oauth_sessions ( 19 did TEXT PRIMARY KEY, 20 session_data JSONB NOT NULL, 21 updated_at TIMESTAMP DEFAULT NOW() 22); 23 24-- User sessions (transient) 25CREATE TABLE user_sessions ( 26 session_id TEXT PRIMARY KEY, 27 did TEXT NOT NULL, 28 fingerprint TEXT NOT NULL, 29 created_at TIMESTAMP DEFAULT NOW(), 30 expires_at TIMESTAMP NOT NULL 31); 32CREATE INDEX idx_user_sessions_did ON user_sessions(did); 33CREATE INDEX idx_user_sessions_expires ON user_sessions(expires_at); 34 35-- User uploads (persistent) 36CREATE TABLE user_uploads ( 37 upload_id TEXT PRIMARY KEY, 38 user_did TEXT NOT NULL, 39 source_platform TEXT NOT NULL, 40 created_at TIMESTAMP DEFAULT NOW(), 41 total_users INTEGER DEFAULT 0, 42 matched_users INTEGER DEFAULT 0, 43 unmatched_users INTEGER DEFAULT 0 44); 45CREATE INDEX idx_user_uploads_user_did ON user_uploads(user_did); 46-- Note: check_frequency and last_checked removed - no periodic checking in Phase 1 47 48-- Source accounts (persistent) 49CREATE TABLE source_accounts ( 50 id SERIAL PRIMARY KEY, 51 source_platform TEXT NOT NULL, 52 original_username TEXT NOT NULL, 53 normalized_username TEXT NOT NULL, 54 date_on_source TIMESTAMP, 55 created_at TIMESTAMP DEFAULT NOW(), 56 UNIQUE(source_platform, normalized_username) 57); 58CREATE INDEX idx_source_accounts_normalized ON source_accounts 59 USING gin(normalized_username gin_trgm_ops); -- Fuzzy matching! 60CREATE INDEX idx_source_accounts_platform ON source_accounts(source_platform); 61 62-- User-source follows (join table) 63CREATE TABLE user_source_follows ( 64 user_did TEXT NOT NULL, 65 upload_id TEXT NOT NULL REFERENCES user_uploads(upload_id) ON DELETE CASCADE, 66 source_account_id INTEGER NOT NULL REFERENCES source_accounts(id), 67 found_at TIMESTAMP DEFAULT NOW(), 68 PRIMARY KEY (upload_id, source_account_id) 69); 70CREATE INDEX idx_user_source_follows_user ON user_source_follows(user_did); 71CREATE INDEX idx_user_source_follows_source ON user_source_follows(source_account_id); 72 73-- AT Protocol matches (persistent) 74CREATE TABLE atproto_matches ( 75 id SERIAL PRIMARY KEY, 76 source_account_id INTEGER NOT NULL REFERENCES source_accounts(id), 77 atproto_did TEXT NOT NULL, 78 atproto_handle TEXT NOT NULL, 79 display_name TEXT, 80 match_score INTEGER NOT NULL, 81 post_count INTEGER, 82 follower_count INTEGER, 83 follow_status JSONB DEFAULT '{}', 84 found_at TIMESTAMP DEFAULT NOW(), 85 UNIQUE(source_account_id, atproto_did) 86); 87CREATE INDEX idx_atproto_matches_source ON atproto_matches(source_account_id); 88CREATE INDEX idx_atproto_matches_did ON atproto_matches(atproto_did); 89CREATE INDEX idx_atproto_matches_score ON atproto_matches(match_score DESC); 90 91-- User match status (persistent) 92CREATE TABLE user_match_status ( 93 user_did TEXT NOT NULL, 94 match_id INTEGER NOT NULL REFERENCES atproto_matches(id), 95 viewed BOOLEAN DEFAULT FALSE, 96 dismissed BOOLEAN DEFAULT FALSE, 97 followed BOOLEAN DEFAULT FALSE, 98 notified BOOLEAN DEFAULT FALSE, 99 updated_at TIMESTAMP DEFAULT NOW(), 100 PRIMARY KEY (user_did, match_id) 101); 102CREATE INDEX idx_user_match_status_user ON user_match_status(user_did); 103CREATE INDEX idx_user_match_status_notified ON user_match_status(user_did, notified) 104 WHERE notified = FALSE; 105 106-- Notification queue (transient - for Phase 2) 107CREATE TABLE notification_queue ( 108 id SERIAL PRIMARY KEY, 109 user_did TEXT NOT NULL, 110 match_id INTEGER NOT NULL REFERENCES atproto_matches(id), 111 notification_type TEXT NOT NULL, -- 'in_app', 'bluesky_dm', 'partner_api' 112 status TEXT DEFAULT 'pending', -- 'pending', 'sent', 'failed' 113 attempts INTEGER DEFAULT 0, 114 last_attempt TIMESTAMP, 115 error_message TEXT, -- Store error details for debugging 116 created_at TIMESTAMP DEFAULT NOW() 117); 118CREATE INDEX idx_notification_queue_status ON notification_queue(status) 119 WHERE status = 'pending'; 120CREATE INDEX idx_notification_queue_user ON notification_queue(user_did); 121 122-- Partner API keys (for Phase 2) 123CREATE TABLE partner_api_keys ( 124 id SERIAL PRIMARY KEY, 125 partner_name TEXT NOT NULL, -- 'skylight', 'spark', etc. 126 api_key_hash TEXT NOT NULL UNIQUE, -- SHA-256 hashed API key 127 created_at TIMESTAMP DEFAULT NOW(), 128 last_used TIMESTAMP, 129 is_active BOOLEAN DEFAULT TRUE 130); 131CREATE INDEX idx_partner_api_keys_hash ON partner_api_keys(api_key_hash) 132 WHERE is_active = TRUE; 133 134-- Cleanup function for old transient data 135CREATE OR REPLACE FUNCTION cleanup_transient_data() RETURNS void AS $$ 136BEGIN 137 -- Clean expired OAuth states (1 hour) 138 DELETE FROM oauth_states WHERE created_at < NOW() - INTERVAL '1 hour'; 139 140 -- Clean expired sessions 141 DELETE FROM user_sessions WHERE expires_at < NOW(); 142 143 -- Clean old sent notifications (7 days) 144 DELETE FROM notification_queue 145 WHERE status = 'sent' AND created_at < NOW() - INTERVAL '7 days'; 146 147 -- Clean old failed notifications (30 days) 148 DELETE FROM notification_queue 149 WHERE status = 'failed' AND created_at < NOW() - INTERVAL '30 days'; 150END; 151$$ LANGUAGE plpgsql;