All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m19s
498 lines
19 KiB
SQL
498 lines
19 KiB
SQL
-- Baseline Migration: Complete database schema
|
||
-- This migration represents the current state of the database schema
|
||
-- For existing databases, use: migrate force 1 (do not run this migration)
|
||
-- For new databases, this will create the complete schema
|
||
|
||
-- ============================================
|
||
-- Core Tables (no dependencies)
|
||
-- ============================================
|
||
|
||
-- Users table (base for multi-tenancy)
|
||
CREATE TABLE users (
|
||
id SERIAL PRIMARY KEY,
|
||
email VARCHAR(255) NOT NULL UNIQUE,
|
||
password_hash VARCHAR(255) NOT NULL,
|
||
name VARCHAR(255),
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
is_active BOOLEAN DEFAULT TRUE,
|
||
last_login_at TIMESTAMP WITH TIME ZONE
|
||
);
|
||
|
||
CREATE INDEX idx_users_email ON users(email);
|
||
|
||
-- Dictionaries table
|
||
CREATE TABLE dictionaries (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_dictionaries_user_id ON dictionaries(user_id);
|
||
|
||
-- Insert default dictionary with id = 0
|
||
DO $$
|
||
BEGIN
|
||
-- Set sequence to -1 so next value will be 0
|
||
PERFORM setval('dictionaries_id_seq', -1, false);
|
||
|
||
-- Insert the default dictionary with id = 0
|
||
INSERT INTO dictionaries (id, name)
|
||
VALUES (0, 'Все слова')
|
||
ON CONFLICT (id) DO NOTHING;
|
||
|
||
-- Set the sequence to start from 1 (so next auto-increment will be 1)
|
||
PERFORM setval('dictionaries_id_seq', 1, false);
|
||
EXCEPTION
|
||
WHEN others THEN
|
||
-- If sequence doesn't exist or other error, try without sequence manipulation
|
||
INSERT INTO dictionaries (id, name)
|
||
VALUES (0, 'Все слова')
|
||
ON CONFLICT (id) DO NOTHING;
|
||
END $$;
|
||
|
||
-- Projects table
|
||
CREATE TABLE projects (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
priority SMALLINT,
|
||
deleted BOOLEAN NOT NULL DEFAULT FALSE,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_projects_deleted ON projects(deleted);
|
||
CREATE INDEX idx_projects_user_id ON projects(user_id);
|
||
|
||
-- Entries table
|
||
CREATE TABLE entries (
|
||
id SERIAL PRIMARY KEY,
|
||
text TEXT NOT NULL,
|
||
created_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_entries_user_id ON entries(user_id);
|
||
|
||
-- ============================================
|
||
-- Dependent Tables
|
||
-- ============================================
|
||
|
||
-- Words table (depends on dictionaries, users)
|
||
CREATE TABLE words (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255) NOT NULL,
|
||
translation TEXT NOT NULL,
|
||
description TEXT,
|
||
dictionary_id INTEGER NOT NULL DEFAULT 0 REFERENCES dictionaries(id),
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_words_dictionary_id ON words(dictionary_id);
|
||
CREATE INDEX idx_words_user_id ON words(user_id);
|
||
|
||
-- Progress table (depends on words, users)
|
||
CREATE TABLE progress (
|
||
id SERIAL PRIMARY KEY,
|
||
word_id INTEGER NOT NULL REFERENCES words(id) ON DELETE CASCADE,
|
||
success INTEGER DEFAULT 0,
|
||
failure INTEGER DEFAULT 0,
|
||
last_success_at TIMESTAMP,
|
||
last_failure_at TIMESTAMP,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
CONSTRAINT progress_word_user_unique UNIQUE (word_id, user_id)
|
||
);
|
||
|
||
CREATE INDEX idx_progress_user_id ON progress(user_id);
|
||
CREATE UNIQUE INDEX idx_progress_word_user_unique ON progress(word_id, user_id);
|
||
|
||
-- Configs table (depends on users)
|
||
CREATE TABLE configs (
|
||
id SERIAL PRIMARY KEY,
|
||
words_count INTEGER NOT NULL,
|
||
max_cards INTEGER,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_configs_user_id ON configs(user_id);
|
||
|
||
-- Config dictionaries table (depends on configs, dictionaries)
|
||
CREATE TABLE config_dictionaries (
|
||
config_id INTEGER NOT NULL REFERENCES configs(id) ON DELETE CASCADE,
|
||
dictionary_id INTEGER NOT NULL REFERENCES dictionaries(id) ON DELETE CASCADE,
|
||
PRIMARY KEY (config_id, dictionary_id)
|
||
);
|
||
|
||
CREATE INDEX idx_config_dictionaries_config_id ON config_dictionaries(config_id);
|
||
CREATE INDEX idx_config_dictionaries_dictionary_id ON config_dictionaries(dictionary_id);
|
||
|
||
-- Nodes table (depends on projects, entries, users)
|
||
CREATE TABLE nodes (
|
||
id SERIAL PRIMARY KEY,
|
||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||
entry_id INTEGER NOT NULL REFERENCES entries(id) ON DELETE CASCADE,
|
||
score NUMERIC(8,4),
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE
|
||
);
|
||
|
||
CREATE INDEX idx_nodes_project_id ON nodes(project_id);
|
||
CREATE INDEX idx_nodes_entry_id ON nodes(entry_id);
|
||
CREATE INDEX idx_nodes_user_id ON nodes(user_id);
|
||
|
||
-- Weekly goals table (depends on projects, users)
|
||
CREATE TABLE weekly_goals (
|
||
id SERIAL PRIMARY KEY,
|
||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||
goal_year INTEGER NOT NULL,
|
||
goal_week INTEGER NOT NULL,
|
||
min_goal_score NUMERIC(10,4) NOT NULL DEFAULT 0,
|
||
max_goal_score NUMERIC(10,4),
|
||
max_score NUMERIC(10,4),
|
||
priority SMALLINT,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
CONSTRAINT weekly_goals_project_id_goal_year_goal_week_key UNIQUE (project_id, goal_year, goal_week)
|
||
);
|
||
|
||
CREATE INDEX idx_weekly_goals_project_id ON weekly_goals(project_id);
|
||
CREATE INDEX idx_weekly_goals_user_id ON weekly_goals(user_id);
|
||
|
||
-- Tasks table (depends on users)
|
||
CREATE TABLE tasks (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
completed INTEGER DEFAULT 0,
|
||
last_completed_at TIMESTAMP WITH TIME ZONE,
|
||
parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
||
reward_message TEXT,
|
||
progression_base NUMERIC(10,4),
|
||
deleted BOOLEAN DEFAULT FALSE,
|
||
repetition_period INTERVAL,
|
||
next_show_at TIMESTAMP WITH TIME ZONE,
|
||
repetition_date TEXT,
|
||
config_id INTEGER REFERENCES configs(id) ON DELETE SET NULL,
|
||
wishlist_id INTEGER,
|
||
reward_policy VARCHAR(20) DEFAULT 'personal'
|
||
);
|
||
|
||
CREATE INDEX idx_tasks_user_id ON tasks(user_id);
|
||
CREATE INDEX idx_tasks_parent_task_id ON tasks(parent_task_id);
|
||
CREATE INDEX idx_tasks_deleted ON tasks(deleted);
|
||
CREATE INDEX idx_tasks_last_completed_at ON tasks(last_completed_at);
|
||
CREATE INDEX idx_tasks_config_id ON tasks(config_id);
|
||
CREATE UNIQUE INDEX idx_tasks_config_id_unique ON tasks(config_id) WHERE config_id IS NOT NULL AND deleted = FALSE;
|
||
CREATE INDEX idx_tasks_wishlist_id ON tasks(wishlist_id);
|
||
CREATE UNIQUE INDEX idx_tasks_wishlist_id_unique ON tasks(wishlist_id) WHERE wishlist_id IS NOT NULL AND deleted = FALSE;
|
||
CREATE INDEX idx_tasks_id_user_deleted ON tasks(id, user_id, deleted) WHERE deleted = FALSE;
|
||
CREATE INDEX idx_tasks_parent_deleted_covering ON tasks(parent_task_id, deleted, id)
|
||
INCLUDE (name, completed, last_completed_at, reward_message, progression_base)
|
||
WHERE deleted = FALSE;
|
||
|
||
COMMENT ON COLUMN tasks.config_id IS 'Link to test config. NULL if task is not a test.';
|
||
COMMENT ON COLUMN tasks.reward_policy IS 'For wishlist tasks: personal = only if user completes, shared = anyone completes';
|
||
|
||
-- Reward configs table (depends on tasks, projects)
|
||
CREATE TABLE reward_configs (
|
||
id SERIAL PRIMARY KEY,
|
||
position INTEGER NOT NULL,
|
||
task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE,
|
||
project_id INTEGER REFERENCES projects(id) ON DELETE CASCADE,
|
||
value NUMERIC(10,4) NOT NULL,
|
||
use_progression BOOLEAN DEFAULT FALSE
|
||
);
|
||
|
||
CREATE INDEX idx_reward_configs_task_id ON reward_configs(task_id);
|
||
CREATE INDEX idx_reward_configs_project_id ON reward_configs(project_id);
|
||
CREATE UNIQUE INDEX idx_reward_configs_task_position ON reward_configs(task_id, position);
|
||
|
||
-- Telegram integrations table (depends on users)
|
||
CREATE TABLE telegram_integrations (
|
||
id SERIAL PRIMARY KEY,
|
||
chat_id VARCHAR(255),
|
||
telegram_user_id BIGINT,
|
||
start_token VARCHAR(255),
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE UNIQUE INDEX idx_telegram_integrations_user_id_unique ON telegram_integrations(user_id) WHERE user_id IS NOT NULL;
|
||
CREATE INDEX idx_telegram_integrations_user_id ON telegram_integrations(user_id);
|
||
CREATE UNIQUE INDEX idx_telegram_integrations_start_token ON telegram_integrations(start_token) WHERE start_token IS NOT NULL;
|
||
CREATE UNIQUE INDEX idx_telegram_integrations_telegram_user_id ON telegram_integrations(telegram_user_id) WHERE telegram_user_id IS NOT NULL;
|
||
CREATE INDEX idx_telegram_integrations_chat_id ON telegram_integrations(chat_id) WHERE chat_id IS NOT NULL;
|
||
|
||
-- Todoist integrations table (depends on users)
|
||
CREATE TABLE todoist_integrations (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
todoist_user_id BIGINT,
|
||
todoist_email VARCHAR(255),
|
||
access_token TEXT,
|
||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT todoist_integrations_user_id_unique UNIQUE (user_id)
|
||
);
|
||
|
||
CREATE INDEX idx_todoist_integrations_user_id ON todoist_integrations(user_id);
|
||
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_user_id ON todoist_integrations(todoist_user_id) WHERE todoist_user_id IS NOT NULL;
|
||
CREATE UNIQUE INDEX idx_todoist_integrations_todoist_email ON todoist_integrations(todoist_email) WHERE todoist_email IS NOT NULL;
|
||
|
||
-- Wishlist boards table (depends on users)
|
||
CREATE TABLE wishlist_boards (
|
||
id SERIAL PRIMARY KEY,
|
||
owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
invite_token VARCHAR(64) UNIQUE,
|
||
invite_enabled BOOLEAN DEFAULT FALSE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN DEFAULT FALSE
|
||
);
|
||
|
||
CREATE INDEX idx_wishlist_boards_owner_id ON wishlist_boards(owner_id);
|
||
CREATE INDEX idx_wishlist_boards_invite_token ON wishlist_boards(invite_token) WHERE invite_token IS NOT NULL;
|
||
CREATE INDEX idx_wishlist_boards_owner_deleted ON wishlist_boards(owner_id, deleted);
|
||
|
||
-- Wishlist board members table (depends on wishlist_boards, users)
|
||
CREATE TABLE wishlist_board_members (
|
||
id SERIAL PRIMARY KEY,
|
||
board_id INTEGER NOT NULL REFERENCES wishlist_boards(id) ON DELETE CASCADE,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
joined_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT unique_board_member UNIQUE (board_id, user_id)
|
||
);
|
||
|
||
CREATE INDEX idx_board_members_board_id ON wishlist_board_members(board_id);
|
||
CREATE INDEX idx_board_members_user_id ON wishlist_board_members(user_id);
|
||
|
||
-- Wishlist items table (depends on users, wishlist_boards)
|
||
CREATE TABLE wishlist_items (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
price NUMERIC(10,2),
|
||
image_path VARCHAR(500),
|
||
link TEXT,
|
||
completed BOOLEAN DEFAULT FALSE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
deleted BOOLEAN DEFAULT FALSE,
|
||
board_id INTEGER REFERENCES wishlist_boards(id) ON DELETE CASCADE,
|
||
author_id INTEGER REFERENCES users(id) ON DELETE SET NULL
|
||
);
|
||
|
||
CREATE INDEX idx_wishlist_items_user_id ON wishlist_items(user_id);
|
||
CREATE INDEX idx_wishlist_items_user_deleted ON wishlist_items(user_id, deleted);
|
||
CREATE INDEX idx_wishlist_items_user_completed ON wishlist_items(user_id, completed, deleted);
|
||
CREATE INDEX idx_wishlist_items_board_id ON wishlist_items(board_id);
|
||
CREATE INDEX idx_wishlist_items_author_id ON wishlist_items(author_id);
|
||
CREATE INDEX idx_wishlist_items_id_deleted_covering ON wishlist_items(id, deleted)
|
||
INCLUDE (name)
|
||
WHERE deleted = FALSE;
|
||
|
||
-- Add foreign key for tasks.wishlist_id after wishlist_items is created
|
||
ALTER TABLE tasks ADD CONSTRAINT tasks_wishlist_id_fkey
|
||
FOREIGN KEY (wishlist_id) REFERENCES wishlist_items(id) ON DELETE SET NULL;
|
||
|
||
COMMENT ON TABLE wishlist_items IS 'Wishlist items for users';
|
||
COMMENT ON COLUMN wishlist_items.completed IS 'Flag indicating item was purchased/received';
|
||
COMMENT ON COLUMN wishlist_items.image_path IS 'Path to image file relative to uploads root';
|
||
|
||
-- Task conditions table (depends on tasks)
|
||
CREATE TABLE task_conditions (
|
||
id SERIAL PRIMARY KEY,
|
||
task_id INTEGER NOT NULL REFERENCES tasks(id) ON DELETE CASCADE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
CONSTRAINT unique_task_condition UNIQUE (task_id)
|
||
);
|
||
|
||
CREATE INDEX idx_task_conditions_task_id ON task_conditions(task_id);
|
||
|
||
COMMENT ON TABLE task_conditions IS 'Reusable unlock conditions based on task completion';
|
||
|
||
-- Score conditions table (depends on projects, users)
|
||
CREATE TABLE score_conditions (
|
||
id SERIAL PRIMARY KEY,
|
||
project_id INTEGER NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||
required_points NUMERIC(10,4) NOT NULL,
|
||
start_date DATE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
CONSTRAINT unique_score_condition UNIQUE (project_id, required_points, start_date)
|
||
);
|
||
|
||
CREATE INDEX idx_score_conditions_project_id ON score_conditions(project_id);
|
||
CREATE INDEX idx_score_conditions_user_id ON score_conditions(user_id);
|
||
|
||
COMMENT ON TABLE score_conditions IS 'Reusable unlock conditions based on project points';
|
||
COMMENT ON COLUMN score_conditions.start_date IS 'Date from which to start counting points. NULL means count all time.';
|
||
|
||
-- Wishlist conditions table (depends on wishlist_items, task_conditions, score_conditions, users)
|
||
CREATE TABLE wishlist_conditions (
|
||
id SERIAL PRIMARY KEY,
|
||
wishlist_item_id INTEGER NOT NULL REFERENCES wishlist_items(id) ON DELETE CASCADE,
|
||
task_condition_id INTEGER REFERENCES task_conditions(id) ON DELETE CASCADE,
|
||
score_condition_id INTEGER REFERENCES score_conditions(id) ON DELETE CASCADE,
|
||
display_order INTEGER DEFAULT 0,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
|
||
CONSTRAINT check_exactly_one_condition CHECK (
|
||
(task_condition_id IS NOT NULL AND score_condition_id IS NULL) OR
|
||
(task_condition_id IS NULL AND score_condition_id IS NOT NULL)
|
||
)
|
||
);
|
||
|
||
CREATE INDEX idx_wishlist_conditions_item_id ON wishlist_conditions(wishlist_item_id);
|
||
CREATE INDEX idx_wishlist_conditions_item_order ON wishlist_conditions(wishlist_item_id, display_order);
|
||
CREATE INDEX idx_wishlist_conditions_task_condition_id ON wishlist_conditions(task_condition_id);
|
||
CREATE INDEX idx_wishlist_conditions_score_condition_id ON wishlist_conditions(score_condition_id);
|
||
CREATE INDEX idx_wishlist_conditions_user_id ON wishlist_conditions(user_id);
|
||
|
||
COMMENT ON TABLE wishlist_conditions IS 'Links between wishlist items and unlock conditions. Multiple conditions per item use AND logic.';
|
||
COMMENT ON COLUMN wishlist_conditions.display_order IS 'Order for displaying conditions in UI';
|
||
COMMENT ON COLUMN wishlist_conditions.user_id IS 'Owner of this condition. Each user has their own goals on shared boards';
|
||
|
||
-- Refresh tokens table (depends on users)
|
||
CREATE TABLE refresh_tokens (
|
||
id SERIAL PRIMARY KEY,
|
||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||
token_hash VARCHAR(255) NOT NULL,
|
||
expires_at TIMESTAMP WITH TIME ZONE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
|
||
CREATE INDEX idx_refresh_tokens_user_id ON refresh_tokens(user_id);
|
||
CREATE INDEX idx_refresh_tokens_token_hash ON refresh_tokens(token_hash);
|
||
|
||
-- ============================================
|
||
-- Materialized Views
|
||
-- ============================================
|
||
|
||
-- Weekly report materialized view
|
||
CREATE MATERIALIZED VIEW weekly_report_mv AS
|
||
SELECT
|
||
p.id AS project_id,
|
||
agg.report_year,
|
||
agg.report_week,
|
||
COALESCE(agg.total_score, 0.0000) AS total_score,
|
||
CASE
|
||
WHEN wg.max_score IS NULL THEN COALESCE(agg.total_score, 0.0000)
|
||
ELSE LEAST(COALESCE(agg.total_score, 0.0000), wg.max_score)
|
||
END AS normalized_total_score
|
||
FROM
|
||
projects p
|
||
LEFT JOIN
|
||
(
|
||
SELECT
|
||
n.project_id,
|
||
EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year,
|
||
EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week,
|
||
SUM(n.score) AS total_score
|
||
FROM
|
||
nodes n
|
||
JOIN
|
||
entries e ON n.entry_id = e.id
|
||
GROUP BY
|
||
1, 2, 3
|
||
) agg
|
||
ON p.id = agg.project_id
|
||
LEFT JOIN
|
||
weekly_goals wg
|
||
ON wg.project_id = p.id
|
||
AND wg.goal_year = agg.report_year
|
||
AND wg.goal_week = agg.report_week
|
||
WHERE
|
||
p.deleted = FALSE
|
||
ORDER BY
|
||
p.id, agg.report_year, agg.report_week
|
||
WITH DATA;
|
||
|
||
CREATE INDEX idx_weekly_report_mv_project_year_week ON weekly_report_mv(project_id, report_year, report_week);
|
||
|
||
COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries. Includes all projects via LEFT JOIN. Adds normalized_total_score using weekly_goals.max_score snapshot.';
|
||
|
||
-- ============================================
|
||
-- Comments
|
||
-- ============================================
|
||
|
||
COMMENT ON TABLE configs IS 'Test configurations (words_count, max_cards, dictionary associations). Linked to tasks via tasks.config_id.';
|
||
COMMENT ON TABLE wishlist_boards IS 'Wishlist boards for organizing and sharing wishes';
|
||
COMMENT ON COLUMN wishlist_boards.invite_token IS 'Token for invite link, NULL = disabled';
|
||
COMMENT ON COLUMN wishlist_boards.invite_enabled IS 'Whether invite link is active';
|
||
COMMENT ON TABLE wishlist_board_members IS 'Users who joined boards via invite link (not owners)';
|
||
COMMENT ON COLUMN wishlist_items.author_id IS 'User who created this item (may differ from board owner on shared boards)';
|
||
COMMENT ON COLUMN wishlist_items.board_id IS 'Board this item belongs to';
|
||
|
||
-- ============================================
|
||
-- Additional Tables
|
||
-- ============================================
|
||
|
||
-- Eateries table
|
||
CREATE TABLE eateries (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255),
|
||
address VARCHAR(255),
|
||
type VARCHAR(50),
|
||
distance DOUBLE PRECISION
|
||
);
|
||
|
||
-- Interesting places table
|
||
CREATE TABLE interesting_places (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT,
|
||
city TEXT,
|
||
description TEXT,
|
||
added_at TIMESTAMP WITH TIME ZONE,
|
||
is_visited BOOLEAN,
|
||
phone_number TEXT,
|
||
address TEXT,
|
||
updated_at TIMESTAMP WITH TIME ZONE
|
||
);
|
||
|
||
-- Music groups table
|
||
CREATE TABLE music_groups (
|
||
id INTEGER PRIMARY KEY,
|
||
name TEXT,
|
||
possible_locations TEXT
|
||
);
|
||
|
||
-- N8N chat histories table
|
||
CREATE TABLE n8n_chat_histories (
|
||
id SERIAL PRIMARY KEY,
|
||
session_id VARCHAR(255) NOT NULL,
|
||
message JSONB NOT NULL
|
||
);
|
||
|
||
-- Places to visit table
|
||
CREATE TABLE places_to_visit (
|
||
id SERIAL PRIMARY KEY,
|
||
name TEXT NOT NULL,
|
||
city TEXT,
|
||
description TEXT,
|
||
added_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
is_visited BOOLEAN DEFAULT FALSE,
|
||
phone_number TEXT,
|
||
address TEXT,
|
||
updated_at TIMESTAMP WITH TIME ZONE
|
||
);
|
||
|
||
-- Restaurants table
|
||
CREATE TABLE restaurants (
|
||
id SERIAL PRIMARY KEY,
|
||
name VARCHAR(255),
|
||
address VARCHAR(255),
|
||
contact_info VARCHAR(255)
|
||
);
|
||
|
||
-- Upcoming concerts table (depends on music_groups)
|
||
CREATE TABLE upcoming_concerts (
|
||
id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
group_id INTEGER NOT NULL REFERENCES music_groups(id),
|
||
scheduled_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||
venue TEXT,
|
||
city TEXT,
|
||
tickets_url TEXT
|
||
);
|
||
|
||
CREATE UNIQUE INDEX idx_unique_concert ON upcoming_concerts(scheduled_at, city, group_id);
|