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