-- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- Posimai Brain Full-Text Save Migration -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- Purpose: Add full_text column to store article body from Reader -- Date: 2026-03-01 -- Impact: Critical data loss prevention -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- ────────────────────────────────────────────────────────────────────── -- STEP 1: Backup existing data -- ────────────────────────────────────────────────────────────────────── -- Before running migration, create backup: -- pg_dump -U brain_user -d brain_db -t articles > /backup/articles_$(date +%Y%m%d).sql -- ────────────────────────────────────────────────────────────────────── -- STEP 2: Add full_text column -- ────────────────────────────────────────────────────────────────────── BEGIN; -- Add full_text column for storing article body ALTER TABLE articles ADD COLUMN IF NOT EXISTS full_text TEXT; -- Add images column (optional, for future use) ALTER TABLE articles ADD COLUMN IF NOT EXISTS images TEXT[]; -- Add updated_at if it doesn't exist ALTER TABLE articles ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP DEFAULT NOW(); COMMIT; -- ────────────────────────────────────────────────────────────────────── -- STEP 3: Create indexes for performance -- ────────────────────────────────────────────────────────────────────── BEGIN; -- Full-text search index (Japanese language support) -- This enables fast text search in Brain UI CREATE INDEX IF NOT EXISTS idx_articles_full_text_search ON articles USING gin(to_tsvector('english', COALESCE(full_text, ''))); -- Note: PostgreSQL doesn't have built-in Japanese tokenizer by default -- For better Japanese search, consider installing pg_bigm extension: -- CREATE EXTENSION IF NOT EXISTS pg_bigm; -- CREATE INDEX IF NOT EXISTS idx_articles_full_text_bigm -- ON articles USING gin(full_text gin_bigm_ops); -- Index for finding articles without full_text (for migration/cleanup) CREATE INDEX IF NOT EXISTS idx_articles_missing_full_text ON articles (id) WHERE full_text IS NULL; COMMIT; -- ────────────────────────────────────────────────────────────────────── -- STEP 4: Verify schema changes -- ────────────────────────────────────────────────────────────────────── -- \d articles -- Expected output should include: -- | Column | Type | Nullable | Description | -- |-------------|-----------|----------|--------------------------------| -- | id | SERIAL | NOT NULL | Primary key | -- | user_id | INTEGER | NOT NULL | Foreign key to users table | -- | url | TEXT | NOT NULL | Original article URL | -- | title | TEXT | NOT NULL | Article title | -- | full_text | TEXT | NULL | ← NEW! Article body from Reader| -- | summary | TEXT | NULL | AI-generated 3-sentence summary| -- | topics | TEXT[] | NULL | AI-generated topic tags | -- | source | TEXT | NULL | Source (reader/feed/bookmarklet)| -- | reading_time| INTEGER | NULL | Estimated reading time (minutes)| -- | favicon | TEXT | NULL | Site favicon URL | -- | og_image | TEXT | NULL | OGP image URL | -- | images | TEXT[] | NULL | ← NEW! Article images (future) | -- | status | TEXT | NOT NULL | inbox/favorite/shared/archived | -- | created_at | TIMESTAMP | NOT NULL | Record creation time | -- | updated_at | TIMESTAMP | NULL | ← NEW! Record update time | -- ────────────────────────────────────────────────────────────────────── -- STEP 5: Test query (verify columns exist) -- ────────────────────────────────────────────────────────────────────── SELECT id, title, LENGTH(full_text) as text_length, LEFT(full_text, 50) as text_preview, summary, topics, created_at FROM articles ORDER BY created_at DESC LIMIT 5; -- Expected: All existing articles will have NULL for full_text -- New articles saved after API update will have full_text populated -- ────────────────────────────────────────────────────────────────────── -- STEP 6: Clean up old articles (optional) -- ────────────────────────────────────────────────────────────────────── -- If you want to delete old articles without full_text after migration: -- (Run this ONLY after confirming new articles are saving correctly) -- Count articles without full_text SELECT COUNT(*) as articles_without_fulltext FROM articles WHERE full_text IS NULL; -- Optionally delete old articles without full_text (BE CAREFUL!) -- DELETE FROM articles WHERE full_text IS NULL AND created_at < NOW() - INTERVAL '30 days'; -- ────────────────────────────────────────────────────────────────────── -- STEP 7: Add constraint for new articles (optional, recommended) -- ────────────────────────────────────────────────────────────────────── -- After confirming Reader is sending full_text correctly, -- you can add a constraint to ensure all new articles have full_text: -- ALTER TABLE articles -- ADD CONSTRAINT full_text_required -- CHECK ( -- (source = 'reader' AND full_text IS NOT NULL) OR -- (source != 'reader') -- ); -- This ensures Reader-sourced articles MUST have full_text -- ────────────────────────────────────────────────────────────────────── -- STEP 8: Grant permissions (if needed) -- ────────────────────────────────────────────────────────────────────── -- If using a separate API user, grant access to new columns: -- GRANT SELECT, INSERT, UPDATE ON articles TO brain_api_user; -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- Migration Complete! -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ -- Next steps: -- 1. Update Brain API server code (see BRAIN_FULLTEXT_IMPLEMENTATION_GUIDE.md) -- 2. Test saving an article from Reader -- 3. Verify full_text is populated in database -- 4. Deploy Brain API changes to production -- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━