Files
storyforge/collector-service/app/database.py
2026-03-14 21:32:55 +08:00

182 lines
6.7 KiB
Python

from __future__ import annotations
import sqlite3
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Iterator
def utc_now() -> str:
from datetime import datetime, timezone
return datetime.now(timezone.utc).replace(microsecond=0).isoformat()
def dict_factory(cursor: sqlite3.Cursor, row: sqlite3.Row) -> dict[str, Any]:
return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
class Database:
def __init__(self, path: str) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
def connect(self) -> sqlite3.Connection:
conn = sqlite3.connect(self.path)
conn.row_factory = dict_factory
conn.execute("PRAGMA foreign_keys = ON")
return conn
@contextmanager
def session(self) -> Iterator[sqlite3.Connection]:
conn = self.connect()
try:
yield conn
conn.commit()
finally:
conn.close()
def fetch_one(self, sql: str, params: tuple[Any, ...] = ()) -> dict[str, Any] | None:
with self.session() as conn:
return conn.execute(sql, params).fetchone()
def fetch_all(self, sql: str, params: tuple[Any, ...] = ()) -> list[dict[str, Any]]:
with self.session() as conn:
return list(conn.execute(sql, params).fetchall())
def execute(self, sql: str, params: tuple[Any, ...] = ()) -> None:
with self.session() as conn:
conn.execute(sql, params)
def init_schema(self) -> None:
schema = """
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY,
username TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
password_salt TEXT NOT NULL,
display_name TEXT NOT NULL,
role TEXT NOT NULL,
approval_status TEXT NOT NULL,
approved_by TEXT,
approved_at TEXT,
preferred_analysis_model_id TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS auth_tokens (
token TEXT PRIMARY KEY,
account_id TEXT NOT NULL,
created_at TEXT NOT NULL,
FOREIGN KEY(account_id) REFERENCES accounts(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS model_profiles (
id TEXT PRIMARY KEY,
owner_account_id TEXT,
name TEXT NOT NULL,
provider TEXT NOT NULL,
base_url TEXT NOT NULL,
api_key TEXT NOT NULL DEFAULT '',
model_name TEXT NOT NULL,
is_system INTEGER NOT NULL DEFAULT 0,
is_default INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(owner_account_id) REFERENCES accounts(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS knowledge_bases (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
fastgpt_dataset_id TEXT,
sync_status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS knowledge_documents (
id TEXT PRIMARY KEY,
knowledge_base_id TEXT NOT NULL,
title TEXT NOT NULL,
source_type TEXT NOT NULL,
source_url TEXT NOT NULL DEFAULT '',
transcript_text TEXT NOT NULL DEFAULT '',
style_summary TEXT NOT NULL DEFAULT '',
combined_text TEXT NOT NULL DEFAULT '',
fastgpt_collection_id TEXT NOT NULL DEFAULT '',
analysis_model_profile_id TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(knowledge_base_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS assistants (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
system_prompt TEXT NOT NULL DEFAULT '',
generation_goal TEXT NOT NULL DEFAULT '',
fastgpt_app_key TEXT NOT NULL DEFAULT '',
model_profile_id TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS assistant_knowledge_bases (
assistant_id TEXT NOT NULL,
knowledge_base_id TEXT NOT NULL,
PRIMARY KEY (assistant_id, knowledge_base_id),
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE CASCADE,
FOREIGN KEY(knowledge_base_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
assistant_id TEXT,
knowledge_base_id TEXT NOT NULL,
source_type TEXT NOT NULL,
source_url TEXT,
title TEXT NOT NULL,
language TEXT NOT NULL DEFAULT 'auto',
status TEXT NOT NULL,
transcript_text TEXT NOT NULL DEFAULT '',
style_summary TEXT NOT NULL DEFAULT '',
fastgpt_collection_id TEXT NOT NULL DEFAULT '',
upload_status TEXT NOT NULL DEFAULT 'pending',
error TEXT NOT NULL DEFAULT '',
artifacts_json TEXT NOT NULL DEFAULT '{}',
analysis_model_profile_id TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(user_id) REFERENCES accounts(id) ON DELETE CASCADE,
FOREIGN KEY(assistant_id) REFERENCES assistants(id) ON DELETE SET NULL,
FOREIGN KEY(knowledge_base_id) REFERENCES knowledge_bases(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS app_updates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
platform TEXT NOT NULL,
channel TEXT NOT NULL,
version_code INTEGER NOT NULL,
version_name TEXT NOT NULL,
min_supported_code INTEGER NOT NULL,
apk_url TEXT NOT NULL,
apk_sha256 TEXT NOT NULL DEFAULT '',
notes TEXT NOT NULL DEFAULT '',
force_update INTEGER NOT NULL DEFAULT 0,
is_active INTEGER NOT NULL DEFAULT 1,
published_at INTEGER NOT NULL,
created_by TEXT NOT NULL
);
"""
with self.session() as conn:
conn.executescript(schema)