8.1 KiB
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 falseto profiles - Creates index for performance
- Adds documentation comment
Run this:
-- Paste contents of add_is_official_column.sql
Verify:
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:
-- Paste contents of seed_official_accounts.sql
Verify:
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:
-- Paste contents of seed_content.sql
Verify:
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:
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):
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:
// lib/models/profile.dart should include:
final bool isOfficial;
2. Run the App
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:
-- 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:
- Delete and recreate:
DELETE FROM profiles WHERE is_official = true;
-- Then re-run seed_official_accounts.sql
- 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:
- Check Profile model includes
isOfficialfield - Check API query includes
is_officialin SELECT:
.select('*, author:profiles(*)')
// Make sure profiles(*) includes is_official
Post-Seeding Checks
Feed Distribution
Check how much of your feed is official content:
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
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:
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)
-- 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
-- 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.