Files
OHTU-German-learning-website/gradesa/data/schema.sql
T
2026-07-06 23:42:38 +00:00

842 lines
32 KiB
PL/PgSQL

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
);
CREATE INDEX click_exercises_search_fts_idx ON public.click_exercises USING gin (to_tsvector('simple'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(category, ''::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,
updated_by bigint
);
CREATE TRIGGER update_exercises_updated_at BEFORE UPDATE ON public.exercises FOR EACH ROW EXECUTE FUNCTION updated_at();
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,
description text
);
CREATE INDEX dnd_exercises_search_fts_idx ON public.dnd_exercises USING gin (to_tsvector('simple'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(description, ''::text))));
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
);
CREATE INDEX dnd_match_exercises_search_fts_idx ON public.dnd_match_exercises USING gin (to_tsvector('simple'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(description, ''::text))));
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);
CREATE INDEX dnd_match_pairs_search_fts_idx ON public.dnd_match_pairs USING gin (to_tsvector('simple'::regconfig, ((COALESCE(left_item, ''::text) || ' '::text) || COALESCE(right_item, ''::text))));
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);
ALTER TABLE public.exercises
ADD CONSTRAINT exercises_updated_by_fkey
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL;
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
);
CREATE INDEX fill_gap_gaps_search_fts_idx ON public.fill_gap_gaps USING gin (to_tsvector('simple'::regconfig, COALESCE(token_text, ''::text)));
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
);
CREATE INDEX fill_gap_exercises_search_fts_idx ON public.fill_gap_exercises USING gin (to_tsvector('simple'::regconfig, ((((COALESCE(title, ''::text) || ' '::text) || COALESCE(source_text, ''::text)) || ' '::text) || COALESCE(source_html, ''::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 INDEX jumbled_sentence_sentences_search_fts_idx ON public.jumbled_sentence_sentences USING gin (to_tsvector('simple'::regconfig, COALESCE(sentence, ''::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
);
CREATE INDEX jumbled_sentence_exercises_search_fts_idx ON public.jumbled_sentence_exercises USING gin (to_tsvector('simple'::regconfig, COALESCE(title, ''::text)));
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
);
CREATE INDEX free_form_exercises_search_fts_idx ON public.free_form_exercises USING gin (to_tsvector('simple'::regconfig, COALESCE(title, ''::text)));
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
);
CREATE INDEX free_form_questions_search_fts_idx ON public.free_form_questions USING gin (to_tsvector('simple'::regconfig, COALESCE(question, ''::text)));
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_user_exercise_question_key
UNIQUE (user_id, free_form_exercise_id, free_form_question_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,
updated_by bigint
);
CREATE TRIGGER update_glossary_entries_updated_at BEFORE UPDATE ON public.glossary_entries FOR EACH ROW EXECUTE FUNCTION updated_at();
ALTER TABLE public.glossary_entries
ADD CONSTRAINT glossary_entries_updated_by_fkey
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL;
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,
updated_at timestamp with time zone NOT NULL DEFAULT now(),
updated_by bigint
);
CREATE INDEX html_pages_search_fts_idx ON public.html_pages USING gin (to_tsvector('simple'::regconfig, ((((COALESCE(title, ''::text) || ' '::text) || COALESCE(description, ''::text)) || ' '::text) || COALESCE(content, ''::text))));
CREATE TRIGGER update_html_pages_updated_at BEFORE UPDATE ON public.html_pages FOR EACH ROW EXECUTE FUNCTION updated_at();
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.html_pages
ADD CONSTRAINT html_pages_updated_by_fkey
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL;
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
);
CREATE INDEX memory_game_exercises_search_fts_idx ON public.memory_game_exercises USING gin (to_tsvector('simple'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(description, ''::text))));
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);
CREATE INDEX memory_game_pairs_search_fts_idx ON public.memory_game_pairs USING gin (to_tsvector('simple'::regconfig, ((COALESCE(left_item, ''::text) || ' '::text) || COALESCE(right_item, ''::text))));
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 INDEX multichoice_content_search_fts_idx ON public.multichoice_content USING gin (to_tsvector('simple'::regconfig, ((COALESCE(content_value, ''::text) || ' '::text) || COALESCE(correct_answer, ''::text))));
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()
);
CREATE INDEX multichoice_exercises_search_fts_idx ON public.multichoice_exercises USING gin (to_tsvector('simple'::regconfig, ((COALESCE(title, ''::text) || ' '::text) || COALESCE(exercise_description, ''::text))));
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.news_articles (
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,
content text NOT NULL,
created_by bigint,
updated_by bigint,
is_teacher_only boolean NOT NULL DEFAULT false
);
CREATE TRIGGER update_news_articles_updated_at BEFORE UPDATE ON public.news_articles FOR EACH ROW EXECUTE FUNCTION updated_at();
ALTER TABLE public.news_articles
ADD CONSTRAINT news_articles_created_by_fkey
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE public.news_articles
ADD CONSTRAINT news_articles_updated_by_fkey
FOREIGN KEY (updated_by) REFERENCES users(id) ON DELETE SET NULL;
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;