style: apply latest black formatting for ci

This commit is contained in:
胡绍康 committed 2026-03-09 13:46:45 +08:00
1 parent 48783387ac
commit 8ee73e6572
9 files changed
+123 -244

No files matched your search

+8 -16
View File
@@ -1803,8 +1803,7 @@ def api_get_failed_refresh_logs() -> Any:
db = get_db() db = get_db()
# 获取每个账号最近一次失败的刷新记录 # 获取每个账号最近一次失败的刷新记录
cursor = db.execute( cursor = db.execute("""
"""
SELECT l.*, a.email as account_email, a.status as account_status SELECT l.*, a.email as account_email, a.status as account_status
FROM account_refresh_logs l FROM account_refresh_logs l
INNER JOIN ( INNER JOIN (
@@ -1815,8 +1814,7 @@ def api_get_failed_refresh_logs() -> Any:
LEFT JOIN accounts a ON l.account_id = a.id LEFT JOIN accounts a ON l.account_id = a.id
WHERE l.status = 'failed' WHERE l.status = 'failed'
ORDER BY l.created_at DESC ORDER BY l.created_at DESC
""" """)
)
logs = [] logs = []
for row in cursor.fetchall(): for row in cursor.fetchall():
@@ -1841,27 +1839,22 @@ def api_get_refresh_stats() -> Any:
"""获取刷新统计信息(统计当前失败状态的邮箱数量)""" """获取刷新统计信息(统计当前失败状态的邮箱数量)"""
db = get_db() db = get_db()
cursor = db.execute( cursor = db.execute("""
"""
SELECT MAX(created_at) as last_refresh_time SELECT MAX(created_at) as last_refresh_time
FROM account_refresh_logs FROM account_refresh_logs
WHERE refresh_type IN ('manual', 'manual_all', 'scheduled', 'retry') WHERE refresh_type IN ('manual', 'manual_all', 'scheduled', 'retry')
""" """)
)
row = cursor.fetchone() row = cursor.fetchone()
last_refresh_time = row["last_refresh_time"] if row else None last_refresh_time = row["last_refresh_time"] if row else None
cursor = db.execute( cursor = db.execute("""
"""
SELECT COUNT(*) as total_accounts SELECT COUNT(*) as total_accounts
FROM accounts FROM accounts
WHERE status = 'active' WHERE status = 'active'
""" """)
)
total_accounts = cursor.fetchone()["total_accounts"] total_accounts = cursor.fetchone()["total_accounts"]
cursor = db.execute( cursor = db.execute("""
"""
SELECT COUNT(DISTINCT l.account_id) as failed_count SELECT COUNT(DISTINCT l.account_id) as failed_count
FROM account_refresh_logs l FROM account_refresh_logs l
INNER JOIN ( INNER JOIN (
@@ -1871,8 +1864,7 @@ def api_get_refresh_stats() -> Any:
) latest ON l.account_id = latest.account_id AND l.created_at = latest.last_refresh ) latest ON l.account_id = latest.account_id AND l.created_at = latest.last_refresh
INNER JOIN accounts a ON l.account_id = a.id INNER JOIN accounts a ON l.account_id = a.id
WHERE l.status = 'failed' AND a.status = 'active' WHERE l.status = 'failed' AND a.status = 'active'
""" """)
)
failed_count = cursor.fetchone()["failed_count"] failed_count = cursor.fetchone()["failed_count"]
return jsonify( return jsonify(
+8 -16
View File
@@ -35,13 +35,11 @@ def api_get_scheduler_status() -> Any:
refresh_cron = settings_repo.get_setting("refresh_cron", "0 2 * * *") refresh_cron = settings_repo.get_setting("refresh_cron", "0 2 * * *")
# 心跳 # 心跳
heartbeat_row = conn.execute( heartbeat_row = conn.execute("""
"""
SELECT value, updated_at SELECT value, updated_at
FROM settings FROM settings
WHERE key = 'scheduler_heartbeat' WHERE key = 'scheduler_heartbeat'
""" """).fetchone()
).fetchone()
heartbeat = None heartbeat = None
heartbeat_age_seconds = None heartbeat_age_seconds = None
@@ -78,28 +76,24 @@ def api_get_scheduler_status() -> Any:
lock_info = {"locked": False} lock_info = {"locked": False}
# 最近一次定时刷新(含手动触发 scheduled_manual) # 最近一次定时刷新(含手动触发 scheduled_manual)
last_scheduled_run = conn.execute( last_scheduled_run = conn.execute("""
"""
SELECT id, trigger_source, status, started_at, finished_at, SELECT id, trigger_source, status, started_at, finished_at,
total, success_count, failed_count, message, trace_id, requested_by_ip total, success_count, failed_count, message, trace_id, requested_by_ip
FROM refresh_runs FROM refresh_runs
WHERE trigger_source IN ('scheduled', 'scheduled_manual') WHERE trigger_source IN ('scheduled', 'scheduled_manual')
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
last_scheduled = dict(last_scheduled_run) if last_scheduled_run else None last_scheduled = dict(last_scheduled_run) if last_scheduled_run else None
running_run = conn.execute( running_run = conn.execute("""
"""
SELECT id, trigger_source, status, started_at, total, success_count, failed_count, trace_id SELECT id, trigger_source, status, started_at, total, success_count, failed_count, trace_id
FROM refresh_runs FROM refresh_runs
WHERE status = 'running' WHERE status = 'running'
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
running = dict(running_run) if running_run else None running = dict(running_run) if running_run else None
@@ -119,8 +113,7 @@ def api_get_scheduler_status() -> Any:
future_runs = [] future_runs = []
else: else:
# 按天数策略:基于最近一次已完成的 scheduled/scheduled_manual 计算 next_due # 按天数策略:基于最近一次已完成的 scheduled/scheduled_manual 计算 next_due
row = conn.execute( row = conn.execute("""
"""
SELECT finished_at SELECT finished_at
FROM refresh_runs FROM refresh_runs
WHERE trigger_source IN ('scheduled', 'scheduled_manual') WHERE trigger_source IN ('scheduled', 'scheduled_manual')
@@ -128,8 +121,7 @@ def api_get_scheduler_status() -> Any:
AND finished_at IS NOT NULL AND finished_at IS NOT NULL
ORDER BY finished_at DESC ORDER BY finished_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
last_finished_at = row["finished_at"] if row else None last_finished_at = row["finished_at"] if row else None
try: try:
last_time = datetime.fromisoformat(last_finished_at) if last_finished_at else None last_time = datetime.fromisoformat(last_finished_at) if last_finished_at else None
+14 -28
View File
@@ -47,13 +47,11 @@ def api_system_health() -> Any:
db_ok = False db_ok = False
# Scheduler 心跳 # Scheduler 心跳
heartbeat_row = conn.execute( heartbeat_row = conn.execute("""
"""
SELECT updated_at SELECT updated_at
FROM settings FROM settings
WHERE key = 'scheduler_heartbeat' WHERE key = 'scheduler_heartbeat'
""" """).fetchone()
).fetchone()
heartbeat_age_seconds = None heartbeat_age_seconds = None
if heartbeat_row and heartbeat_row["updated_at"]: if heartbeat_row and heartbeat_row["updated_at"]:
@@ -78,15 +76,13 @@ def api_system_health() -> Any:
).fetchone() ).fetchone()
locked = bool(lock_row and lock_row["expires_at"] and lock_row["expires_at"] > time.time()) locked = bool(lock_row and lock_row["expires_at"] and lock_row["expires_at"] > time.time())
running_run = conn.execute( running_run = conn.execute("""
"""
SELECT id, trigger_source, started_at, trace_id SELECT id, trigger_source, started_at, trace_id
FROM refresh_runs FROM refresh_runs
WHERE status = 'running' WHERE status = 'running'
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
return jsonify( return jsonify(
{ {
@@ -137,32 +133,26 @@ def api_system_diagnostics() -> Any:
(now_ts,), (now_ts,),
).fetchone()["c"] ).fetchone()["c"]
running_runs = conn.execute( running_runs = conn.execute("""
"""
SELECT id, trigger_source, started_at, trace_id SELECT id, trigger_source, started_at, trace_id
FROM refresh_runs FROM refresh_runs
WHERE status = 'running' WHERE status = 'running'
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 5 LIMIT 5
""" """).fetchall()
).fetchall()
last_runs = conn.execute( last_runs = conn.execute("""
"""
SELECT id, trigger_source, status, started_at, finished_at, total, success_count, failed_count, trace_id SELECT id, trigger_source, status, started_at, finished_at, total, success_count, failed_count, trace_id
FROM refresh_runs FROM refresh_runs
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 10 LIMIT 10
""" """).fetchall()
).fetchall()
locks = conn.execute( locks = conn.execute("""
"""
SELECT name, owner_id, acquired_at, expires_at SELECT name, owner_id, acquired_at, expires_at
FROM distributed_locks FROM distributed_locks
ORDER BY name ASC ORDER BY name ASC
""" """).fetchall()
).fetchall()
# 数据库升级状态(可验证) # 数据库升级状态(可验证)
schema_version_row = conn.execute( schema_version_row = conn.execute(
@@ -172,14 +162,12 @@ def api_system_diagnostics() -> Any:
last_migration = None last_migration = None
try: try:
mig = conn.execute( mig = conn.execute("""
"""
SELECT id, from_version, to_version, status, started_at, finished_at, error, trace_id SELECT id, from_version, to_version, status, started_at, finished_at, error, trace_id
FROM schema_migrations FROM schema_migrations
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
last_migration = dict(mig) if mig else None last_migration = dict(mig) if mig else None
except Exception: except Exception:
last_migration = None last_migration = None
@@ -225,14 +213,12 @@ def api_system_upgrade_status() -> Any:
last_migration = None last_migration = None
try: try:
mig = conn.execute( mig = conn.execute("""
"""
SELECT id, from_version, to_version, status, started_at, finished_at, error, trace_id SELECT id, from_version, to_version, status, started_at, finished_at, error, trace_id
FROM schema_migrations FROM schema_migrations
ORDER BY started_at DESC ORDER BY started_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
last_migration = dict(mig) if mig else None last_migration = dict(mig) if mig else None
except Exception: except Exception:
last_migration = None last_migration = None
+78 -156
View File
@@ -92,19 +92,16 @@ def init_db(database_path: Optional[str] = None):
cursor.execute("BEGIN IMMEDIATE") cursor.execute("BEGIN IMMEDIATE")
# 创建设置表 # 创建设置表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS settings ( CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY, key TEXT PRIMARY KEY,
value TEXT, value TEXT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """)
)
# 数据库迁移记录(用于升级可验证/可诊断) # 数据库迁移记录(用于升级可验证/可诊断)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS schema_migrations ( CREATE TABLE IF NOT EXISTS schema_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
from_version INTEGER NOT NULL, from_version INTEGER NOT NULL,
@@ -115,14 +112,11 @@ def init_db(database_path: Optional[str] = None):
error TEXT, error TEXT,
trace_id TEXT trace_id TEXT
) )
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_schema_migrations_started_at CREATE INDEX IF NOT EXISTS idx_schema_migrations_started_at
ON schema_migrations(started_at) ON schema_migrations(started_at)
""" """)
)
# 在锁内读取当前 schema 版本(保证一致性) # 在锁内读取当前 schema 版本(保证一致性)
row = cursor.execute("SELECT value FROM settings WHERE key = ?", (DB_SCHEMA_VERSION_KEY,)).fetchone() row = cursor.execute("SELECT value FROM settings WHERE key = ?", (DB_SCHEMA_VERSION_KEY,)).fetchone()
@@ -155,8 +149,7 @@ def init_db(database_path: Optional[str] = None):
# -------------------- Schema 创建/迁移(幂等) -------------------- # -------------------- Schema 创建/迁移(幂等) --------------------
# 分组表 # 分组表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS groups ( CREATE TABLE IF NOT EXISTS groups (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
@@ -166,12 +159,10 @@ def init_db(database_path: Optional[str] = None):
is_system INTEGER DEFAULT 0, is_system INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """)
)
# 邮箱账号表 # 邮箱账号表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS accounts ( CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL,
@@ -191,12 +182,10 @@ def init_db(database_path: Optional[str] = None):
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (group_id) REFERENCES groups (id) FOREIGN KEY (group_id) REFERENCES groups (id)
) )
""" """)
)
# 临时邮箱表 # 临时邮箱表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS temp_emails ( CREATE TABLE IF NOT EXISTS temp_emails (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL,
@@ -204,12 +193,10 @@ def init_db(database_path: Optional[str] = None):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """)
)
# 临时邮件表(存储从 GPTMail 获取的邮件) # 临时邮件表(存储从 GPTMail 获取的邮件)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS temp_email_messages ( CREATE TABLE IF NOT EXISTS temp_email_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id TEXT UNIQUE NOT NULL, message_id TEXT UNIQUE NOT NULL,
@@ -224,12 +211,10 @@ def init_db(database_path: Optional[str] = None):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (email_address) REFERENCES temp_emails (email) FOREIGN KEY (email_address) REFERENCES temp_emails (email)
) )
""" """)
)
# 刷新记录表(账号级) # 刷新记录表(账号级)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS account_refresh_logs ( CREATE TABLE IF NOT EXISTS account_refresh_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL, account_id INTEGER NOT NULL,
@@ -241,18 +226,14 @@ def init_db(database_path: Optional[str] = None):
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE
) )
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_run_id CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_run_id
ON account_refresh_logs(run_id) ON account_refresh_logs(run_id)
""" """)
)
# 审计日志表 # 审计日志表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS audit_logs ( CREATE TABLE IF NOT EXISTS audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT NOT NULL, action TEXT NOT NULL,
@@ -263,36 +244,28 @@ def init_db(database_path: Optional[str] = None):
trace_id TEXT, trace_id TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_audit_logs_trace_id CREATE INDEX IF NOT EXISTS idx_audit_logs_trace_id
ON audit_logs(trace_id) ON audit_logs(trace_id)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at
ON audit_logs(created_at) ON audit_logs(created_at)
""" """)
)
# 标签表 # 标签表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS tags ( CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL, name TEXT UNIQUE NOT NULL,
color TEXT NOT NULL, color TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
""" """)
)
# 账号标签关联表 # 账号标签关联表
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS account_tags ( CREATE TABLE IF NOT EXISTS account_tags (
account_id INTEGER NOT NULL, account_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL, tag_id INTEGER NOT NULL,
@@ -301,24 +274,20 @@ def init_db(database_path: Optional[str] = None):
FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE, FOREIGN KEY (account_id) REFERENCES accounts (id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE FOREIGN KEY (tag_id) REFERENCES tags (id) ON DELETE CASCADE
) )
""" """)
)
# 分布式锁(用于刷新冲突控制/多进程一致性) # 分布式锁(用于刷新冲突控制/多进程一致性)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS distributed_locks ( CREATE TABLE IF NOT EXISTS distributed_locks (
name TEXT PRIMARY KEY, name TEXT PRIMARY KEY,
owner_id TEXT NOT NULL, owner_id TEXT NOT NULL,
acquired_at REAL NOT NULL, acquired_at REAL NOT NULL,
expires_at REAL NOT NULL expires_at REAL NOT NULL
) )
""" """)
)
# 导出二次验证 Token(持久化,支持重启/多进程) # 导出二次验证 Token(持久化,支持重启/多进程)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS export_verify_tokens ( CREATE TABLE IF NOT EXISTS export_verify_tokens (
token TEXT PRIMARY KEY, token TEXT PRIMARY KEY,
ip TEXT, ip TEXT,
@@ -326,24 +295,20 @@ def init_db(database_path: Optional[str] = None):
expires_at REAL NOT NULL, expires_at REAL NOT NULL,
created_at REAL NOT NULL created_at REAL NOT NULL
) )
""" """)
)
# 登录速率限制(持久化,支持重启/多进程) # 登录速率限制(持久化,支持重启/多进程)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS login_attempts ( CREATE TABLE IF NOT EXISTS login_attempts (
ip TEXT PRIMARY KEY, ip TEXT PRIMARY KEY,
count INTEGER NOT NULL, count INTEGER NOT NULL,
last_attempt_at REAL NOT NULL, last_attempt_at REAL NOT NULL,
locked_until_at REAL locked_until_at REAL
) )
""" """)
)
# 刷新运行记录(用于“最近触发/来源/统计/运行中状态”的可验证性) # 刷新运行记录(用于“最近触发/来源/统计/运行中状态”的可验证性)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS refresh_runs ( CREATE TABLE IF NOT EXISTS refresh_runs (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
trigger_source TEXT NOT NULL, trigger_source TEXT NOT NULL,
@@ -358,20 +323,15 @@ def init_db(database_path: Optional[str] = None):
message TEXT, message TEXT,
trace_id TEXT trace_id TEXT
) )
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_refresh_runs_started_at CREATE INDEX IF NOT EXISTS idx_refresh_runs_started_at
ON refresh_runs(started_at) ON refresh_runs(started_at)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_refresh_runs_trigger_source CREATE INDEX IF NOT EXISTS idx_refresh_runs_trigger_source
ON refresh_runs(trigger_source) ON refresh_runs(trigger_source)
""" """)
)
# 兼容旧 schema:补齐缺失列 # 兼容旧 schema:补齐缺失列
cursor.execute("PRAGMA table_info(accounts)") cursor.execute("PRAGMA table_info(accounts)")
@@ -420,20 +380,16 @@ def init_db(database_path: Optional[str] = None):
cursor.execute("ALTER TABLE audit_logs ADD COLUMN trace_id TEXT") cursor.execute("ALTER TABLE audit_logs ADD COLUMN trace_id TEXT")
# 默认分组 # 默认分组
cursor.execute( cursor.execute("""
"""
INSERT OR IGNORE INTO groups (name, description, color) INSERT OR IGNORE INTO groups (name, description, color)
VALUES ('默认分组', '未分组的邮箱', '#666666') VALUES ('默认分组', '未分组的邮箱', '#666666')
""" """)
)
# 临时邮箱分组(系统分组) # 临时邮箱分组(系统分组)
cursor.execute( cursor.execute("""
"""
INSERT OR IGNORE INTO groups (name, description, color, is_system) INSERT OR IGNORE INTO groups (name, description, color, is_system)
VALUES ('临时邮箱', 'GPTMail 临时邮箱服务', '#00bcf2', 1) VALUES ('临时邮箱', 'GPTMail 临时邮箱服务', '#00bcf2', 1)
""" """)
)
# 初始化默认设置:登录密码(自动迁移明文 -> 哈希) # 初始化默认设置:登录密码(自动迁移明文 -> 哈希)
cursor.execute("SELECT value FROM settings WHERE key = 'login_password'") cursor.execute("SELECT value FROM settings WHERE key = 'login_password'")
@@ -467,98 +423,69 @@ def init_db(database_path: Optional[str] = None):
) )
# 初始化刷新配置 # 初始化刷新配置
cursor.execute( cursor.execute("""
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('refresh_interval_days', '30') VALUES ('refresh_interval_days', '30')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('refresh_delay_seconds', '5') VALUES ('refresh_delay_seconds', '5')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('refresh_cron', '0 2 * * *') VALUES ('refresh_cron', '0 2 * * *')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('use_cron_schedule', 'false') VALUES ('use_cron_schedule', 'false')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('enable_scheduled_refresh', 'true') VALUES ('enable_scheduled_refresh', 'true')
""" """)
)
# 初始化轮询配置 # 初始化轮询配置
cursor.execute( cursor.execute("""
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('enable_auto_polling', 'false') VALUES ('enable_auto_polling', 'false')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('polling_interval', '10') VALUES ('polling_interval', '10')
""" """)
) cursor.execute("""
cursor.execute(
"""
INSERT OR IGNORE INTO settings (key, value) INSERT OR IGNORE INTO settings (key, value)
VALUES ('polling_count', '5') VALUES ('polling_count', '5')
""" """)
)
# 索引(性能基线) # 索引(性能基线)
cursor.execute( cursor.execute("""
"""
CREATE INDEX IF NOT EXISTS idx_accounts_last_refresh_at CREATE INDEX IF NOT EXISTS idx_accounts_last_refresh_at
ON accounts(last_refresh_at) ON accounts(last_refresh_at)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_accounts_status CREATE INDEX IF NOT EXISTS idx_accounts_status
ON accounts(status) ON accounts(status)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_accounts_group_id CREATE INDEX IF NOT EXISTS idx_accounts_group_id
ON accounts(group_id) ON accounts(group_id)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_account_id CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_account_id
ON account_refresh_logs(account_id) ON account_refresh_logs(account_id)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_account_id_id CREATE INDEX IF NOT EXISTS idx_account_refresh_logs_account_id_id
ON account_refresh_logs(account_id, id) ON account_refresh_logs(account_id, id)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_account_tags_tag_id CREATE INDEX IF NOT EXISTS idx_account_tags_tag_id
ON account_tags(tag_id) ON account_tags(tag_id)
""" """)
)
# v5: Telegram 推送去重日志(BUG-00011 P2) # v5: Telegram 推送去重日志(BUG-00011 P2)
cursor.execute( cursor.execute("""
"""
CREATE TABLE IF NOT EXISTS telegram_push_log ( CREATE TABLE IF NOT EXISTS telegram_push_log (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
account_id INTEGER NOT NULL, account_id INTEGER NOT NULL,
@@ -566,20 +493,15 @@ def init_db(database_path: Optional[str] = None):
pushed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')), pushed_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now')),
UNIQUE(account_id, message_id) UNIQUE(account_id, message_id)
) )
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_telegram_push_log_account_id CREATE INDEX IF NOT EXISTS idx_telegram_push_log_account_id
ON telegram_push_log(account_id) ON telegram_push_log(account_id)
""" """)
) cursor.execute("""
cursor.execute(
"""
CREATE INDEX IF NOT EXISTS idx_telegram_push_log_pushed_at CREATE INDEX IF NOT EXISTS idx_telegram_push_log_pushed_at
ON telegram_push_log(pushed_at) ON telegram_push_log(pushed_at)
""" """)
)
# 迁移现有明文数据为加密数据 # 迁移现有明文数据为加密数据
migrate_sensitive_data(conn) migrate_sensitive_data(conn)
+4 -8
View File
@@ -22,14 +22,12 @@ def load_accounts(group_id: int = None) -> List[Dict]:
(group_id,), (group_id,),
) )
else: else:
cursor = db.execute( cursor = db.execute("""
"""
SELECT a.*, g.name as group_name, g.color as group_color SELECT a.*, g.name as group_name, g.color as group_color
FROM accounts a FROM accounts a
LEFT JOIN groups g ON a.group_id = g.id LEFT JOIN groups g ON a.group_id = g.id
ORDER BY a.created_at DESC ORDER BY a.created_at DESC
""" """)
)
rows = cursor.fetchall() rows = cursor.fetchall()
tags_by_account: Dict[int, List[Dict[str, Any]]] = {} tags_by_account: Dict[int, List[Dict[str, Any]]] = {}
@@ -417,13 +415,11 @@ def update_telegram_cursor(account_id: int, checked_at: str) -> None:
def get_telegram_push_accounts() -> List[Dict]: def get_telegram_push_accounts() -> List[Dict]:
"""返回所有 telegram_push_enabled=1 且非 disabled 状态的账号。""" """返回所有 telegram_push_enabled=1 且非 disabled 状态的账号。"""
db = get_db() db = get_db()
rows = db.execute( rows = db.execute("""SELECT a.id, a.email, a.provider, a.client_id, a.refresh_token,
"""SELECT a.id, a.email, a.provider, a.client_id, a.refresh_token,
a.imap_host, a.imap_port, a.imap_password, a.imap_host, a.imap_port, a.imap_password,
a.telegram_last_checked_at, a.group_id, a.telegram_last_checked_at, a.group_id,
g.proxy_url g.proxy_url
FROM accounts a FROM accounts a
LEFT JOIN groups g ON a.group_id = g.id LEFT JOIN groups g ON a.group_id = g.id
WHERE a.telegram_push_enabled = 1 AND a.status != 'disabled'""" WHERE a.telegram_push_enabled = 1 AND a.status != 'disabled'""").fetchall()
).fetchall()
return [dict(r) for r in rows] return [dict(r) for r in rows]
+2 -4
View File
@@ -9,14 +9,12 @@ from outlook_web.db import get_db
def load_groups() -> List[Dict]: def load_groups() -> List[Dict]:
"""加载所有分组(临时邮箱分组排在最前面)""" """加载所有分组(临时邮箱分组排在最前面)"""
db = get_db() db = get_db()
cursor = db.execute( cursor = db.execute("""
"""
SELECT * FROM groups SELECT * FROM groups
ORDER BY ORDER BY
CASE WHEN name = '临时邮箱' THEN 0 ELSE 1 END, CASE WHEN name = '临时邮箱' THEN 0 ELSE 1 END,
id id
""" """)
)
rows = cursor.fetchall() rows = cursor.fetchall()
return [dict(row) for row in rows] return [dict(row) for row in rows]
+4 -8
View File
@@ -316,8 +316,7 @@ def stream_trigger_scheduled_refresh(
) )
if (not force) and (not use_cron): if (not force) and (not use_cron):
row = conn.execute( row = conn.execute("""
"""
SELECT finished_at SELECT finished_at
FROM refresh_runs FROM refresh_runs
WHERE trigger_source IN ('scheduled', 'scheduled_manual') WHERE trigger_source IN ('scheduled', 'scheduled_manual')
@@ -325,8 +324,7 @@ def stream_trigger_scheduled_refresh(
AND finished_at IS NOT NULL AND finished_at IS NOT NULL
ORDER BY finished_at DESC ORDER BY finished_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
if row and row["finished_at"]: if row and row["finished_at"]:
try: try:
@@ -564,8 +562,7 @@ def refresh_failed_accounts(
"""重试所有失败的账号(非流式)""" """重试所有失败的账号(非流式)"""
lock_owner_id = uuid.uuid4().hex lock_owner_id = uuid.uuid4().hex
cursor = db.execute( cursor = db.execute("""
"""
SELECT DISTINCT a.id, a.email, a.client_id, a.refresh_token, a.group_id SELECT DISTINCT a.id, a.email, a.client_id, a.refresh_token, a.group_id
FROM accounts a FROM accounts a
INNER JOIN ( INNER JOIN (
@@ -575,8 +572,7 @@ def refresh_failed_accounts(
) latest ON a.id = latest.account_id ) latest ON a.id = latest.account_id
INNER JOIN account_refresh_logs l ON a.id = l.account_id AND l.created_at = latest.last_refresh INNER JOIN account_refresh_logs l ON a.id = l.account_id AND l.created_at = latest.last_refresh
WHERE l.status = 'failed' AND a.status = 'active' WHERE l.status = 'failed' AND a.status = 'active'
""" """)
)
accounts = cursor.fetchall() accounts = cursor.fetchall()
total = len(accounts) total = len(accounts)
+4 -8
View File
@@ -233,8 +233,7 @@ def scheduled_refresh_task(app, test_refresh_token):
# 按天数策略:未到周期则跳过(不产生账号级刷新日志) # 按天数策略:未到周期则跳过(不产生账号级刷新日志)
if not use_cron: if not use_cron:
row = conn.execute( row = conn.execute("""
"""
SELECT finished_at SELECT finished_at
FROM refresh_runs FROM refresh_runs
WHERE trigger_source = 'scheduled' WHERE trigger_source = 'scheduled'
@@ -242,8 +241,7 @@ def scheduled_refresh_task(app, test_refresh_token):
AND finished_at IS NOT NULL AND finished_at IS NOT NULL
ORDER BY finished_at DESC ORDER BY finished_at DESC
LIMIT 1 LIMIT 1
""" """).fetchone()
).fetchone()
if row and row["finished_at"]: if row and row["finished_at"]:
try: try:
@@ -278,14 +276,12 @@ def scheduled_refresh_task(app, test_refresh_token):
pass pass
# PRD-00005 / TDD-00005:定时刷新只处理 Outlook 账号(IMAP 账号无 OAuth token 刷新语义) # PRD-00005 / TDD-00005:定时刷新只处理 Outlook 账号(IMAP 账号无 OAuth token 刷新语义)
accounts = conn.execute( accounts = conn.execute("""
"""
SELECT id, email, client_id, refresh_token, group_id SELECT id, email, client_id, refresh_token, group_id
FROM accounts FROM accounts
WHERE status = 'active' WHERE status = 'active'
AND (account_type = 'outlook' OR account_type IS NULL) AND (account_type = 'outlook' OR account_type IS NULL)
""" """).fetchall()
).fetchall()
total = len(accounts) total = len(accounts)
# 更新 run_id 的 total # 更新 run_id 的 total
+1
View File
@@ -3,6 +3,7 @@
""" """
启动脚本 - 智能初始化环境变量并启动 Flask 应用 启动脚本 - 智能初始化环境变量并启动 Flask 应用
""" """
import os import os
import secrets import secrets
import shutil import shutil