sojorn/sojorn_docs/deployment/SEEDING_SETUP.md

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 false to 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:

  1. Delete and recreate:
DELETE FROM profiles WHERE is_official = true;
-- Then re-run seed_official_accounts.sql
  1. 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:
.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.