# Sojorn Seeding Setup Guide ## Prerequisites - Supabase project deployed - Categories seeded (run `seed_categories.sql`) - At least one real user account created (for testing) ## Setup Order Run these scripts in order in your Supabase SQL Editor: https://app.supabase.com/project/zwkihedetedlatyvplyz/sql/new ### Step 1: Add is_official Column **File:** `supabase/migrations/add_is_official_column.sql` **Purpose:** Adds `is_official` boolean column to profiles table **What it does:** - Adds `is_official BOOLEAN DEFAULT false` to profiles - Creates index for performance - Adds documentation comment **Run this:** ```sql -- Paste contents of add_is_official_column.sql ``` **Verify:** ```sql SELECT column_name, data_type, column_default FROM information_schema.columns WHERE table_name = 'profiles' AND column_name = 'is_official'; -- Should return: -- column_name | data_type | column_default -- is_official | boolean | false ``` --- ### Step 2: Create Official Accounts **File:** `supabase/seed/seed_official_accounts.sql` **Purpose:** Creates 3 official Sojorn accounts **What it does:** - Creates @sojorn (platform announcements) - Creates @sojorn_read (reading content) - Creates @sojorn_write (writing prompts) - Sets disabled passwords (cannot log in) - Marks `is_official = true` - Creates trust_state records - Adds RLS policies **Run this:** ```sql -- Paste contents of seed_official_accounts.sql ``` **Verify:** ```sql SELECT handle, display_name, is_official, bio FROM profiles WHERE is_official = true; -- Should return 3 rows: -- sojorn | Sojorn | true | Official Sojorn account • Platform updates... -- sojorn_read | Sojorn Reading | true | Excerpts, quotes, and reading prompts... -- sojorn_write | Sojorn Writing | true | Writing prompts and gentle reflections ``` **Expected output:** ``` NOTICE: Official accounts created successfully NOTICE: @sojorn: [UUID] NOTICE: @sojorn_read: [UUID] NOTICE: @sojorn_write: [UUID] ``` --- ### Step 3: Seed Content **File:** `supabase/seed/seed_content.sql` **Purpose:** Creates ~55 posts from official accounts **What it does:** - Inserts platform transparency posts - Inserts public domain poetry - Inserts reading reflections - Inserts writing prompts - Inserts observational content - Backdates posts over 14 days - Sets all engagement metrics to 0 **Run this:** ```sql -- Paste contents of seed_content.sql ``` **Verify:** ```sql SELECT p.handle, COUNT(posts.*) as post_count FROM posts JOIN profiles p ON p.id = posts.author_id WHERE p.is_official = true GROUP BY p.handle ORDER BY p.handle; -- Should return approximately: -- sojorn | 5 -- sojorn_read | 20-25 -- sojorn_write | 25-30 ``` **Check timestamp distribution:** ```sql SELECT DATE(created_at) as post_date, COUNT(*) as posts FROM posts JOIN profiles p ON p.id = posts.author_id WHERE p.is_official = true GROUP BY DATE(created_at) ORDER BY post_date; -- Should show posts spread over ~14 days ``` **Check engagement (should all be 0):** ```sql SELECT like_count, save_count, comment_count, view_count, COUNT(*) as posts_with_these_values FROM post_metrics pm JOIN posts ON posts.id = pm.post_id JOIN profiles p ON p.id = posts.author_id WHERE p.is_official = true GROUP BY like_count, save_count, comment_count, view_count; -- Should return: -- 0 | 0 | 0 | 0 | [total_count] ``` **Expected output:** ``` NOTICE: Seed content created successfully NOTICE: Posts span 14 days, backdated from NOW NOTICE: All engagement metrics set to 0 (no fake activity) ``` --- ## Testing in Flutter App ### 1. Update Flutter Dependencies Make sure you've pulled the latest code with the updated Profile model: ```dart // lib/models/profile.dart should include: final bool isOfficial; ``` ### 2. Run the App ```bash cd sojorn_app flutter run -d chrome ``` ### 3. Verify Official Badge - Navigate to the Sojorn feed - Look for posts from official accounts - Should see **[SOJORN]** badge next to author name - Badge should be soft blue (AppTheme.info) - Badge should be small (8px font) ### 4. Verify No Fake Engagement - Official posts should show 0 likes, 0 saves, 0 comments - No "trending" or "recommended" language - Just the content and the [SOJORN] badge --- ## Troubleshooting ### Error: "column is_official does not exist" **Cause:** Step 1 (migration) was skipped **Fix:** ```sql -- Run add_is_official_column.sql first ALTER TABLE profiles ADD COLUMN IF NOT EXISTS is_official BOOLEAN DEFAULT false; ``` ### Error: "No users found" in seed_content.sql **Cause:** Official accounts not created yet **Fix:** Run Step 2 (seed_official_accounts.sql) first ### Error: "duplicate key value violates unique constraint" **Cause:** Official accounts already exist **Fix:** Either: 1. Delete and recreate: ```sql DELETE FROM profiles WHERE is_official = true; -- Then re-run seed_official_accounts.sql ``` 2. Or skip seed_official_accounts.sql if already done ### Official badge not showing in Flutter **Cause:** Profile model not updated or API not returning is_official **Fix:** 1. Check Profile model includes `isOfficial` field 2. Check API query includes `is_official` in SELECT: ```typescript .select('*, author:profiles(*)') // Make sure profiles(*) includes is_official ``` --- ## Post-Seeding Checks ### Feed Distribution Check how much of your feed is official content: ```sql WITH feed_stats AS ( SELECT p.is_official, COUNT(*) as count FROM posts JOIN profiles p ON p.id = posts.author_id WHERE posts.status = 'active' GROUP BY p.is_official ) SELECT CASE WHEN is_official THEN 'Official' ELSE 'User' END as account_type, count, ROUND(100.0 * count / SUM(count) OVER (), 1) as percentage FROM feed_stats; -- Expected (day 1): -- Official | 55 | 100.0% -- User | 0 | 0.0% -- Expected (after users join): -- Official | 55 | ~50-80% -- User | XX | ~20-50% ``` ### Content by Category ```sql SELECT c.name as category, COUNT(*) as posts FROM posts JOIN profiles p ON p.id = posts.author_id JOIN categories c ON c.id = posts.category_id WHERE p.is_official = true GROUP BY c.name ORDER BY posts DESC; -- Should show balanced distribution across categories ``` --- ## Monthly Maintenance ### Check Official Content Ratio Run monthly to ensure official content isn't dominating: ```sql SELECT DATE_TRUNC('week', created_at) as week, SUM(CASE WHEN p.is_official THEN 1 ELSE 0 END) as official_posts, SUM(CASE WHEN NOT p.is_official THEN 1 ELSE 0 END) as user_posts, ROUND( 100.0 * SUM(CASE WHEN p.is_official THEN 1 ELSE 0 END) / COUNT(*), 1 ) as pct_official FROM posts JOIN profiles p ON p.id = posts.author_id WHERE posts.status = 'active' AND created_at >= NOW() - INTERVAL '4 weeks' GROUP BY week ORDER BY week DESC; ``` **Target ratios:** - Week 1-2: 80-100% official (okay, platform is new) - Week 3-4: 50-80% official (user content growing) - Month 2+: 10-30% official (user content dominant) - Month 6+: 0-10% official (archive old posts) ### Archive Old Official Posts (After 6 Months) ```sql -- Optional: Move old official posts to archived status UPDATE posts SET status = 'archived' WHERE author_id IN ( SELECT id FROM profiles WHERE is_official = true ) AND created_at < NOW() - INTERVAL '6 months' AND status = 'active'; ``` --- ## Rollback (If Needed) ### Remove All Seed Content ```sql -- Delete seed posts DELETE FROM posts WHERE author_id IN ( SELECT id FROM profiles WHERE is_official = true ); -- Delete official accounts DELETE FROM profiles WHERE is_official = true; -- Remove column (optional) ALTER TABLE profiles DROP COLUMN IF EXISTS is_official; ``` --- ## Summary **What you should have after seeding:** ✅ 3 official accounts (@sojorn, @sojorn_read, @sojorn_write) ✅ ~55 posts backdated over 14 days ✅ 0 fake engagement on all posts ✅ Clear [SOJORN] badge in UI ✅ Balanced content across categories ✅ New users see content immediately ✅ Trust preserved through transparency **What you should NOT have:** ❌ Fake user personas ❌ Inflated metrics ❌ Hidden origin ❌ Synthetic conversations ❌ Deceptive language --- **Philosophy:** Honest hospitality, not deception. **Next:** Monitor feed ratio monthly and plan archival after 6 months.