CREATE OR REPLACE FUNCTION public.check_dnd_match_pair_exercise_integrity() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE left_exercise_id BIGINT; right_exercise_id BIGINT; BEGIN SELECT dnd_match_exercise_id INTO left_exercise_id FROM dnd_match_pairs WHERE id = NEW.left_pair_id; SELECT dnd_match_exercise_id INTO right_exercise_id FROM dnd_match_pairs WHERE id = NEW.selected_right_pair_id; IF left_exercise_id IS DISTINCT FROM NEW.dnd_match_exercise_id OR right_exercise_id IS DISTINCT FROM NEW.dnd_match_exercise_id THEN RAISE EXCEPTION 'dnd_match_user_answers: left_pair_id and selected_right_pair_id must belong to dnd_match_exercise_id %', NEW.dnd_match_exercise_id; END IF; RETURN NEW; END; $function$ ; CREATE OR REPLACE FUNCTION public.insert_into_exercises() RETURNS trigger LANGUAGE plpgsql AS $function$ DECLARE new_exercise_id BIGINT; BEGIN -- Insert a new row into the exercises table INSERT INTO exercises (created_at, updated_at, category) VALUES (NOW(), NOW(), 'click') RETURNING id INTO new_exercise_id; -- Link the new exercise to the click_exercises entry INSERT INTO click_to_exercises (click_id, exercise_id, created_at, updated_at) VALUES (NEW.id, new_exercise_id, NOW(), NOW()); RETURN NEW; END; $function$ ; CREATE OR REPLACE FUNCTION public.updated_at() RETURNS trigger LANGUAGE plpgsql AS $function$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $function$ ; CREATE TABLE public.anchors ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), anchor_id text UNIQUE NOT NULL ); CREATE TABLE public.click_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), user_id bigint NOT NULL, click_exercise_id bigint NOT NULL, answer text[] NOT NULL, target_words text[] NOT NULL, is_correct boolean NOT NULL GENERATED ALWAYS AS ((answer = target_words)) STORED ); ALTER TABLE public.click_answers ADD CONSTRAINT click_answers_unique UNIQUE (user_id, click_exercise_id); CREATE TABLE public.click_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), title text NOT NULL, category text NOT NULL, target_words text[] NOT NULL, all_words text[] NOT NULL, source_html text ); ALTER TABLE public.click_exercises ADD CONSTRAINT click_exercises_all_words_target_words_key UNIQUE (all_words, target_words); ALTER TABLE public.click_exercises ADD CONSTRAINT click_exercises_id_target_words_key UNIQUE (id, target_words); CREATE TRIGGER after_click_exercises_insert AFTER INSERT ON public.click_exercises FOR EACH ROW EXECUTE FUNCTION insert_into_exercises(); ALTER TABLE public.click_answers ADD CONSTRAINT click_answers_click_exercise_id_fkey FOREIGN KEY (click_exercise_id) REFERENCES click_exercises(id) ON DELETE CASCADE; ALTER TABLE public.click_answers ADD CONSTRAINT click_answers_click_exercise_id_target_words_fkey FOREIGN KEY (click_exercise_id, target_words) REFERENCES click_exercises(id, target_words) ON UPDATE CASCADE ON DELETE CASCADE; CREATE TABLE public.users ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, email text UNIQUE NOT NULL, password_hash text NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), salt text NOT NULL, username text NOT NULL DEFAULT ''::text, is_admin boolean DEFAULT false, is_superadmin boolean NOT NULL DEFAULT false ); ALTER TABLE public.click_answers ADD CONSTRAINT click_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.click_to_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), click_id bigint, exercise_id bigint ); ALTER TABLE public.click_to_exercises ADD CONSTRAINT click_to_exercises_click_id_fkey FOREIGN KEY (click_id) REFERENCES click_exercises(id); CREATE TABLE public.exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), category text, created_by bigint ); ALTER TABLE public.click_to_exercises ADD CONSTRAINT click_to_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id); CREATE TABLE public.dnd_categories ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), category text NOT NULL, color text NOT NULL ); ALTER TABLE public.dnd_categories ADD CONSTRAINT unique_category_color UNIQUE (category, color); CREATE TABLE public.dnd_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), created_by bigint NOT NULL, exercise_id bigint NOT NULL, title text NOT NULL ); ALTER TABLE public.dnd_exercises ADD CONSTRAINT dnd_exercises_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE; ALTER TABLE public.dnd_exercises ADD CONSTRAINT dnd_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; CREATE TABLE public.dnd_match_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint UNIQUE NOT NULL, created_by bigint NOT NULL, title text NOT NULL, description text NOT NULL ); ALTER TABLE public.dnd_match_exercises ADD CONSTRAINT dnd_match_exercises_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE; ALTER TABLE public.dnd_match_exercises ADD CONSTRAINT dnd_match_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; CREATE TABLE public.dnd_match_pairs ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), dnd_match_exercise_id bigint NOT NULL, left_item text NOT NULL, right_item text NOT NULL, pair_order integer NOT NULL ); CREATE INDEX dnd_match_pairs_exercise_order_idx ON public.dnd_match_pairs USING btree (dnd_match_exercise_id, pair_order); ALTER TABLE public.dnd_match_pairs ADD CONSTRAINT dnd_match_pairs_dnd_match_exercise_id_pair_order_key UNIQUE (dnd_match_exercise_id, pair_order); ALTER TABLE public.dnd_match_pairs ADD CONSTRAINT dnd_match_pairs_dnd_match_exercise_id_right_item_key UNIQUE (dnd_match_exercise_id, right_item); ALTER TABLE public.dnd_match_pairs ADD CONSTRAINT dnd_match_pairs_dnd_match_exercise_id_fkey FOREIGN KEY (dnd_match_exercise_id) REFERENCES dnd_match_exercises(id) ON DELETE CASCADE; CREATE TABLE public.dnd_match_user_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), user_id bigint NOT NULL, dnd_match_exercise_id bigint NOT NULL, left_pair_id bigint NOT NULL, selected_right_pair_id bigint NOT NULL, is_correct boolean NOT NULL DEFAULT false ); CREATE INDEX dnd_match_user_answers_exercise_idx ON public.dnd_match_user_answers USING btree (dnd_match_exercise_id); CREATE INDEX dnd_match_user_answers_user_exercise_idx ON public.dnd_match_user_answers USING btree (user_id, dnd_match_exercise_id); ALTER TABLE public.dnd_match_user_answers ADD CONSTRAINT dnd_match_user_answers_user_id_dnd_match_exercise_id_left_p_key UNIQUE (user_id, dnd_match_exercise_id, left_pair_id); CREATE TRIGGER check_pair_exercise_integrity BEFORE INSERT OR UPDATE ON public.dnd_match_user_answers FOR EACH ROW EXECUTE FUNCTION check_dnd_match_pair_exercise_integrity(); ALTER TABLE public.dnd_match_user_answers ADD CONSTRAINT dnd_match_user_answers_dnd_match_exercise_id_fkey FOREIGN KEY (dnd_match_exercise_id) REFERENCES dnd_match_exercises(id) ON DELETE CASCADE; ALTER TABLE public.dnd_match_user_answers ADD CONSTRAINT dnd_match_user_answers_left_pair_id_fkey FOREIGN KEY (left_pair_id) REFERENCES dnd_match_pairs(id) ON DELETE CASCADE; ALTER TABLE public.dnd_match_user_answers ADD CONSTRAINT dnd_match_user_answers_selected_right_pair_id_fkey FOREIGN KEY (selected_right_pair_id) REFERENCES dnd_match_pairs(id) ON DELETE CASCADE; ALTER TABLE public.dnd_match_user_answers ADD CONSTRAINT dnd_match_user_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.dnd_user_answer ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), user_id bigint NOT NULL, word_id bigint NOT NULL, category_id bigint, is_correct boolean NOT NULL ); ALTER TABLE public.dnd_user_answer ADD CONSTRAINT dnd_user_answer_category_id_fkey FOREIGN KEY (category_id) REFERENCES dnd_categories(id) ON DELETE CASCADE; ALTER TABLE public.dnd_user_answer ADD CONSTRAINT dnd_user_answer_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.draggable_words ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), word text UNIQUE NOT NULL ); ALTER TABLE public.dnd_user_answer ADD CONSTRAINT dnd_user_answer_word_id_fkey FOREIGN KEY (word_id) REFERENCES draggable_words(id) ON DELETE CASCADE; CREATE TABLE public.exercise_anchors ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint NOT NULL, anchor_id bigint NOT NULL, position integer NOT NULL DEFAULT 0, difficulty text NOT NULL DEFAULT 'medium'::text, title text ); CREATE INDEX exercise_anchors_anchor_id_idx ON public.exercise_anchors USING btree (anchor_id); CREATE INDEX exercise_anchors_exercise_id_idx ON public.exercise_anchors USING btree (exercise_id); ALTER TABLE public.exercise_anchors ADD CONSTRAINT exercise_anchors_difficulty_check CHECK ((difficulty = ANY (ARRAY['easy'::text, 'medium'::text, 'hard'::text]))); ALTER TABLE public.exercise_anchors ADD CONSTRAINT exercise_anchors_exercise_id_anchor_id_key UNIQUE (exercise_id, anchor_id); ALTER TABLE public.exercise_anchors ADD CONSTRAINT exercise_anchors_anchor_id_fkey FOREIGN KEY (anchor_id) REFERENCES anchors(id) ON DELETE CASCADE; ALTER TABLE public.exercise_anchors ADD CONSTRAINT exercise_anchors_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; ALTER TABLE public.exercises ADD CONSTRAINT exercises_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id); CREATE TABLE public.exercises_to_grammar_items ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, grammar_item_id bigint, exercise_id bigint ); ALTER TABLE public.exercises_to_grammar_items ADD CONSTRAINT exercises_to_grammar_items_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id); CREATE TABLE public.grammar_items ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, grammar_item text UNIQUE NOT NULL ); ALTER TABLE public.exercises_to_grammar_items ADD CONSTRAINT exercises_to_grammar_items_grammar_item_id_fkey FOREIGN KEY (grammar_item_id) REFERENCES grammar_items(id); CREATE SEQUENCE public.feedbacks_id_seq; CREATE TABLE public.feedbacks ( id integer PRIMARY KEY NOT NULL DEFAULT nextval('feedbacks_id_seq'::regclass), email character varying(255) NOT NULL, message text NOT NULL, user_id integer NOT NULL, timestamp timestamp with time zone DEFAULT CURRENT_TIMESTAMP ); ALTER SEQUENCE public.feedbacks_id_seq OWNED BY public.feedbacks.id; CREATE TABLE public.fill_gap_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), fill_gap_gap_id bigint NOT NULL, answer text NOT NULL, is_correct boolean NOT NULL DEFAULT false, feedback text NOT NULL ); CREATE TABLE public.fill_gap_gaps ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), fill_gap_exercise_id bigint NOT NULL, token_index integer NOT NULL, token_text text NOT NULL, gap_order integer NOT NULL ); ALTER TABLE public.fill_gap_gaps ADD CONSTRAINT fill_gap_gaps_fill_gap_exercise_id_token_index_key UNIQUE (fill_gap_exercise_id, token_index); ALTER TABLE public.fill_gap_answers ADD CONSTRAINT fill_gap_answers_fill_gap_gap_id_fkey FOREIGN KEY (fill_gap_gap_id) REFERENCES fill_gap_gaps(id) ON DELETE CASCADE; CREATE TABLE public.fill_gap_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint UNIQUE NOT NULL, title text NOT NULL, source_text text NOT NULL, source_html text NOT NULL DEFAULT ''::text ); ALTER TABLE public.fill_gap_exercises ADD CONSTRAINT fill_gap_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; ALTER TABLE public.fill_gap_gaps ADD CONSTRAINT fill_gap_gaps_fill_gap_exercise_id_fkey FOREIGN KEY (fill_gap_exercise_id) REFERENCES fill_gap_exercises(id) ON DELETE CASCADE; CREATE TABLE public.fill_gap_user_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), user_id bigint NOT NULL, fill_gap_gap_id bigint NOT NULL, answer text NOT NULL, is_correct boolean NOT NULL DEFAULT false, feedback text ); ALTER TABLE public.fill_gap_user_answers ADD CONSTRAINT fill_gap_user_answers_user_id_fill_gap_gap_id_key UNIQUE (user_id, fill_gap_gap_id); ALTER TABLE public.fill_gap_user_answers ADD CONSTRAINT fill_gap_user_answers_fill_gap_gap_id_fkey FOREIGN KEY (fill_gap_gap_id) REFERENCES fill_gap_gaps(id) ON DELETE CASCADE; ALTER TABLE public.fill_gap_user_answers ADD CONSTRAINT fill_gap_user_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.jumbled_sentence_sentences ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, jumbled_exercise_id bigint NOT NULL, sentence text NOT NULL, alternates text[] DEFAULT ARRAY[]::text[], correct_feedback text, alternate_feedbacks text[] NOT NULL DEFAULT ARRAY[]::text[], incorrect_feedbacks text[] NOT NULL DEFAULT ARRAY[]::text[], incorrect_alternates text[] NOT NULL DEFAULT ARRAY[]::text[] ); CREATE TABLE public.jumbled_sentence_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint UNIQUE NOT NULL, created_by bigint NOT NULL, title text NOT NULL ); ALTER TABLE public.jumbled_sentence_sentences ADD CONSTRAINT fk_jumbled_exercise FOREIGN KEY (jumbled_exercise_id) REFERENCES jumbled_sentence_exercises(id) ON DELETE CASCADE; CREATE TABLE public.form_parts ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), form_id bigint NOT NULL, title_en text, title_de text, step_label text NOT NULL, advice_en text, advice_de text, advice_threshold numeric(5,2) DEFAULT 3 ); ALTER TABLE public.form_parts ADD CONSTRAINT check_step_label CHECK ((step_label ~ '^[A-Za-z0-9]$'::text)); ALTER TABLE public.form_parts ADD CONSTRAINT unique_step_label UNIQUE (form_id, step_label); CREATE TABLE public.forms ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), public_id text UNIQUE NOT NULL, title_en text, title_de text, description_en text, description_de text ); ALTER TABLE public.form_parts ADD CONSTRAINT form_parts_form_id_fkey FOREIGN KEY (form_id) REFERENCES forms(id) ON DELETE RESTRICT; CREATE TABLE public.free_form_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), free_form_exercise_id bigint NOT NULL, answer text NOT NULL, is_correct boolean DEFAULT false, feedback text, free_form_question_id bigint ); CREATE INDEX free_form_answers_free_form_exercise_id_idx ON public.free_form_answers USING btree (free_form_exercise_id); CREATE TABLE public.free_form_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint NOT NULL, question text, title text NOT NULL ); ALTER TABLE public.free_form_answers ADD CONSTRAINT free_form_answers_free_form_exercise_id_fkey FOREIGN KEY (free_form_exercise_id) REFERENCES free_form_exercises(id) ON DELETE CASCADE; CREATE TABLE public.free_form_questions ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), free_form_exercise_id bigint NOT NULL, question text NOT NULL, question_order integer NOT NULL DEFAULT 1 ); ALTER TABLE public.free_form_answers ADD CONSTRAINT free_form_answers_free_form_question_id_fkey FOREIGN KEY (free_form_question_id) REFERENCES free_form_questions(id) ON DELETE CASCADE; ALTER TABLE public.free_form_exercises ADD CONSTRAINT free_form_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; ALTER TABLE public.free_form_questions ADD CONSTRAINT free_form_questions_free_form_exercise_id_fkey FOREIGN KEY (free_form_exercise_id) REFERENCES free_form_exercises(id) ON DELETE CASCADE; CREATE TABLE public.free_form_user_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), answer text NOT NULL, free_form_exercise_id bigint NOT NULL, is_correct boolean DEFAULT false, user_id bigint NOT NULL, free_form_question_id bigint ); ALTER TABLE public.free_form_user_answers ADD CONSTRAINT free_form_user_answers_free_form_exercise_id_user_id_key UNIQUE (free_form_exercise_id, user_id); ALTER TABLE public.free_form_user_answers ADD CONSTRAINT free_form_user_answers_free_form_exercise_id_fkey FOREIGN KEY (free_form_exercise_id) REFERENCES free_form_exercises(id) ON DELETE CASCADE; ALTER TABLE public.free_form_user_answers ADD CONSTRAINT free_form_user_answers_free_form_question_id_fkey FOREIGN KEY (free_form_question_id) REFERENCES free_form_questions(id) ON DELETE CASCADE; ALTER TABLE public.free_form_user_answers ADD CONSTRAINT free_form_user_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.glossary_entries ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), word text NOT NULL, word_definition text NOT NULL ); CREATE TRIGGER update_glossary_entries_updated_at BEFORE UPDATE ON public.glossary_entries FOR EACH ROW EXECUTE FUNCTION updated_at(); CREATE SEQUENCE public.grammar_topics_id_seq; CREATE TABLE public.grammar_topics ( id integer PRIMARY KEY NOT NULL DEFAULT nextval('grammar_topics_id_seq'::regclass), name text NOT NULL, sort_order integer ); ALTER SEQUENCE public.grammar_topics_id_seq OWNED BY public.grammar_topics.id; CREATE TABLE public.html_pages ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, title text, content text, page_order integer, slug text, page_group text, grammar_topic_id integer, description text ); ALTER TABLE public.html_pages ADD CONSTRAINT html_pages_grammar_topic_id_fkey FOREIGN KEY (grammar_topic_id) REFERENCES grammar_topics(id); ALTER TABLE public.jumbled_sentence_exercises ADD CONSTRAINT jumbled_sentence_exercises_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE; ALTER TABLE public.jumbled_sentence_exercises ADD CONSTRAINT jumbled_sentence_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; CREATE TABLE public.memory_game_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), exercise_id bigint UNIQUE NOT NULL, created_by bigint NOT NULL, title text NOT NULL, description text NOT NULL ); ALTER TABLE public.memory_game_exercises ADD CONSTRAINT memory_game_exercises_created_by_fkey FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE CASCADE; ALTER TABLE public.memory_game_exercises ADD CONSTRAINT memory_game_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; CREATE TABLE public.memory_game_pairs ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), memory_game_exercise_id bigint NOT NULL, left_item text NOT NULL, right_item text NOT NULL, pair_order integer NOT NULL ); CREATE INDEX memory_game_pairs_exercise_order_idx ON public.memory_game_pairs USING btree (memory_game_exercise_id, pair_order); ALTER TABLE public.memory_game_pairs ADD CONSTRAINT memory_game_pairs_memory_game_exercise_id_pair_order_key UNIQUE (memory_game_exercise_id, pair_order); ALTER TABLE public.memory_game_pairs ADD CONSTRAINT memory_game_pairs_memory_game_exercise_id_right_item_key UNIQUE (memory_game_exercise_id, right_item); ALTER TABLE public.memory_game_pairs ADD CONSTRAINT memory_game_pairs_memory_game_exercise_id_fkey FOREIGN KEY (memory_game_exercise_id) REFERENCES memory_game_exercises(id) ON DELETE CASCADE; CREATE TABLE public.memory_game_user_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), user_id bigint NOT NULL, memory_game_exercise_id bigint NOT NULL, attempts jsonb NOT NULL, score integer NOT NULL, completed boolean NOT NULL DEFAULT false ); CREATE INDEX memory_game_user_answers_user_exercise_idx ON public.memory_game_user_answers USING btree (user_id, memory_game_exercise_id); ALTER TABLE public.memory_game_user_answers ADD CONSTRAINT memory_game_user_answers_user_id_memory_game_exercise_id_key UNIQUE (user_id, memory_game_exercise_id); ALTER TABLE public.memory_game_user_answers ADD CONSTRAINT memory_game_user_answers_memory_game_exercise_id_fkey FOREIGN KEY (memory_game_exercise_id) REFERENCES memory_game_exercises(id) ON DELETE CASCADE; ALTER TABLE public.memory_game_user_answers ADD CONSTRAINT memory_game_user_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.multichoice_content ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, multichoice_exercise_id bigint NOT NULL, content_type text NOT NULL, content_value text NOT NULL, content_order integer NOT NULL, correct_answer text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now() ); CREATE TABLE public.multichoice_exercises ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, exercise_id bigint NOT NULL, title text NOT NULL, exercise_description text, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now() ); ALTER TABLE public.multichoice_content ADD CONSTRAINT multichoice_content_multichoice_exercise_id_fkey FOREIGN KEY (multichoice_exercise_id) REFERENCES multichoice_exercises(id) ON DELETE CASCADE; ALTER TABLE public.multichoice_exercises ADD CONSTRAINT multichoice_exercises_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES exercises(id) ON DELETE CASCADE; CREATE TABLE public.multichoice_options ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, multichoice_content_id bigint NOT NULL, option_value text NOT NULL, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now() ); ALTER TABLE public.multichoice_options ADD CONSTRAINT multichoice_options_multichoice_content_id_fkey FOREIGN KEY (multichoice_content_id) REFERENCES multichoice_content(id) ON DELETE CASCADE; CREATE TABLE public.part_questions ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), form_part_id bigint NOT NULL, title_en text, title_de text ); ALTER TABLE public.part_questions ADD CONSTRAINT part_questions_form_part_id_fkey FOREIGN KEY (form_part_id) REFERENCES form_parts(id) ON DELETE RESTRICT; CREATE TABLE public.pgmigrate_migrations ( id text PRIMARY KEY NOT NULL, checksum text NOT NULL, execution_time_in_millis bigint NOT NULL, applied_at timestamp with time zone NOT NULL ); CREATE TABLE public.user_question_answers ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), answer integer NOT NULL, user_id bigint, part_question_id bigint NOT NULL, answerer_id text NOT NULL ); ALTER TABLE public.user_question_answers ADD CONSTRAINT user_question_answers_user_id_part_question_id_key UNIQUE (user_id, part_question_id); ALTER TABLE public.user_question_answers ADD CONSTRAINT user_question_answers_part_question_id_fkey FOREIGN KEY (part_question_id) REFERENCES part_questions(id) ON DELETE RESTRICT; ALTER TABLE public.user_question_answers ADD CONSTRAINT user_question_answers_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; CREATE TABLE public.word_category_mappings ( id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY, created_at timestamp with time zone NOT NULL DEFAULT now(), updated_at timestamp with time zone NOT NULL DEFAULT now(), word_id bigint NOT NULL, category_id bigint NOT NULL, exercise_id bigint NOT NULL ); CREATE TRIGGER updated_at BEFORE UPDATE ON public.word_category_mappings FOR EACH ROW EXECUTE FUNCTION updated_at(); ALTER TABLE public.word_category_mappings ADD CONSTRAINT word_category_mappings_category_id_fkey FOREIGN KEY (category_id) REFERENCES dnd_categories(id) ON DELETE CASCADE; ALTER TABLE public.word_category_mappings ADD CONSTRAINT word_category_mappings_exercise_id_fkey FOREIGN KEY (exercise_id) REFERENCES dnd_exercises(id) ON DELETE CASCADE; ALTER TABLE public.word_category_mappings ADD CONSTRAINT word_category_mappings_word_id_fkey FOREIGN KEY (word_id) REFERENCES draggable_words(id) ON DELETE CASCADE;