from __future__ import annotations import sqlite3 from datetime import datetime from backend.features.accounts.models import ( MembershipAccount, MembershipRecord, SessionRecord, UserRecord, ) def _datetime(value: str) -> datetime: return datetime.fromisoformat(value) def _optional_datetime(value: str | None) -> datetime | None: return _datetime(value) if value else None def _user(row: sqlite3.Row) -> UserRecord: return UserRecord( id=int(row["id"]), username=str(row["username"]), username_key=str(row["username_key"]), password_hash=str(row["password_hash"]), is_admin=bool(row["is_admin"]), status=str(row["status"]), created_at=_datetime(str(row["created_at"])), updated_at=_datetime(str(row["updated_at"])), ) def _membership(row: sqlite3.Row, prefix: str = "") -> MembershipRecord: return MembershipRecord( user_id=int(row[f"{prefix}user_id"]), state=str(row[f"{prefix}state"]), expires_at=_optional_datetime(row[f"{prefix}expires_at"]), is_permanent=bool(row[f"{prefix}is_permanent"]), daily_llm_limit=int(row[f"{prefix}daily_llm_limit"]), updated_at=_datetime(str(row[f"{prefix}updated_at"])), updated_by=( int(row[f"{prefix}updated_by"]) if row[f"{prefix}updated_by"] is not None else None ), ) class AccountRepository: @staticmethod def count_users(connection: sqlite3.Connection) -> int: return int(connection.execute("SELECT COUNT(*) FROM users").fetchone()[0]) @staticmethod def create_user( connection: sqlite3.Connection, username: str, username_key: str, password_hash: str, is_admin: bool, now: datetime, ) -> UserRecord: timestamp = now.isoformat(timespec="seconds") cursor = connection.execute( """ INSERT INTO users (username, username_key, password_hash, is_admin, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) """, (username, username_key, password_hash, int(is_admin), timestamp, timestamp), ) return UserRecord( id=int(cursor.lastrowid), username=username, username_key=username_key, password_hash=password_hash, is_admin=is_admin, status="active", created_at=now, updated_at=now, ) @staticmethod def get_user_by_username_key( connection: sqlite3.Connection, username_key: str ) -> UserRecord | None: row = connection.execute( "SELECT * FROM users WHERE username_key = ?", (username_key,) ).fetchone() return _user(row) if row else None @staticmethod def get_user_by_id(connection: sqlite3.Connection, user_id: int) -> UserRecord | None: row = connection.execute("SELECT * FROM users WHERE id = ?", (user_id,)).fetchone() return _user(row) if row else None @staticmethod def create_default_membership( connection: sqlite3.Connection, user_id: int, now: datetime ) -> MembershipRecord: timestamp = now.isoformat(timespec="seconds") connection.execute( """ INSERT INTO memberships (user_id, state, daily_llm_limit, updated_at) VALUES (?, 'inactive', 50, ?) """, (user_id, timestamp), ) return MembershipRecord(user_id, "inactive", None, False, 50, now, None) @staticmethod def get_membership(connection: sqlite3.Connection, user_id: int) -> MembershipRecord | None: row = connection.execute( "SELECT * FROM memberships WHERE user_id = ?", (user_id,) ).fetchone() return _membership(row) if row else None @staticmethod def update_password( connection: sqlite3.Connection, user_id: int, password_hash: str, now: datetime ) -> None: connection.execute( "UPDATE users SET password_hash = ?, updated_at = ? WHERE id = ?", (password_hash, now.isoformat(timespec="seconds"), user_id), ) @staticmethod def create_session( connection: sqlite3.Connection, token_hash: str, csrf_hash: str, user_id: int, now: datetime, expires_at: datetime, ) -> None: timestamp = now.isoformat(timespec="seconds") connection.execute( """ INSERT INTO sessions (token_hash, user_id, csrf_hash, created_at, expires_at, last_seen_at) VALUES (?, ?, ?, ?, ?, ?) """, ( token_hash, user_id, csrf_hash, timestamp, expires_at.isoformat(timespec="seconds"), timestamp, ), ) @staticmethod def get_session(connection: sqlite3.Connection, token_hash: str) -> SessionRecord | None: row = connection.execute( """ SELECT u.*, m.user_id AS membership_user_id, m.state AS membership_state, m.expires_at AS membership_expires_at, m.is_permanent AS membership_is_permanent, m.daily_llm_limit AS membership_daily_llm_limit, m.updated_at AS membership_updated_at, m.updated_by AS membership_updated_by, s.token_hash AS session_token_hash, s.csrf_hash AS session_csrf_hash, s.expires_at AS session_expires_at FROM sessions s JOIN users u ON u.id = s.user_id JOIN memberships m ON m.user_id = u.id WHERE s.token_hash = ? """, (token_hash,), ).fetchone() if row is None: return None return SessionRecord( token_hash=str(row["session_token_hash"]), csrf_hash=str(row["session_csrf_hash"]), user=_user(row), membership=_membership(row, "membership_"), expires_at=_datetime(str(row["session_expires_at"])), ) @staticmethod def delete_session(connection: sqlite3.Connection, token_hash: str) -> None: connection.execute("DELETE FROM sessions WHERE token_hash = ?", (token_hash,)) @staticmethod def delete_other_sessions( connection: sqlite3.Connection, user_id: int, token_hash: str ) -> None: connection.execute( "DELETE FROM sessions WHERE user_id = ? AND token_hash <> ?", (user_id, token_hash), ) @staticmethod def get_encrypted_profile(connection: sqlite3.Connection, user_id: int) -> sqlite3.Row | None: return connection.execute( "SELECT encrypted_payload, updated_at FROM birth_profiles WHERE user_id = ?", (user_id,), ).fetchone() @staticmethod def save_encrypted_profile( connection: sqlite3.Connection, user_id: int, encrypted_payload: str, now: datetime, ) -> None: timestamp = now.isoformat(timespec="seconds") connection.execute( """ INSERT INTO birth_profiles (user_id, encrypted_payload, created_at, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(user_id) DO UPDATE SET encrypted_payload = excluded.encrypted_payload, updated_at = excluded.updated_at """, (user_id, encrypted_payload, timestamp, timestamp), ) @staticmethod def delete_profile(connection: sqlite3.Connection, user_id: int) -> None: connection.execute("DELETE FROM birth_profiles WHERE user_id = ?", (user_id,)) @staticmethod def usage_today(connection: sqlite3.Connection, user_id: int, usage_date: str) -> int: row = connection.execute( """ SELECT successful_calls FROM llm_usage_daily WHERE user_id = ? AND usage_date = ? """, (user_id, usage_date), ).fetchone() return int(row["successful_calls"]) if row else 0 @staticmethod def usage_for_users(connection: sqlite3.Connection, usage_date: str) -> dict[int, int]: return { int(row["user_id"]): int(row["successful_calls"]) for row in connection.execute( "SELECT user_id, successful_calls FROM llm_usage_daily WHERE usage_date = ?", (usage_date,), ) } @staticmethod def list_memberships(connection: sqlite3.Connection) -> tuple[MembershipAccount, ...]: rows = connection.execute( """ SELECT u.*, m.user_id AS membership_user_id, m.state AS membership_state, m.expires_at AS membership_expires_at, m.is_permanent AS membership_is_permanent, m.daily_llm_limit AS membership_daily_llm_limit, m.updated_at AS membership_updated_at, m.updated_by AS membership_updated_by FROM users u JOIN memberships m ON m.user_id = u.id ORDER BY u.id """ ).fetchall() return tuple(MembershipAccount(_user(row), _membership(row, "membership_")) for row in rows) @staticmethod def get_membership_account( connection: sqlite3.Connection, user_id: int ) -> MembershipAccount | None: row = connection.execute( """ SELECT u.*, m.user_id AS membership_user_id, m.state AS membership_state, m.expires_at AS membership_expires_at, m.is_permanent AS membership_is_permanent, m.daily_llm_limit AS membership_daily_llm_limit, m.updated_at AS membership_updated_at, m.updated_by AS membership_updated_by FROM users u JOIN memberships m ON m.user_id = u.id WHERE u.id = ? """, (user_id,), ).fetchone() return MembershipAccount(_user(row), _membership(row, "membership_")) if row else None @staticmethod def update_membership( connection: sqlite3.Connection, user_id: int, state: str, expires_at: datetime | None, is_permanent: bool, daily_limit: int, updated_by: int, now: datetime, ) -> MembershipRecord | None: connection.execute( """ UPDATE memberships SET state = ?, expires_at = ?, is_permanent = ?, daily_llm_limit = ?, updated_at = ?, updated_by = ? WHERE user_id = ? """, ( state, expires_at.isoformat(timespec="seconds") if expires_at else None, int(is_permanent), daily_limit, now.isoformat(timespec="seconds"), updated_by, user_id, ), ) return AccountRepository.get_membership(connection, user_id) class SystemCredentialRepository: @staticmethod def list_status(connection: sqlite3.Connection) -> dict[str, str]: return { str(row["name"]): str(row["updated_at"]) for row in connection.execute("SELECT name, updated_at FROM system_credentials") } @staticmethod def save( connection: sqlite3.Connection, name: str, encrypted_value: str, updated_by: int, now: datetime, ) -> None: connection.execute( """ INSERT INTO system_credentials (name, encrypted_value, updated_at, updated_by) VALUES (?, ?, ?, ?) ON CONFLICT(name) DO UPDATE SET encrypted_value = excluded.encrypted_value, updated_at = excluded.updated_at, updated_by = excluded.updated_by """, (name, encrypted_value, now.isoformat(timespec="seconds"), updated_by), ) @staticmethod def get_encrypted(connection: sqlite3.Connection, name: str) -> str | None: row = connection.execute( "SELECT encrypted_value FROM system_credentials WHERE name = ?", (name,) ).fetchone() return str(row["encrypted_value"]) if row else None