CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE users ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), name text NOT NULL, email text NOT NULL UNIQUE, password_hash text NOT NULL, avatar_file text, created_at timestamptz NOT NULL DEFAULT NOW(), updated_at timestamptz NOT NULL DEFAULT NOW() ); CREATE TABLE sessions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash text NOT NULL UNIQUE, expires_at timestamptz NOT NULL, created_at timestamptz NOT NULL DEFAULT NOW() ); CREATE INDEX sessions_user_id_idx ON sessions(user_id); CREATE INDEX sessions_expires_at_idx ON sessions(expires_at); CREATE TABLE subscriptions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, amount double precision NOT NULL, billing_cycle text NOT NULL, activation_date text NOT NULL, first_payment_date text NOT NULL, end_date text, occurrence_count integer CHECK (occurrence_count > 0), custom_interval_value integer CHECK (custom_interval_value > 0), custom_interval_unit text CHECK ( custom_interval_unit IN ('days', 'weeks', 'months', 'years') ), subscription_status text NOT NULL, created_at text NOT NULL, updated_at text NOT NULL, CONSTRAINT subscriptions_amount_positive CHECK (amount > 0), CONSTRAINT subscriptions_billing_cycle_valid CHECK ( billing_cycle IN ('weekly', 'monthly', 'yearly', 'custom') ), CONSTRAINT subscriptions_status_valid CHECK ( subscription_status IN ('active', 'suspended', 'terminated') ), CONSTRAINT subscriptions_dates_valid CHECK ( activation_date <= first_payment_date AND (end_date IS NULL OR end_date >= first_payment_date) ) ); CREATE INDEX subscriptions_user_id_idx ON subscriptions(user_id); CREATE INDEX subscriptions_first_payment_date_idx ON subscriptions(first_payment_date); CREATE TABLE subscription_payments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, subscription_id uuid NOT NULL REFERENCES subscriptions(id) ON DELETE CASCADE, renewal_date text NOT NULL, status text NOT NULL, payment_date text, linked_payment_id uuid, created_at text NOT NULL, updated_at text NOT NULL, UNIQUE (subscription_id, renewal_date, user_id) ); CREATE INDEX subscription_payments_user_id_idx ON subscription_payments(user_id); CREATE TABLE bills ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, amount double precision NOT NULL, due_date text NOT NULL, status text NOT NULL, payment_date text, linked_payment_id uuid, created_at text NOT NULL, updated_at text NOT NULL ); CREATE INDEX bills_user_id_idx ON bills(user_id); CREATE INDEX bills_due_date_idx ON bills(due_date); CREATE TABLE one_time_payments ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, amount double precision NOT NULL, date text NOT NULL, linked_payment_id uuid, created_at text NOT NULL, updated_at text NOT NULL ); CREATE INDEX one_time_payments_user_id_idx ON one_time_payments(user_id); CREATE TABLE payments_ledger ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, amount double precision NOT NULL, date text NOT NULL, source_type text NOT NULL, source_id uuid NOT NULL, created_at text NOT NULL, updated_at text NOT NULL, UNIQUE (source_type, source_id, user_id) ); CREATE INDEX payments_ledger_user_id_idx ON payments_ledger(user_id); CREATE INDEX payments_ledger_date_idx ON payments_ledger(date); CREATE TABLE payment_titles ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, name text NOT NULL, normalized_name text NOT NULL, payment_type text NOT NULL, created_at text NOT NULL, updated_at text NOT NULL, CONSTRAINT payment_titles_type_valid CHECK ( payment_type IN ('subscription', 'bill', 'one_time', 'income', 'any') ), UNIQUE (user_id, normalized_name, payment_type) ); CREATE INDEX payment_titles_user_id_idx ON payment_titles(user_id); CREATE TABLE income ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, title text NOT NULL, amount double precision NOT NULL, type text NOT NULL, date text NOT NULL, created_at text NOT NULL, updated_at text NOT NULL ); CREATE INDEX income_user_id_idx ON income(user_id); CREATE TABLE user_settings ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), user_id uuid NOT NULL UNIQUE REFERENCES users(id) ON DELETE CASCADE, full_name text, monthly_income_goal double precision, monthly_savings_goal double precision, currency text NOT NULL, locale text NOT NULL, first_day_of_week text NOT NULL, default_dashboard_view text NOT NULL, created_at text NOT NULL, updated_at text NOT NULL );