CREATE TABLE IF NOT EXISTS groups ( id SERIAL PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS students ( id SERIAL PRIMARY KEY, name VARCHAR(150) NOT NULL UNIQUE, group_id INT REFERENCES groups(id), created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS entries ( id SERIAL PRIMARY KEY, student_name VARCHAR(150) NOT NULL, group_id INT NOT NULL REFERENCES groups(id), description TEXT NOT NULL, photo_path VARCHAR(255), deleted_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT now() ); CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT ); INSERT INTO settings (key, value) VALUES ('spam_interval_min', '30') ON CONFLICT (key) DO NOTHING; CREATE TABLE IF NOT EXISTS share_links ( id SERIAL PRIMARY KEY, token VARCHAR(40) UNIQUE NOT NULL, name VARCHAR(200) NOT NULL, group_id INT REFERENCES groups(id), student_name VARCHAR(150), date_from DATE, date_to DATE, anonymize_names BOOLEAN DEFAULT false, expires_at TIMESTAMPTZ, access_password_hash VARCHAR(255), created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_share_links_expires_at ON share_links(expires_at); INSERT INTO groups (name) VALUES ('Beginner'), ('Intermediate'), ('Advanced') ON CONFLICT (name) DO NOTHING; CREATE TABLE IF NOT EXISTS group_photos ( id SERIAL PRIMARY KEY, group_id INT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, photo_path VARCHAR(255) NOT NULL, caption TEXT, taken_at DATE, created_at TIMESTAMPTZ DEFAULT now() ); CREATE INDEX IF NOT EXISTS idx_group_photos_group_id ON group_photos(group_id); CREATE TABLE IF NOT EXISTS project_files ( id SERIAL PRIMARY KEY, entry_id INT REFERENCES entries(id) ON DELETE CASCADE, token VARCHAR(64) UNIQUE NOT NULL, path VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, created_at TIMESTAMPTZ DEFAULT now(), detached_at TIMESTAMPTZ );