-- Schema for app tables (Supabase API uses db.schema: 'omotomo' in config) CREATE SCHEMA IF NOT EXISTS omotomo; -- Decks table (metadata + config for Decky quiz/flashcard app) CREATE TABLE IF NOT EXISTS omotomo.decks ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), owner_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, title text NOT NULL, description text NOT NULL DEFAULT '', config jsonb NOT NULL DEFAULT '{ "requiredConsecutiveCorrect": 3, "defaultAttemptSize": 10, "priorityIncreaseOnIncorrect": 5, "priorityDecreaseOnCorrect": 2, "immediateFeedbackEnabled": true, "includeKnownInAttempts": false, "shuffleAnswerOrder": true, "excludeFlaggedQuestions": false, "timeLimitSeconds": null }'::jsonb, published boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now() ); -- Questions table (deck content) CREATE TABLE IF NOT EXISTS omotomo.questions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), deck_id uuid NOT NULL REFERENCES omotomo.decks(id) ON DELETE CASCADE, sort_order int NOT NULL DEFAULT 0, prompt text NOT NULL, explanation text, answers jsonb NOT NULL DEFAULT '[]'::jsonb, correct_answer_indices jsonb NOT NULL DEFAULT '[]'::jsonb ); CREATE INDEX IF NOT EXISTS idx_questions_deck_id ON omotomo.questions(deck_id); CREATE INDEX IF NOT EXISTS idx_decks_owner_id ON omotomo.decks(owner_id); CREATE INDEX IF NOT EXISTS idx_decks_published ON omotomo.decks(published) WHERE published = true; -- updated_at trigger for decks CREATE OR REPLACE FUNCTION omotomo.set_decks_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END; $$ LANGUAGE plpgsql; DROP TRIGGER IF EXISTS decks_updated_at ON omotomo.decks; CREATE TRIGGER decks_updated_at BEFORE UPDATE ON omotomo.decks FOR EACH ROW EXECUTE PROCEDURE omotomo.set_decks_updated_at(); -- RLS ALTER TABLE omotomo.decks ENABLE ROW LEVEL SECURITY; ALTER TABLE omotomo.questions ENABLE ROW LEVEL SECURITY; -- decks: SELECT if owner or published; mutate only if owner DROP POLICY IF EXISTS decks_select ON omotomo.decks; DROP POLICY IF EXISTS decks_insert ON omotomo.decks; DROP POLICY IF EXISTS decks_update ON omotomo.decks; DROP POLICY IF EXISTS decks_delete ON omotomo.decks; CREATE POLICY decks_select ON omotomo.decks FOR SELECT USING (owner_id = auth.uid() OR published = true); CREATE POLICY decks_insert ON omotomo.decks FOR INSERT WITH CHECK (owner_id = auth.uid()); CREATE POLICY decks_update ON omotomo.decks FOR UPDATE USING (owner_id = auth.uid()) WITH CHECK (owner_id = auth.uid()); CREATE POLICY decks_delete ON omotomo.decks FOR DELETE USING (owner_id = auth.uid()); -- questions: SELECT if deck is visible; mutate only if deck is owned DROP POLICY IF EXISTS questions_select ON omotomo.questions; DROP POLICY IF EXISTS questions_insert ON omotomo.questions; DROP POLICY IF EXISTS questions_update ON omotomo.questions; DROP POLICY IF EXISTS questions_delete ON omotomo.questions; CREATE POLICY questions_select ON omotomo.questions FOR SELECT USING ( EXISTS ( SELECT 1 FROM omotomo.decks d WHERE d.id = questions.deck_id AND (d.owner_id = auth.uid() OR d.published = true) ) ); CREATE POLICY questions_insert ON omotomo.questions FOR INSERT WITH CHECK ( EXISTS ( SELECT 1 FROM omotomo.decks d WHERE d.id = questions.deck_id AND d.owner_id = auth.uid() ) ); CREATE POLICY questions_update ON omotomo.questions FOR UPDATE USING ( EXISTS ( SELECT 1 FROM omotomo.decks d WHERE d.id = questions.deck_id AND d.owner_id = auth.uid() ) ) WITH CHECK ( EXISTS ( SELECT 1 FROM omotomo.decks d WHERE d.id = questions.deck_id AND d.owner_id = auth.uid() ) ); CREATE POLICY questions_delete ON omotomo.questions FOR DELETE USING ( EXISTS ( SELECT 1 FROM omotomo.decks d WHERE d.id = questions.deck_id AND d.owner_id = auth.uid() ) );