import sqlite3 from typing import Iterable from app.config import DB_NAME DEFAULT_CATEGORIES: Iterable[tuple[str, str]] = [ ("Продукты", "Расход"), ("Транспорт", "Расход"), ("Кафе", "Расход"), ("Жильё", "Расход"), ("Здоровье", "Расход"), ("Развлечения", "Расход"), ("Одежда", "Расход"), ("Образование", "Расход"), ("Коммунальные услуги", "Расход"), ("Связь и интернет", "Расход"), ("Зарплата", "Доход"), ("Фриланс", "Доход"), ("Подарки", "Доход"), ("Возврат", "Доход"), ("Проценты по вкладу", "Доход"), ] def get_connection() -> sqlite3.Connection: """Open SQLite connection with row access by column name.""" connection = sqlite3.connect(DB_NAME) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys = ON") return connection def init_database() -> None: """Create all tables, indexes and default records.""" with get_connection() as connection: cursor = connection.cursor() cursor.executescript( """ CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, account_type TEXT NOT NULL, balance REAL NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS categories ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, category_type TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(name, category_type) ); CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, amount REAL NOT NULL, transaction_type TEXT NOT NULL, account_id INTEGER NOT NULL, category_id INTEGER NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (account_id) REFERENCES accounts(id) ON DELETE RESTRICT, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS subscriptions ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, amount REAL NOT NULL, billing_day INTEGER NOT NULL, period TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'Активна', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS budgets ( id INTEGER PRIMARY KEY AUTOINCREMENT, category_id INTEGER NOT NULL UNIQUE, limit_amount REAL NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE RESTRICT ); CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_transactions_created_at ON transactions(created_at); CREATE INDEX IF NOT EXISTS idx_transactions_type ON transactions(transaction_type); CREATE INDEX IF NOT EXISTS idx_transactions_account ON transactions(account_id); CREATE INDEX IF NOT EXISTS idx_transactions_category ON transactions(category_id); """ ) _ensure_column(cursor, "accounts", "created_at", "TEXT") _ensure_column(cursor, "categories", "created_at", "TEXT") _ensure_column(cursor, "subscriptions", "created_at", "TEXT") _ensure_column(cursor, "budgets", "created_at", "TEXT") cursor.executemany( """ INSERT OR IGNORE INTO categories (name, category_type) VALUES (?, ?) """, DEFAULT_CATEGORIES, ) connection.commit() def _ensure_column(cursor: sqlite3.Cursor, table: str, column: str, column_type: str) -> None: """Add a column when an old alpha database is opened.""" columns = [row[1] for row in cursor.execute(f"PRAGMA table_info({table})")] if column not in columns: cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {column_type}") cursor.execute( f"UPDATE {table} SET {column} = CURRENT_TIMESTAMP " f"WHERE {column} IS NULL" )