407 lines
18 KiB
PL/PgSQL
407 lines
18 KiB
PL/PgSQL
-- Supabase Schema Initialization for Batsir Productivity Planner
|
|
-- Idempotent version - safe to run multiple times without conflicts
|
|
|
|
-- Enable extensions
|
|
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
|
|
|
|
----------------------------------------------------
|
|
-- 1. Tables Setup (Idempotent - IF NOT EXISTS)
|
|
----------------------------------------------------
|
|
|
|
-- Core tables
|
|
CREATE TABLE IF NOT EXISTS public.profiles (
|
|
id UUID REFERENCES auth.users(id) ON DELETE CASCADE PRIMARY KEY,
|
|
email TEXT,
|
|
display_name TEXT,
|
|
preferences JSONB DEFAULT '{}'::jsonb,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.habits (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
title TEXT NOT NULL,
|
|
frequency TEXT DEFAULT 'daily',
|
|
preferred_time TIME,
|
|
location TEXT,
|
|
two_minute_version TEXT,
|
|
anchor_habit_id UUID REFERENCES public.habits(id) ON DELETE SET NULL,
|
|
weekend_flexibility BOOLEAN DEFAULT FALSE,
|
|
is_active BOOLEAN DEFAULT TRUE,
|
|
current_streak INTEGER DEFAULT 0,
|
|
max_streak INTEGER DEFAULT 0,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.schedules (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
date DATE NOT NULL,
|
|
time_blocks JSONB DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
habit_id UUID REFERENCES public.habits(id) ON DELETE CASCADE NOT NULL,
|
|
status TEXT,
|
|
logged_at TIMESTAMPTZ DEFAULT NOW(),
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.sync_queue (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
table_name TEXT,
|
|
operation TEXT,
|
|
payload JSONB,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.shortcuts (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
title TEXT,
|
|
url TEXT,
|
|
icon TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.tasks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
title TEXT,
|
|
status TEXT DEFAULT 'todo',
|
|
estimated_sessions INTEGER DEFAULT 1,
|
|
completed_sessions INTEGER DEFAULT 0,
|
|
tag TEXT,
|
|
todos JSONB DEFAULT '[]'::jsonb,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.books (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
title TEXT NOT NULL,
|
|
author TEXT,
|
|
total_pages INTEGER DEFAULT 0,
|
|
current_page INTEGER DEFAULT 0,
|
|
last_page_read INTEGER DEFAULT 0,
|
|
file_uri TEXT,
|
|
cover_uri TEXT,
|
|
status TEXT DEFAULT 'want_to_read',
|
|
synthesis TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.reading_logs (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
book_id UUID REFERENCES public.books(id) ON DELETE CASCADE NOT NULL,
|
|
pages_read INTEGER DEFAULT 0,
|
|
duration_minutes DOUBLE PRECISION DEFAULT 0,
|
|
duration_seconds DOUBLE PRECISION DEFAULT 0,
|
|
logged_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.bookmarks (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
book_id UUID REFERENCES public.books(id) ON DELETE CASCADE NOT NULL,
|
|
page_number INTEGER NOT NULL,
|
|
note TEXT,
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.sync_history (
|
|
old_id TEXT NOT NULL,
|
|
new_id UUID NOT NULL,
|
|
table_name TEXT NOT NULL,
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
synced_at TIMESTAMPTZ DEFAULT NOW(),
|
|
PRIMARY KEY (old_id, table_name, user_id)
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS public.chat_messages (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
user_id UUID REFERENCES public.profiles(id) ON DELETE CASCADE NOT NULL,
|
|
text TEXT NOT NULL,
|
|
sender TEXT NOT NULL CHECK (sender IN ('user', 'ai')),
|
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
----------------------------------------------------
|
|
-- 2. Safe Column Migrations (Check before adding)
|
|
----------------------------------------------------
|
|
|
|
DO $$
|
|
BEGIN
|
|
-- Books table migrations
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'books' AND column_name = 'synthesis') THEN
|
|
ALTER TABLE public.books ADD COLUMN synthesis TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'books' AND column_name = 'last_page_read') THEN
|
|
ALTER TABLE public.books ADD COLUMN last_page_read INTEGER DEFAULT 0;
|
|
END IF;
|
|
|
|
-- Reading logs migrations
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'reading_logs' AND column_name = 'duration_seconds') THEN
|
|
ALTER TABLE public.reading_logs ADD COLUMN duration_seconds DOUBLE PRECISION DEFAULT 0;
|
|
END IF;
|
|
|
|
-- Alter column type safely (only if needed)
|
|
IF EXISTS (
|
|
SELECT 1 FROM information_schema.columns
|
|
WHERE table_name = 'reading_logs' AND column_name = 'duration_minutes' AND data_type = 'integer'
|
|
) THEN
|
|
ALTER TABLE public.reading_logs ALTER COLUMN duration_minutes TYPE DOUBLE PRECISION;
|
|
END IF;
|
|
END $$;
|
|
|
|
----------------------------------------------------
|
|
-- 3. RLS Enablement (Idempotent)
|
|
----------------------------------------------------
|
|
|
|
DO $$
|
|
DECLARE
|
|
tables text[] := ARRAY['profiles', 'habits', 'schedules', 'logs', 'sync_queue', 'shortcuts', 'tasks', 'books', 'reading_logs', 'bookmarks', 'sync_history', 'chat_messages'];
|
|
tbl text;
|
|
BEGIN
|
|
FOREACH tbl IN ARRAY tables
|
|
LOOP
|
|
EXECUTE format('ALTER TABLE IF EXISTS public.%I ENABLE ROW LEVEL SECURITY', tbl);
|
|
END LOOP;
|
|
END $$;
|
|
|
|
----------------------------------------------------
|
|
-- 4. Safe Policy Recreation (Drop only existing policies)
|
|
----------------------------------------------------
|
|
|
|
DO $$
|
|
DECLARE
|
|
pol RECORD;
|
|
BEGIN
|
|
FOR pol IN (
|
|
SELECT policyname, tablename
|
|
FROM pg_policies
|
|
WHERE schemaname = 'public'
|
|
)
|
|
LOOP
|
|
BEGIN
|
|
EXECUTE format('DROP POLICY IF EXISTS %I ON public.%I', pol.policyname, pol.tablename);
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE 'Could not drop policy % on table %: %', pol.policyname, pol.tablename, SQLERRM;
|
|
END;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- Profiles (Link is 'id' to auth.uid())
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Profiles: Users view own' AND tablename = 'profiles') THEN
|
|
CREATE POLICY "Profiles: Users view own" ON public.profiles FOR SELECT USING (auth.uid() = id);
|
|
CREATE POLICY "Profiles: Users update own" ON public.profiles FOR UPDATE USING (auth.uid() = id);
|
|
CREATE POLICY "Profiles: Users insert own" ON public.profiles FOR INSERT WITH CHECK (auth.uid() = id);
|
|
END IF;
|
|
END $$;
|
|
|
|
-- Repeat for each table (only create if not exists)
|
|
DO $$
|
|
BEGIN
|
|
-- Habits policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Habits: Users view own' AND tablename = 'habits') THEN
|
|
CREATE POLICY "Habits: Users view own" ON public.habits FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Habits: Users insert own" ON public.habits FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Habits: Users update own" ON public.habits FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Habits: Users delete own" ON public.habits FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Schedules policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Schedules: Users view own' AND tablename = 'schedules') THEN
|
|
CREATE POLICY "Schedules: Users view own" ON public.schedules FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Schedules: Users insert own" ON public.schedules FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Schedules: Users update own" ON public.schedules FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Schedules: Users delete own" ON public.schedules FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Logs policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Logs: Users view own' AND tablename = 'logs') THEN
|
|
CREATE POLICY "Logs: Users view own" ON public.logs FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Logs: Users insert own" ON public.logs FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Logs: Users update own" ON public.logs FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Logs: Users delete own" ON public.logs FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Sync Queue policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'SyncQueue: Users view own' AND tablename = 'sync_queue') THEN
|
|
CREATE POLICY "SyncQueue: Users view own" ON public.sync_queue FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "SyncQueue: Users insert own" ON public.sync_queue FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "SyncQueue: Users delete own" ON public.sync_queue FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Shortcuts policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Shortcuts: Users view own' AND tablename = 'shortcuts') THEN
|
|
CREATE POLICY "Shortcuts: Users view own" ON public.shortcuts FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Shortcuts: Users insert own" ON public.shortcuts FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Shortcuts: Users update own" ON public.shortcuts FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Shortcuts: Users delete own" ON public.shortcuts FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Tasks policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Tasks: Users view own' AND tablename = 'tasks') THEN
|
|
CREATE POLICY "Tasks: Users view own" ON public.tasks FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Tasks: Users insert own" ON public.tasks FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Tasks: Users update own" ON public.tasks FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Tasks: Users delete own" ON public.tasks FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Books policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Books: Users view own' AND tablename = 'books') THEN
|
|
CREATE POLICY "Books: Users view own" ON public.books FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Books: Users insert own" ON public.books FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Books: Users update own" ON public.books FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Books: Users delete own" ON public.books FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Reading Logs policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'ReadingLogs: Users view own' AND tablename = 'reading_logs') THEN
|
|
CREATE POLICY "ReadingLogs: Users view own" ON public.reading_logs FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "ReadingLogs: Users insert own" ON public.reading_logs FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Bookmarks policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Bookmarks: Users view own' AND tablename = 'bookmarks') THEN
|
|
CREATE POLICY "Bookmarks: Users view own" ON public.bookmarks FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "Bookmarks: Users insert own" ON public.bookmarks FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
CREATE POLICY "Bookmarks: Users update own" ON public.bookmarks FOR UPDATE USING (auth.uid() = user_id);
|
|
CREATE POLICY "Bookmarks: Users delete own" ON public.bookmarks FOR DELETE USING (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Sync History policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'SyncHistory: Users view own' AND tablename = 'sync_history') THEN
|
|
CREATE POLICY "SyncHistory: Users view own" ON public.sync_history FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "SyncHistory: Users insert own" ON public.sync_history FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
END IF;
|
|
|
|
-- Chat Messages policies
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'ChatMessages: Users view own' AND tablename = 'chat_messages') THEN
|
|
CREATE POLICY "ChatMessages: Users view own" ON public.chat_messages FOR SELECT USING (auth.uid() = user_id);
|
|
CREATE POLICY "ChatMessages: Users insert own" ON public.chat_messages FOR INSERT WITH CHECK (auth.uid() = user_id);
|
|
END IF;
|
|
END $$;
|
|
|
|
----------------------------------------------------
|
|
-- 5. Triggers & Functions (Safe create or replace)
|
|
----------------------------------------------------
|
|
|
|
-- Function for updated_at
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
-- Profile Handler
|
|
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
INSERT INTO public.profiles (id, email)
|
|
VALUES (new.id, new.email)
|
|
ON CONFLICT (id) DO UPDATE
|
|
SET email = EXCLUDED.email;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- Drop and recreate triggers safely
|
|
DO $$
|
|
DECLARE
|
|
trigger_config record;
|
|
BEGIN
|
|
-- Drop existing triggers
|
|
FOR trigger_config IN (
|
|
SELECT tgname AS trigger_name, relname AS table_name
|
|
FROM pg_trigger
|
|
JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid
|
|
WHERE tgname IN ('update_profiles_updated_at', 'update_habits_updated_at', 'update_schedules_updated_at',
|
|
'update_logs_updated_at', 'update_shortcuts_updated_at', 'update_tasks_updated_at',
|
|
'update_books_updated_at', 'on_auth_user_created')
|
|
) LOOP
|
|
BEGIN
|
|
EXECUTE format('DROP TRIGGER IF EXISTS %I ON public.%I', trigger_config.trigger_name, trigger_config.table_name);
|
|
EXCEPTION
|
|
WHEN OTHERS THEN
|
|
RAISE NOTICE 'Could not drop trigger %: %', trigger_config.trigger_name, SQLERRM;
|
|
END;
|
|
END LOOP;
|
|
END $$;
|
|
|
|
-- Recreate triggers
|
|
CREATE TRIGGER update_profiles_updated_at BEFORE UPDATE ON public.profiles FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_habits_updated_at BEFORE UPDATE ON public.habits FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_schedules_updated_at BEFORE UPDATE ON public.schedules FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_logs_updated_at BEFORE UPDATE ON public.logs FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_shortcuts_updated_at BEFORE UPDATE ON public.shortcuts FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_tasks_updated_at BEFORE UPDATE ON public.tasks FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
CREATE TRIGGER update_books_updated_at BEFORE UPDATE ON public.books FOR EACH ROW EXECUTE PROCEDURE update_updated_at_column();
|
|
|
|
-- Handle auth trigger separately to avoid duplicate
|
|
DROP TRIGGER IF EXISTS on_auth_user_created ON auth.users;
|
|
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users FOR EACH ROW EXECUTE PROCEDURE public.handle_new_user();
|
|
|
|
----------------------------------------------------
|
|
-- 6. Storage Setup (Idempotent)
|
|
----------------------------------------------------
|
|
|
|
-- Insert bucket if not exists
|
|
INSERT INTO storage.buckets (id, name, public)
|
|
VALUES ('books', 'books', false)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
|
|
-- Create storage policies safely
|
|
DO $$
|
|
BEGIN
|
|
IF NOT EXISTS (SELECT 1 FROM pg_policies WHERE policyname = 'Books Bucket: Users upload own' AND schemaname = 'storage') THEN
|
|
CREATE POLICY "Books Bucket: Users upload own" ON storage.objects
|
|
FOR INSERT WITH CHECK (bucket_id = 'books' AND auth.uid() = owner);
|
|
|
|
CREATE POLICY "Books Bucket: Users view own" ON storage.objects
|
|
FOR SELECT USING (bucket_id = 'books' AND auth.uid() = owner);
|
|
|
|
CREATE POLICY "Books Bucket: Users delete own" ON storage.objects
|
|
FOR DELETE USING (bucket_id = 'books' AND auth.uid() = owner);
|
|
END IF;
|
|
END $$;
|
|
|
|
----------------------------------------------------
|
|
-- 7. Additional Conflict Prevention
|
|
----------------------------------------------------
|
|
|
|
-- Create indexes for better performance and conflict prevention
|
|
CREATE INDEX IF NOT EXISTS idx_habits_user_id ON public.habits(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_schedules_user_date ON public.schedules(user_id, date);
|
|
CREATE INDEX IF NOT EXISTS idx_logs_user_habit ON public.logs(user_id, habit_id);
|
|
CREATE INDEX IF NOT EXISTS idx_tasks_user_id ON public.tasks(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_books_user_id ON public.books(user_id);
|
|
CREATE INDEX IF NOT EXISTS idx_reading_logs_book_id ON public.reading_logs(book_id);
|
|
CREATE INDEX IF NOT EXISTS idx_bookmarks_book_id ON public.bookmarks(book_id);
|
|
|
|
-- Add comments for documentation
|
|
COMMENT ON TABLE public.profiles IS 'User profiles with preferences';
|
|
COMMENT ON TABLE public.habits IS 'User habits with streak tracking';
|
|
COMMENT ON TABLE public.schedules IS 'Daily schedules with time blocks';
|
|
COMMENT ON TABLE public.logs IS 'Habit completion logs';
|
|
COMMENT ON TABLE public.tasks IS 'Task management';
|
|
COMMENT ON TABLE public.books IS 'Book library with reading progress'; |