style: apply latest black formatting for ci
This commit is contained in:
1 parent
48783387ac
commit
8ee73e6572
9 files changed
+123
-244
No files matched your search
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user