iStory uses Supabase (PostgreSQL) for data persistence with Row Level Security (RLS) policies for defense-in-depth protection. All database changes are managed through migration files.
Database migrations are located in ~/workspace/source/supabase/migrations/ and should be run in order.
Stores user account information for both OAuth and wallet authentication.
CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), wallet_address TEXT UNIQUE, email TEXT UNIQUE, username TEXT UNIQUE, name TEXT, bio TEXT, avatar_url TEXT, -- OAuth fields google_id TEXT UNIQUE, google_email TEXT, google_avatar TEXT, auth_provider TEXT CHECK (auth_provider IN ('wallet', 'google', 'linked')), -- Profile metadata location TEXT, website TEXT, writing_streak INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW());CREATE INDEX idx_users_wallet ON users(wallet_address);CREATE INDEX idx_users_google ON users(google_id);CREATE INDEX idx_users_email ON users(email);
CREATE TABLE stories ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), author_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, author_wallet TEXT NOT NULL, title TEXT NOT NULL, content TEXT NOT NULL, audio_url TEXT, -- Metadata mood TEXT, tags TEXT[] DEFAULT '{}', is_public BOOLEAN DEFAULT false, -- Paywall has_paywall BOOLEAN DEFAULT false, paywall_price NUMERIC, -- Engagement likes_count INT DEFAULT 0, views_count INT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW());CREATE INDEX idx_stories_author ON stories(author_id);CREATE INDEX idx_stories_created ON stories(created_at DESC);CREATE INDEX idx_stories_public ON stories(is_public) WHERE is_public = true;
RLS Enabled: Users can only read/modify their own stories. Public stories are readable by all.
Stores AI-generated weekly summaries of journaling patterns (Phase 3).
CREATE TABLE weekly_reflections ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, user_wallet TEXT NOT NULL, reflection_text TEXT NOT NULL, stories_analyzed UUID[] DEFAULT '{}', themes_identified TEXT[] DEFAULT '{}', dominant_tone TEXT, dominant_domain TEXT, week_start TIMESTAMPTZ NOT NULL, week_end TIMESTAMPTZ NOT NULL, canonical_weight FLOAT DEFAULT 0, created_at TIMESTAMPTZ DEFAULT NOW());CREATE INDEX idx_reflections_user ON weekly_reflections(user_id);CREATE UNIQUE INDEX idx_reflections_user_week ON weekly_reflections(user_id, week_start);
Unique Constraint: One reflection per user per week (Sunday 00:00 - Saturday 23:59)
Tracks user habits and daily check-ins for the Daily Tracker feature.
CREATE TABLE habits ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, title TEXT NOT NULL, category TEXT DEFAULT 'General', target_frequency TEXT DEFAULT 'daily', created_at TIMESTAMPTZ DEFAULT NOW());CREATE TABLE daily_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES users(id) ON DELETE CASCADE NOT NULL, date DATE NOT NULL, completed_habit_ids UUID[] DEFAULT '{}', mood TEXT DEFAULT 'good', sleep_hours NUMERIC, notes TEXT DEFAULT '', created_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE(user_id, date));
Stores privacy-preserving CRE-attested story metrics (off-chain, linked to on-chain proofs).
CREATE TABLE verified_metrics ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), story_id UUID REFERENCES stories(id) ON DELETE CASCADE UNIQUE NOT NULL, author_wallet TEXT NOT NULL, -- Full metrics (author-only, off-chain) significance_score INT, emotional_depth INT, quality_score INT, word_count INT, themes TEXT[] DEFAULT '{}', -- Privacy fields (public) quality_tier INT CHECK (quality_tier BETWEEN 1 AND 5), meets_quality_threshold BOOLEAN, metrics_hash TEXT NOT NULL, -- CRE attestation tx_hash TEXT NOT NULL, attestation_id TEXT NOT NULL, verified_at TIMESTAMPTZ DEFAULT NOW());CREATE INDEX idx_verified_metrics_story ON verified_metrics(story_id);CREATE INDEX idx_verified_metrics_author ON verified_metrics(author_wallet);
Author-Based Filtering: API routes must verify authorship before returning full metrics. Public users only see quality_tier and meets_quality_threshold.
-- Users can read their own storiesCREATE POLICY "Users can read their own stories" ON stories FOR SELECT USING (auth.uid()::text = author_id::text);-- Users can read public storiesCREATE POLICY "Users can read public stories" ON stories FOR SELECT USING (is_public = true);-- Users can insert their own storiesCREATE POLICY "Users can insert their own stories" ON stories FOR INSERT WITH CHECK (auth.uid()::text = author_id::text);-- Users can update their own storiesCREATE POLICY "Users can update their own stories" ON stories FOR UPDATE USING (auth.uid()::text = author_id::text) WITH CHECK (auth.uid()::text = author_id::text);-- Users can delete their own storiesCREATE POLICY "Users can delete their own stories" ON stories FOR DELETE USING (auth.uid()::text = author_id::text);
Story Metadata Policies
-- Users can read metadata for their own storiesCREATE POLICY "Users can read their own story metadata" ON story_metadata FOR SELECT USING ( EXISTS ( SELECT 1 FROM stories WHERE stories.id = story_metadata.story_id AND stories.author_id::text = auth.uid()::text ) );-- Users can read metadata for public storiesCREATE POLICY "Users can read public story metadata" ON story_metadata FOR SELECT USING ( EXISTS ( SELECT 1 FROM stories WHERE stories.id = story_metadata.story_id AND stories.is_public = true ) );-- Users can update metadata for their own storiesCREATE POLICY "Users can update their own story metadata" ON story_metadata FOR UPDATE USING ( EXISTS ( SELECT 1 FROM stories WHERE stories.id = story_metadata.story_id AND stories.author_id::text = auth.uid()::text ) );
Weekly Reflections Policies
-- Users can only read their own reflectionsCREATE POLICY "Users can read their own reflections" ON weekly_reflections FOR SELECT USING (auth.uid()::text = user_id::text);-- Users can insert their own reflectionsCREATE POLICY "Users can insert their own reflections" ON weekly_reflections FOR INSERT WITH CHECK (auth.uid()::text = user_id::text);-- Users can delete their own reflectionsCREATE POLICY "Users can delete their own reflections" ON weekly_reflections FOR DELETE USING (auth.uid()::text = user_id::text);
RLS is defense-in-depth: API routes use admin client (bypasses RLS) but always include explicit authorization checks. RLS protects against direct database access.
# Run all pending migrationssupabase db push# Create a new migrationsupabase migration new create_new_table# Reset database (caution: destroys data)supabase db reset
-- UsersCREATE INDEX idx_users_wallet ON users(wallet_address);CREATE INDEX idx_users_google ON users(google_id);CREATE INDEX idx_users_email ON users(email);-- StoriesCREATE INDEX idx_stories_author ON stories(author_id);CREATE INDEX idx_stories_created ON stories(created_at DESC);CREATE INDEX idx_stories_public ON stories(is_public) WHERE is_public = true;-- Story MetadataCREATE INDEX idx_metadata_story ON story_metadata(story_id);CREATE INDEX idx_metadata_canonical ON story_metadata(is_canonical) WHERE is_canonical = true;-- Weekly ReflectionsCREATE INDEX idx_reflections_user ON weekly_reflections(user_id);CREATE INDEX idx_reflections_created ON weekly_reflections(created_at DESC);-- Verified MetricsCREATE INDEX idx_verified_metrics_story ON verified_metrics(story_id);CREATE INDEX idx_verified_metrics_author ON verified_metrics(author_wallet);