fix: auto-fix code issues (cron)
- 修复重复导入/字段 - 修复异常处理 - 修复PEP8格式问题 - 修复语法错误(运算符空格问题) - 修复类型注解格式
This commit is contained in:
@@ -13,13 +13,13 @@ from dataclasses import dataclass
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
|
||||
DB_PATH = os.getenv("DB_PATH", "/app/data/insightflow.db")
|
||||
DB_PATH = os.getenv("DB_PATH", "/app/data/insightflow.db")
|
||||
|
||||
|
||||
class ApiKeyStatus(Enum):
|
||||
ACTIVE = "active"
|
||||
REVOKED = "revoked"
|
||||
EXPIRED = "expired"
|
||||
ACTIVE = "active"
|
||||
REVOKED = "revoked"
|
||||
EXPIRED = "expired"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -37,18 +37,18 @@ class ApiKey:
|
||||
last_used_at: str | None
|
||||
revoked_at: str | None
|
||||
revoked_reason: str | None
|
||||
total_calls: int = 0
|
||||
total_calls: int = 0
|
||||
|
||||
|
||||
class ApiKeyManager:
|
||||
"""API Key 管理器"""
|
||||
|
||||
# Key 前缀
|
||||
KEY_PREFIX = "ak_live_"
|
||||
KEY_LENGTH = 48 # 总长度: 前缀(8) + 随机部分(40)
|
||||
KEY_PREFIX = "ak_live_"
|
||||
KEY_LENGTH = 48 # 总长度: 前缀(8) + 随机部分(40)
|
||||
|
||||
def __init__(self, db_path: str = DB_PATH) -> None:
|
||||
self.db_path = db_path
|
||||
def __init__(self, db_path: str = DB_PATH) -> None:
|
||||
self.db_path = db_path
|
||||
self._init_db()
|
||||
|
||||
def _init_db(self) -> None:
|
||||
@@ -117,7 +117,7 @@ class ApiKeyManager:
|
||||
def _generate_key(self) -> str:
|
||||
"""生成新的 API Key"""
|
||||
# 生成 40 字符的随机字符串
|
||||
random_part = secrets.token_urlsafe(30)[:40]
|
||||
random_part = secrets.token_urlsafe(30)[:40]
|
||||
return f"{self.KEY_PREFIX}{random_part}"
|
||||
|
||||
def _hash_key(self, key: str) -> str:
|
||||
@@ -131,10 +131,10 @@ class ApiKeyManager:
|
||||
def create_key(
|
||||
self,
|
||||
name: str,
|
||||
owner_id: str | None = None,
|
||||
permissions: list[str] = None,
|
||||
rate_limit: int = 60,
|
||||
expires_days: int | None = None,
|
||||
owner_id: str | None = None,
|
||||
permissions: list[str] = None,
|
||||
rate_limit: int = 60,
|
||||
expires_days: int | None = None,
|
||||
) -> tuple[str, ApiKey]:
|
||||
"""
|
||||
创建新的 API Key
|
||||
@@ -143,32 +143,32 @@ class ApiKeyManager:
|
||||
tuple: (原始key(仅返回一次), ApiKey对象)
|
||||
"""
|
||||
if permissions is None:
|
||||
permissions = ["read"]
|
||||
permissions = ["read"]
|
||||
|
||||
key_id = secrets.token_hex(16)
|
||||
raw_key = self._generate_key()
|
||||
key_hash = self._hash_key(raw_key)
|
||||
key_preview = self._get_preview(raw_key)
|
||||
key_id = secrets.token_hex(16)
|
||||
raw_key = self._generate_key()
|
||||
key_hash = self._hash_key(raw_key)
|
||||
key_preview = self._get_preview(raw_key)
|
||||
|
||||
expires_at = None
|
||||
expires_at = None
|
||||
if expires_days:
|
||||
expires_at = (datetime.now() + timedelta(days=expires_days)).isoformat()
|
||||
expires_at = (datetime.now() + timedelta(days = expires_days)).isoformat()
|
||||
|
||||
api_key = ApiKey(
|
||||
id=key_id,
|
||||
key_hash=key_hash,
|
||||
key_preview=key_preview,
|
||||
name=name,
|
||||
owner_id=owner_id,
|
||||
permissions=permissions,
|
||||
rate_limit=rate_limit,
|
||||
status=ApiKeyStatus.ACTIVE.value,
|
||||
created_at=datetime.now().isoformat(),
|
||||
expires_at=expires_at,
|
||||
last_used_at=None,
|
||||
revoked_at=None,
|
||||
revoked_reason=None,
|
||||
total_calls=0,
|
||||
api_key = ApiKey(
|
||||
id = key_id,
|
||||
key_hash = key_hash,
|
||||
key_preview = key_preview,
|
||||
name = name,
|
||||
owner_id = owner_id,
|
||||
permissions = permissions,
|
||||
rate_limit = rate_limit,
|
||||
status = ApiKeyStatus.ACTIVE.value,
|
||||
created_at = datetime.now().isoformat(),
|
||||
expires_at = expires_at,
|
||||
last_used_at = None,
|
||||
revoked_at = None,
|
||||
revoked_reason = None,
|
||||
total_calls = 0,
|
||||
)
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
@@ -203,16 +203,16 @@ class ApiKeyManager:
|
||||
Returns:
|
||||
ApiKey if valid, None otherwise
|
||||
"""
|
||||
key_hash = self._hash_key(key)
|
||||
key_hash = self._hash_key(key)
|
||||
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
row = conn.execute("SELECT * FROM api_keys WHERE key_hash = ?", (key_hash,)).fetchone()
|
||||
conn.row_factory = sqlite3.Row
|
||||
row = conn.execute("SELECT * FROM api_keys WHERE key_hash = ?", (key_hash, )).fetchone()
|
||||
|
||||
if not row:
|
||||
return None
|
||||
|
||||
api_key = self._row_to_api_key(row)
|
||||
api_key = self._row_to_api_key(row)
|
||||
|
||||
# 检查状态
|
||||
if api_key.status != ApiKeyStatus.ACTIVE.value:
|
||||
@@ -220,11 +220,11 @@ class ApiKeyManager:
|
||||
|
||||
# 检查是否过期
|
||||
if api_key.expires_at:
|
||||
expires = datetime.fromisoformat(api_key.expires_at)
|
||||
expires = datetime.fromisoformat(api_key.expires_at)
|
||||
if datetime.now() > expires:
|
||||
# 更新状态为过期
|
||||
conn.execute(
|
||||
"UPDATE api_keys SET status = ? WHERE id = ?",
|
||||
"UPDATE api_keys SET status = ? WHERE id = ?",
|
||||
(ApiKeyStatus.EXPIRED.value, api_key.id),
|
||||
)
|
||||
conn.commit()
|
||||
@@ -232,22 +232,22 @@ class ApiKeyManager:
|
||||
|
||||
return api_key
|
||||
|
||||
def revoke_key(self, key_id: str, reason: str = "", owner_id: str | None = None) -> bool:
|
||||
def revoke_key(self, key_id: str, reason: str = "", owner_id: str | None = None) -> bool:
|
||||
"""撤销 API Key"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
# 验证所有权(如果提供了 owner_id)
|
||||
if owner_id:
|
||||
row = conn.execute(
|
||||
"SELECT owner_id FROM api_keys WHERE id = ?", (key_id,)
|
||||
row = conn.execute(
|
||||
"SELECT owner_id FROM api_keys WHERE id = ?", (key_id, )
|
||||
).fetchone()
|
||||
if not row or row[0] != owner_id:
|
||||
return False
|
||||
|
||||
cursor = conn.execute(
|
||||
cursor = conn.execute(
|
||||
"""
|
||||
UPDATE api_keys
|
||||
SET status = ?, revoked_at = ?, revoked_reason = ?
|
||||
WHERE id = ? AND status = ?
|
||||
SET status = ?, revoked_at = ?, revoked_reason = ?
|
||||
WHERE id = ? AND status = ?
|
||||
""",
|
||||
(
|
||||
ApiKeyStatus.REVOKED.value,
|
||||
@@ -260,17 +260,17 @@ class ApiKeyManager:
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
def get_key_by_id(self, key_id: str, owner_id: str | None = None) -> ApiKey | None:
|
||||
def get_key_by_id(self, key_id: str, owner_id: str | None = None) -> ApiKey | None:
|
||||
"""通过 ID 获取 API Key(不包含敏感信息)"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
if owner_id:
|
||||
row = conn.execute(
|
||||
"SELECT * FROM api_keys WHERE id = ? AND owner_id = ?", (key_id, owner_id)
|
||||
row = conn.execute(
|
||||
"SELECT * FROM api_keys WHERE id = ? AND owner_id = ?", (key_id, owner_id)
|
||||
).fetchone()
|
||||
else:
|
||||
row = conn.execute("SELECT * FROM api_keys WHERE id = ?", (key_id,)).fetchone()
|
||||
row = conn.execute("SELECT * FROM api_keys WHERE id = ?", (key_id, )).fetchone()
|
||||
|
||||
if row:
|
||||
return self._row_to_api_key(row)
|
||||
@@ -278,54 +278,54 @@ class ApiKeyManager:
|
||||
|
||||
def list_keys(
|
||||
self,
|
||||
owner_id: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
owner_id: str | None = None,
|
||||
status: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> list[ApiKey]:
|
||||
"""列出 API Keys"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
query = "SELECT * FROM api_keys WHERE 1=1"
|
||||
params = []
|
||||
query = "SELECT * FROM api_keys WHERE 1 = 1"
|
||||
params = []
|
||||
|
||||
if owner_id:
|
||||
query += " AND owner_id = ?"
|
||||
query += " AND owner_id = ?"
|
||||
params.append(owner_id)
|
||||
|
||||
if status:
|
||||
query += " AND status = ?"
|
||||
query += " AND status = ?"
|
||||
params.append(status)
|
||||
|
||||
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
|
||||
rows = conn.execute(query, params).fetchall()
|
||||
rows = conn.execute(query, params).fetchall()
|
||||
return [self._row_to_api_key(row) for row in rows]
|
||||
|
||||
def update_key(
|
||||
self,
|
||||
key_id: str,
|
||||
name: str | None = None,
|
||||
permissions: list[str] | None = None,
|
||||
rate_limit: int | None = None,
|
||||
owner_id: str | None = None,
|
||||
name: str | None = None,
|
||||
permissions: list[str] | None = None,
|
||||
rate_limit: int | None = None,
|
||||
owner_id: str | None = None,
|
||||
) -> bool:
|
||||
"""更新 API Key 信息"""
|
||||
updates = []
|
||||
params = []
|
||||
updates = []
|
||||
params = []
|
||||
|
||||
if name is not None:
|
||||
updates.append("name = ?")
|
||||
updates.append("name = ?")
|
||||
params.append(name)
|
||||
|
||||
if permissions is not None:
|
||||
updates.append("permissions = ?")
|
||||
updates.append("permissions = ?")
|
||||
params.append(json.dumps(permissions))
|
||||
|
||||
if rate_limit is not None:
|
||||
updates.append("rate_limit = ?")
|
||||
updates.append("rate_limit = ?")
|
||||
params.append(rate_limit)
|
||||
|
||||
if not updates:
|
||||
@@ -336,14 +336,14 @@ class ApiKeyManager:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
# 验证所有权
|
||||
if owner_id:
|
||||
row = conn.execute(
|
||||
"SELECT owner_id FROM api_keys WHERE id = ?", (key_id,)
|
||||
row = conn.execute(
|
||||
"SELECT owner_id FROM api_keys WHERE id = ?", (key_id, )
|
||||
).fetchone()
|
||||
if not row or row[0] != owner_id:
|
||||
return False
|
||||
|
||||
query = f"UPDATE api_keys SET {', '.join(updates)} WHERE id = ?"
|
||||
cursor = conn.execute(query, params)
|
||||
query = f"UPDATE api_keys SET {', '.join(updates)} WHERE id = ?"
|
||||
cursor = conn.execute(query, params)
|
||||
conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
|
||||
@@ -353,8 +353,8 @@ class ApiKeyManager:
|
||||
conn.execute(
|
||||
"""
|
||||
UPDATE api_keys
|
||||
SET last_used_at = ?, total_calls = total_calls + 1
|
||||
WHERE id = ?
|
||||
SET last_used_at = ?, total_calls = total_calls + 1
|
||||
WHERE id = ?
|
||||
""",
|
||||
(datetime.now().isoformat(), key_id),
|
||||
)
|
||||
@@ -365,12 +365,12 @@ class ApiKeyManager:
|
||||
api_key_id: str,
|
||||
endpoint: str,
|
||||
method: str,
|
||||
status_code: int = 200,
|
||||
response_time_ms: int = 0,
|
||||
ip_address: str = "",
|
||||
user_agent: str = "",
|
||||
error_message: str = "",
|
||||
):
|
||||
status_code: int = 200,
|
||||
response_time_ms: int = 0,
|
||||
ip_address: str = "",
|
||||
user_agent: str = "",
|
||||
error_message: str = "",
|
||||
) -> None:
|
||||
"""记录 API 调用日志"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.execute(
|
||||
@@ -395,21 +395,21 @@ class ApiKeyManager:
|
||||
|
||||
def get_call_logs(
|
||||
self,
|
||||
api_key_id: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
api_key_id: str | None = None,
|
||||
start_date: str | None = None,
|
||||
end_date: str | None = None,
|
||||
limit: int = 100,
|
||||
offset: int = 0,
|
||||
) -> list[dict]:
|
||||
"""获取 API 调用日志"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
query = "SELECT * FROM api_call_logs WHERE 1=1"
|
||||
params = []
|
||||
query = "SELECT * FROM api_call_logs WHERE 1 = 1"
|
||||
params = []
|
||||
|
||||
if api_key_id:
|
||||
query += " AND api_key_id = ?"
|
||||
query += " AND api_key_id = ?"
|
||||
params.append(api_key_id)
|
||||
|
||||
if start_date:
|
||||
@@ -423,16 +423,16 @@ class ApiKeyManager:
|
||||
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
|
||||
rows = conn.execute(query, params).fetchall()
|
||||
rows = conn.execute(query, params).fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def get_call_stats(self, api_key_id: str | None = None, days: int = 30) -> dict:
|
||||
def get_call_stats(self, api_key_id: str | None = None, days: int = 30) -> dict:
|
||||
"""获取 API 调用统计"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
conn.row_factory = sqlite3.Row
|
||||
conn.row_factory = sqlite3.Row
|
||||
|
||||
# 总体统计
|
||||
query = f"""
|
||||
query = f"""
|
||||
SELECT
|
||||
COUNT(*) as total_calls,
|
||||
COUNT(CASE WHEN status_code < 400 THEN 1 END) as success_calls,
|
||||
@@ -444,15 +444,15 @@ class ApiKeyManager:
|
||||
WHERE created_at >= date('now', '-{days} days')
|
||||
"""
|
||||
|
||||
params = []
|
||||
params = []
|
||||
if api_key_id:
|
||||
query = query.replace("WHERE created_at", "WHERE api_key_id = ? AND created_at")
|
||||
query = query.replace("WHERE created_at", "WHERE api_key_id = ? AND created_at")
|
||||
params.insert(0, api_key_id)
|
||||
|
||||
row = conn.execute(query, params).fetchone()
|
||||
row = conn.execute(query, params).fetchone()
|
||||
|
||||
# 按端点统计
|
||||
endpoint_query = f"""
|
||||
endpoint_query = f"""
|
||||
SELECT
|
||||
endpoint,
|
||||
method,
|
||||
@@ -462,19 +462,19 @@ class ApiKeyManager:
|
||||
WHERE created_at >= date('now', '-{days} days')
|
||||
"""
|
||||
|
||||
endpoint_params = []
|
||||
endpoint_params = []
|
||||
if api_key_id:
|
||||
endpoint_query = endpoint_query.replace(
|
||||
"WHERE created_at", "WHERE api_key_id = ? AND created_at"
|
||||
endpoint_query = endpoint_query.replace(
|
||||
"WHERE created_at", "WHERE api_key_id = ? AND created_at"
|
||||
)
|
||||
endpoint_params.insert(0, api_key_id)
|
||||
|
||||
endpoint_query += " GROUP BY endpoint, method ORDER BY calls DESC"
|
||||
|
||||
endpoint_rows = conn.execute(endpoint_query, endpoint_params).fetchall()
|
||||
endpoint_rows = conn.execute(endpoint_query, endpoint_params).fetchall()
|
||||
|
||||
# 按天统计
|
||||
daily_query = f"""
|
||||
daily_query = f"""
|
||||
SELECT
|
||||
date(created_at) as date,
|
||||
COUNT(*) as calls,
|
||||
@@ -483,16 +483,16 @@ class ApiKeyManager:
|
||||
WHERE created_at >= date('now', '-{days} days')
|
||||
"""
|
||||
|
||||
daily_params = []
|
||||
daily_params = []
|
||||
if api_key_id:
|
||||
daily_query = daily_query.replace(
|
||||
"WHERE created_at", "WHERE api_key_id = ? AND created_at"
|
||||
daily_query = daily_query.replace(
|
||||
"WHERE created_at", "WHERE api_key_id = ? AND created_at"
|
||||
)
|
||||
daily_params.insert(0, api_key_id)
|
||||
|
||||
daily_query += " GROUP BY date(created_at) ORDER BY date"
|
||||
|
||||
daily_rows = conn.execute(daily_query, daily_params).fetchall()
|
||||
daily_rows = conn.execute(daily_query, daily_params).fetchall()
|
||||
|
||||
return {
|
||||
"summary": {
|
||||
@@ -510,30 +510,30 @@ class ApiKeyManager:
|
||||
def _row_to_api_key(self, row: sqlite3.Row) -> ApiKey:
|
||||
"""将数据库行转换为 ApiKey 对象"""
|
||||
return ApiKey(
|
||||
id=row["id"],
|
||||
key_hash=row["key_hash"],
|
||||
key_preview=row["key_preview"],
|
||||
name=row["name"],
|
||||
owner_id=row["owner_id"],
|
||||
permissions=json.loads(row["permissions"]),
|
||||
rate_limit=row["rate_limit"],
|
||||
status=row["status"],
|
||||
created_at=row["created_at"],
|
||||
expires_at=row["expires_at"],
|
||||
last_used_at=row["last_used_at"],
|
||||
revoked_at=row["revoked_at"],
|
||||
revoked_reason=row["revoked_reason"],
|
||||
total_calls=row["total_calls"],
|
||||
id = row["id"],
|
||||
key_hash = row["key_hash"],
|
||||
key_preview = row["key_preview"],
|
||||
name = row["name"],
|
||||
owner_id = row["owner_id"],
|
||||
permissions = json.loads(row["permissions"]),
|
||||
rate_limit = row["rate_limit"],
|
||||
status = row["status"],
|
||||
created_at = row["created_at"],
|
||||
expires_at = row["expires_at"],
|
||||
last_used_at = row["last_used_at"],
|
||||
revoked_at = row["revoked_at"],
|
||||
revoked_reason = row["revoked_reason"],
|
||||
total_calls = row["total_calls"],
|
||||
)
|
||||
|
||||
|
||||
# 全局实例
|
||||
_api_key_manager: ApiKeyManager | None = None
|
||||
_api_key_manager: ApiKeyManager | None = None
|
||||
|
||||
|
||||
def get_api_key_manager() -> ApiKeyManager:
|
||||
"""获取 API Key 管理器实例"""
|
||||
global _api_key_manager
|
||||
if _api_key_manager is None:
|
||||
_api_key_manager = ApiKeyManager()
|
||||
_api_key_manager = ApiKeyManager()
|
||||
return _api_key_manager
|
||||
|
||||
Reference in New Issue
Block a user