9 KiB
Sojorn - Setup Guide
Quick guide to get Sojorn running locally and deployed.
Prerequisites
- Supabase CLI installed
- Deno installed (for Edge Functions)
- Git installed
- A Supabase account (free tier works)
Step 1: Environment Setup
1.1 Create Supabase Project
- Go to app.supabase.com
- Click "New Project"
- Choose a name (e.g., "sojorn-dev")
- Set a strong database password
- Select a region close to you
- Wait for project to initialize (~2 minutes)
1.2 Get Your Credentials
Once your project is ready:
-
Go to Settings → API
-
Copy these values:
- Project URL (e.g.,
https://abcdefgh.supabase.co) - anon/public key (starts with
eyJ...) - service_role key (starts with
eyJ...)
- Project URL (e.g.,
-
Go to Settings → General
- Copy your Project Reference ID (e.g.,
abcdefgh)
- Copy your Project Reference ID (e.g.,
-
Go to Settings → Database
- Copy your Database Password (or reset it if you forgot)
1.3 Configure .env File
Open .env in this project and fill in your values:
SUPABASE_URL=https://YOUR_PROJECT_REF.supabase.co
SUPABASE_ANON_KEY=eyJ...your_anon_key...
SUPABASE_SERVICE_ROLE_KEY=eyJ...your_service_role_key...
SUPABASE_PROJECT_REF=YOUR_PROJECT_REF
SUPABASE_DB_PASSWORD=your_database_password
CRON_SECRET=generate_with_openssl_rand
NODE_ENV=development
API_BASE_URL=https://YOUR_PROJECT_REF.supabase.co/functions/v1
1.4 Generate CRON_SECRET
In your terminal:
# macOS/Linux
openssl rand -base64 32
# Windows (PowerShell)
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_})
Copy the output and paste it as your CRON_SECRET in .env.
Step 2: Link to Supabase
# Login to Supabase
supabase login
# Link your local project to your Supabase project
supabase link --project-ref YOUR_PROJECT_REF
# Enter your database password when prompted
Step 3: Deploy Database
3.1 Push Migrations
# Apply all migrations to your Supabase project
supabase db push
This will create all tables, functions, and RLS policies.
3.2 Seed Categories
Connect to your database and run the seed script:
# Using psql
psql "postgresql://postgres:YOUR_DB_PASSWORD@db.YOUR_PROJECT_REF.supabase.co:5432/postgres" \
-f supabase/seed/seed_categories.sql
# Or using Supabase SQL Editor:
# 1. Go to https://app.supabase.com/project/YOUR_PROJECT/editor
# 2. Copy contents of supabase/seed/seed_categories.sql
# 3. Paste and run
3.3 Verify Database Setup
# Check that tables were created
supabase db remote commit
Or in the Supabase Dashboard:
- Go to Table Editor → You should see all 14 tables
Step 4: Deploy Edge Functions
4.1 Set Secrets
# Set the CRON_SECRET for the harmony calculation function
supabase secrets set CRON_SECRET="your_cron_secret_from_env"
4.2 Deploy All Functions
# Deploy each function
supabase functions deploy publish-post
supabase functions deploy publish-comment
supabase functions deploy block
supabase functions deploy report
supabase functions deploy feed-personal
supabase functions deploy feed-sojorn
supabase functions deploy trending
supabase functions deploy calculate-harmony
Or deploy all at once (if you have a deployment script).
4.3 Verify Functions
Go to Edge Functions in your Supabase dashboard:
- You should see 8 functions listed
- Check logs to ensure no deployment errors
Step 5: Test the API
5.1 Create a Test User
- Go to Authentication → Users in Supabase Dashboard
- Click "Add User"
- Enter email and password
- Copy the User ID (UUID)
5.2 Manually Create Profile
In SQL Editor, run:
-- Replace USER_ID with the UUID from step 5.1
INSERT INTO profiles (id, handle, display_name, bio)
VALUES ('USER_ID', 'testuser', 'Test User', 'Testing Sojorn');
5.3 Get a JWT Token
- Use the Supabase Auth API:
curl -X POST "https://YOUR_PROJECT_REF.supabase.co/auth/v1/token?grant_type=password" \
-H "apikey: YOUR_ANON_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "your_password"
}'
- Copy the
access_tokenfrom the response
5.4 Test Edge Functions
# Set your token
export TOKEN="your_access_token_here"
# Get a category ID (from seed data)
# Go to Table Editor → categories → Copy the 'general' category UUID
# Test publishing a post
curl -X POST "https://YOUR_PROJECT_REF.supabase.co/functions/v1/publish-post" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"category_id": "CATEGORY_UUID",
"body": "This is my first friendly post on Sojorn."
}'
# Test getting personal feed
curl "https://YOUR_PROJECT_REF.supabase.co/functions/v1/feed-personal" \
-H "Authorization: Bearer $TOKEN"
# Test Sojorn feed
curl "https://YOUR_PROJECT_REF.supabase.co/functions/v1/feed-sojorn?limit=10" \
-H "Authorization: Bearer $TOKEN"
Step 6: Schedule Harmony Calculation
Option 1: GitHub Actions (Recommended for small projects)
Create .github/workflows/harmony-cron.yml:
name: Calculate Harmony Daily
on:
schedule:
- cron: '0 2 * * *' # 2 AM UTC daily
workflow_dispatch:
jobs:
calculate:
runs-on: ubuntu-latest
steps:
- name: Trigger harmony calculation
run: |
curl -X POST \
https://${{ secrets.SUPABASE_PROJECT_REF }}.supabase.co/functions/v1/calculate-harmony \
-H "Authorization: Bearer ${{ secrets.CRON_SECRET }}"
Add secrets in GitHub:
SUPABASE_PROJECT_REFCRON_SECRET
Option 2: Cron-Job.org (External service)
- Go to cron-job.org and create account
- Create new cron job:
- URL:
https://YOUR_PROJECT_REF.supabase.co/functions/v1/calculate-harmony - Schedule: Daily at 2 AM
- Method: POST
- Header:
Authorization: Bearer YOUR_CRON_SECRET
- URL:
Step 7: Verify Everything Works
7.1 Check Tables
In Table Editor, verify:
profileshas your test usercategorieshas 12 categoriestrust_statehas your user (with harmony_score = 50)postshas any posts you created
7.2 Check RLS Policies
In SQL Editor, test block enforcement:
-- Create a second test user
INSERT INTO auth.users (id, email) VALUES (gen_random_uuid(), 'test2@example.com');
INSERT INTO profiles (id, handle, display_name)
VALUES ((SELECT id FROM auth.users WHERE email = 'test2@example.com'), 'testuser2', 'Test User 2');
-- Block user 2 from user 1
INSERT INTO blocks (blocker_id, blocked_id)
VALUES (
(SELECT id FROM profiles WHERE handle = 'testuser'),
(SELECT id FROM profiles WHERE handle = 'testuser2')
);
-- Verify user 1 cannot see user 2's profile
SET request.jwt.claims TO '{"sub": "USER_1_ID"}';
SELECT * FROM profiles WHERE handle = 'testuser2'; -- Should return 0 rows
-- Reset
RESET request.jwt.claims;
7.3 Test Tone Detection
Try publishing a hostile post:
curl -X POST "https://YOUR_PROJECT_REF.supabase.co/functions/v1/publish-post" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"category_id": "CATEGORY_UUID",
"body": "This is fucking bullshit."
}'
Expected response:
{
"error": "Content rejected",
"message": "This post contains language that does not fit here.",
"suggestion": "This space works without profanity. Try rephrasing."
}
Troubleshooting
"Relation does not exist" error
- Run
supabase db pushagain - Check that migrations completed successfully
"JWT expired" error
- Your auth token expired (tokens last 1 hour)
- Sign in again to get a new token
"Failed to fetch" error
- Check your
SUPABASE_URLis correct - Verify Edge Functions are deployed
- Check function logs:
supabase functions logs FUNCTION_NAME
RLS policies blocking everything
- Ensure you're using the correct user ID in JWT
- Check that user exists in
profilestable - Verify RLS policies with
\d+ TABLE_NAMEin psql
Next Steps
Now that your backend is running:
- Build missing Edge Functions (signup, follow, like, etc.)
- Start Flutter client (see Flutter setup guide when created)
- Write transparency pages (How Reach Works, Rules)
- Add admin tooling (report review, trending overrides)
Useful Commands
# View Edge Function logs
supabase functions logs publish-post --tail
# Reset database (WARNING: deletes all data)
supabase db reset
# Check Supabase status
supabase status
# View remote database changes
supabase db remote commit
# Generate TypeScript types from database
supabase gen types typescript --local > types/database.ts