style(database,static): 优化代码格式并本地化静态资源

- 重新组织 database/services.py 的导入语句,按照标准顺序排列
- 统一代码格式,包括函数参数对齐和尾随逗号
- 优化 delete_all_error_logs 函数,移除不必要的计数查询以提高性能
- 添加本地字体文件 fonts.css,包含 Inter 字体的多种字重和语言支持
- 本地化 Tailwind CSS 脚本,减少外部依赖
- 更新 base.html 模板以使用本地静态资源
This commit is contained in:
snaily
2025-08-16 03:41:42 +08:00
parent 40c9689eae
commit 13e1db7d69
4 changed files with 523 additions and 114 deletions
+123 -112
View File
@@ -1,12 +1,15 @@
""" """
数据库服务模块 数据库服务模块
""" """
from typing import List, Optional, Dict, Any, Union
from datetime import datetime, timezone
from sqlalchemy import func, desc, asc, select, insert, update, delete
import json import json
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Union
from sqlalchemy import asc, delete, desc, func, insert, select, update
from app.database.connection import database from app.database.connection import database
from app.database.models import Settings, ErrorLog, RequestLog, FileRecord, FileState from app.database.models import ErrorLog, FileRecord, FileState, RequestLog, Settings
from app.log.logger import get_database_logger from app.log.logger import get_database_logger
from app.utils.helpers import redact_key_for_logging from app.utils.helpers import redact_key_for_logging
@@ -16,7 +19,7 @@ logger = get_database_logger()
async def get_all_settings() -> List[Dict[str, Any]]: async def get_all_settings() -> List[Dict[str, Any]]:
""" """
获取所有设置 获取所有设置
Returns: Returns:
List[Dict[str, Any]]: 设置列表 List[Dict[str, Any]]: 设置列表
""" """
@@ -32,10 +35,10 @@ async def get_all_settings() -> List[Dict[str, Any]]:
async def get_setting(key: str) -> Optional[Dict[str, Any]]: async def get_setting(key: str) -> Optional[Dict[str, Any]]:
""" """
获取指定键的设置 获取指定键的设置
Args: Args:
key: 设置键名 key: 设置键名
Returns: Returns:
Optional[Dict[str, Any]]: 设置信息,如果不存在则返回None Optional[Dict[str, Any]]: 设置信息,如果不存在则返回None
""" """
@@ -48,22 +51,24 @@ async def get_setting(key: str) -> Optional[Dict[str, Any]]:
raise raise
async def update_setting(key: str, value: str, description: Optional[str] = None) -> bool: async def update_setting(
key: str, value: str, description: Optional[str] = None
) -> bool:
""" """
更新设置 更新设置
Args: Args:
key: 设置键名 key: 设置键名
value: 设置值 value: 设置值
description: 设置描述 description: 设置描述
Returns: Returns:
bool: 是否更新成功 bool: 是否更新成功
""" """
try: try:
# 检查设置是否存在 # 检查设置是否存在
setting = await get_setting(key) setting = await get_setting(key)
if setting: if setting:
# 更新设置 # 更新设置
query = ( query = (
@@ -72,7 +77,7 @@ async def update_setting(key: str, value: str, description: Optional[str] = None
.values( .values(
value=value, value=value,
description=description if description else setting["description"], description=description if description else setting["description"],
updated_at=datetime.now() updated_at=datetime.now(),
) )
) )
await database.execute(query) await database.execute(query)
@@ -80,15 +85,12 @@ async def update_setting(key: str, value: str, description: Optional[str] = None
return True return True
else: else:
# 插入设置 # 插入设置
query = ( query = insert(Settings).values(
insert(Settings) key=key,
.values( value=value,
key=key, description=description,
value=value, created_at=datetime.now(),
description=description, updated_at=datetime.now(),
created_at=datetime.now(),
updated_at=datetime.now()
)
) )
await database.execute(query) await database.execute(query)
logger.info(f"Inserted setting: {key}") logger.info(f"Inserted setting: {key}")
@@ -104,17 +106,17 @@ async def add_error_log(
error_type: Optional[str] = None, error_type: Optional[str] = None,
error_log: Optional[str] = None, error_log: Optional[str] = None,
error_code: Optional[int] = None, error_code: Optional[int] = None,
request_msg: Optional[Union[Dict[str, Any], str]] = None request_msg: Optional[Union[Dict[str, Any], str]] = None,
) -> bool: ) -> bool:
""" """
添加错误日志 添加错误日志
Args: Args:
gemini_key: Gemini API密钥 gemini_key: Gemini API密钥
error_log: 错误日志 error_log: 错误日志
error_code: 错误代码 (例如 HTTP 状态码) error_code: 错误代码 (例如 HTTP 状态码)
request_msg: 请求消息 request_msg: 请求消息
Returns: Returns:
bool: 是否添加成功 bool: 是否添加成功
""" """
@@ -129,19 +131,16 @@ async def add_error_log(
request_msg_json = {"message": request_msg} request_msg_json = {"message": request_msg}
else: else:
request_msg_json = None request_msg_json = None
# 插入错误日志 # 插入错误日志
query = ( query = insert(ErrorLog).values(
insert(ErrorLog) gemini_key=gemini_key,
.values( error_type=error_type,
gemini_key=gemini_key, error_log=error_log,
error_type=error_type, model_name=model_name,
error_log=error_log, error_code=error_code,
model_name=model_name, request_msg=request_msg_json,
error_code=error_code, request_time=datetime.now(),
request_msg=request_msg_json,
request_time=datetime.now()
)
) )
await database.execute(query) await database.execute(query)
logger.info(f"Added error log for key: {redact_key_for_logging(gemini_key)}") logger.info(f"Added error log for key: {redact_key_for_logging(gemini_key)}")
@@ -159,8 +158,8 @@ async def get_error_logs(
error_code_search: Optional[str] = None, error_code_search: Optional[str] = None,
start_date: Optional[datetime] = None, start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None, end_date: Optional[datetime] = None,
sort_by: str = 'id', sort_by: str = "id",
sort_order: str = 'desc' sort_order: str = "desc",
) -> List[Dict[str, Any]]: ) -> List[Dict[str, Any]]:
""" """
获取错误日志,支持搜索、日期过滤和排序 获取错误日志,支持搜索、日期过滤和排序
@@ -187,15 +186,15 @@ async def get_error_logs(
ErrorLog.error_type, ErrorLog.error_type,
ErrorLog.error_log, ErrorLog.error_log,
ErrorLog.error_code, ErrorLog.error_code,
ErrorLog.request_time ErrorLog.request_time,
) )
if key_search: if key_search:
query = query.where(ErrorLog.gemini_key.ilike(f"%{key_search}%")) query = query.where(ErrorLog.gemini_key.ilike(f"%{key_search}%"))
if error_search: if error_search:
query = query.where( query = query.where(
(ErrorLog.error_type.ilike(f"%{error_search}%")) | (ErrorLog.error_type.ilike(f"%{error_search}%"))
(ErrorLog.error_log.ilike(f"%{error_search}%")) | (ErrorLog.error_log.ilike(f"%{error_search}%"))
) )
if start_date: if start_date:
query = query.where(ErrorLog.request_time >= start_date) query = query.where(ErrorLog.request_time >= start_date)
@@ -206,10 +205,12 @@ async def get_error_logs(
error_code_int = int(error_code_search) error_code_int = int(error_code_search)
query = query.where(ErrorLog.error_code == error_code_int) query = query.where(ErrorLog.error_code == error_code_int)
except ValueError: except ValueError:
logger.warning(f"Invalid format for error_code_search: '{error_code_search}'. Expected an integer. Skipping error code filter.") logger.warning(
f"Invalid format for error_code_search: '{error_code_search}'. Expected an integer. Skipping error code filter."
)
sort_column = getattr(ErrorLog, sort_by, ErrorLog.id) sort_column = getattr(ErrorLog, sort_by, ErrorLog.id)
if sort_order.lower() == 'asc': if sort_order.lower() == "asc":
query = query.order_by(asc(sort_column)) query = query.order_by(asc(sort_column))
else: else:
query = query.order_by(desc(sort_column)) query = query.order_by(desc(sort_column))
@@ -228,7 +229,7 @@ async def get_error_logs_count(
error_search: Optional[str] = None, error_search: Optional[str] = None,
error_code_search: Optional[str] = None, error_code_search: Optional[str] = None,
start_date: Optional[datetime] = None, start_date: Optional[datetime] = None,
end_date: Optional[datetime] = None end_date: Optional[datetime] = None,
) -> int: ) -> int:
""" """
获取符合条件的错误日志总数 获取符合条件的错误日志总数
@@ -250,8 +251,8 @@ async def get_error_logs_count(
query = query.where(ErrorLog.gemini_key.ilike(f"%{key_search}%")) query = query.where(ErrorLog.gemini_key.ilike(f"%{key_search}%"))
if error_search: if error_search:
query = query.where( query = query.where(
(ErrorLog.error_type.ilike(f"%{error_search}%")) | (ErrorLog.error_type.ilike(f"%{error_search}%"))
(ErrorLog.error_log.ilike(f"%{error_search}%")) | (ErrorLog.error_log.ilike(f"%{error_search}%"))
) )
if start_date: if start_date:
query = query.where(ErrorLog.request_time >= start_date) query = query.where(ErrorLog.request_time >= start_date)
@@ -262,8 +263,9 @@ async def get_error_logs_count(
error_code_int = int(error_code_search) error_code_int = int(error_code_search)
query = query.where(ErrorLog.error_code == error_code_int) query = query.where(ErrorLog.error_code == error_code_int)
except ValueError: except ValueError:
logger.warning(f"Invalid format for error_code_search in count: '{error_code_search}'. Expected an integer. Skipping error code filter.") logger.warning(
f"Invalid format for error_code_search in count: '{error_code_search}'. Expected an integer. Skipping error code filter."
)
count_result = await database.fetch_one(query) count_result = await database.fetch_one(query)
return count_result[0] if count_result else 0 return count_result[0] if count_result else 0
@@ -289,12 +291,14 @@ async def get_error_log_details(log_id: int) -> Optional[Dict[str, Any]]:
if result: if result:
# 将 request_msg (JSONB) 转换为字符串以便在 API 中返回 # 将 request_msg (JSONB) 转换为字符串以便在 API 中返回
log_dict = dict(result) log_dict = dict(result)
if 'request_msg' in log_dict and log_dict['request_msg'] is not None: if "request_msg" in log_dict and log_dict["request_msg"] is not None:
# 确保即使是 None 或非 JSON 数据也能处理 # 确保即使是 None 或非 JSON 数据也能处理
try: try:
log_dict['request_msg'] = json.dumps(log_dict['request_msg'], ensure_ascii=False, indent=2) log_dict["request_msg"] = json.dumps(
log_dict["request_msg"], ensure_ascii=False, indent=2
)
except TypeError: except TypeError:
log_dict['request_msg'] = str(log_dict['request_msg']) log_dict["request_msg"] = str(log_dict["request_msg"])
return log_dict return log_dict
else: else:
return None return None
@@ -327,12 +331,15 @@ async def delete_error_logs_by_ids(log_ids: List[int]) -> int:
# 注意:databases 的 execute 不返回 rowcount,所以我们不能直接返回删除的数量 # 注意:databases 的 execute 不返回 rowcount,所以我们不能直接返回删除的数量
# 返回 log_ids 的长度作为尝试删除的数量,或者返回 0/1 表示操作尝试 # 返回 log_ids 的长度作为尝试删除的数量,或者返回 0/1 表示操作尝试
logger.info(f"Attempted bulk deletion for error logs with IDs: {log_ids}") logger.info(f"Attempted bulk deletion for error logs with IDs: {log_ids}")
return len(log_ids) # 返回尝试删除的数量 return len(log_ids) # 返回尝试删除的数量
except Exception as e: except Exception as e:
# 数据库连接或执行错误 # 数据库连接或执行错误
logger.error(f"Error during bulk deletion of error logs {log_ids}: {e}", exc_info=True) logger.error(
f"Error during bulk deletion of error logs {log_ids}: {e}", exc_info=True
)
raise raise
async def delete_error_log_by_id(log_id: int) -> bool: async def delete_error_log_by_id(log_id: int) -> bool:
""" """
根据 ID 删除单个错误日志 (异步)。 根据 ID 删除单个错误日志 (异步)。
@@ -349,7 +356,9 @@ async def delete_error_log_by_id(log_id: int) -> bool:
exists = await database.fetch_one(check_query) exists = await database.fetch_one(check_query)
if not exists: if not exists:
logger.warning(f"Attempted to delete non-existent error log with ID: {log_id}") logger.warning(
f"Attempted to delete non-existent error log with ID: {log_id}"
)
return False return False
# 执行删除 # 执行删除
@@ -360,35 +369,31 @@ async def delete_error_log_by_id(log_id: int) -> bool:
except Exception as e: except Exception as e:
logger.error(f"Error deleting error log with ID {log_id}: {e}", exc_info=True) logger.error(f"Error deleting error log with ID {log_id}: {e}", exc_info=True)
raise raise
async def delete_all_error_logs() -> int: async def delete_all_error_logs() -> int:
""" """
删除所有错误日志条目。 删除所有错误日志条目。
Returns: Returns:
int: 被删除的错误日志数量。 int: 被删除的错误日志数量。如果使用的数据库驱动不支持返回受影响行数,则返回 -1 表示操作成功。
""" """
try: try:
# 1. 获取删除前的总数 # 直接执行删除操作,避免不必要的查询
count_query = select(func.count()).select_from(ErrorLog)
total_to_delete = await database.fetch_val(count_query)
if total_to_delete == 0:
logger.info("No error logs found to delete.")
return 0
# 2. 执行删除操作
delete_query = delete(ErrorLog) delete_query = delete(ErrorLog)
await database.execute(delete_query) await database.execute(delete_query)
logger.info(f"Successfully deleted all {total_to_delete} error logs.") logger.info("Successfully deleted all error logs.")
return total_to_delete
# 由于 databases 库的 execute 方法不返回受影响的行数,
# 返回 -1 表示删除操作成功执行,但具体删除数量未知
# 这比先查询再删除的方式更高效
return -1
except Exception as e: except Exception as e:
logger.error(f"Failed to delete all error logs: {str(e)}", exc_info=True) logger.error(f"Failed to delete all error logs: {str(e)}", exc_info=True)
raise raise
# 新增函数:添加请求日志 # 新增函数:添加请求日志
async def add_request_log( async def add_request_log(
model_name: Optional[str], model_name: Optional[str],
@@ -396,7 +401,7 @@ async def add_request_log(
is_success: bool, is_success: bool,
status_code: Optional[int] = None, status_code: Optional[int] = None,
latency_ms: Optional[int] = None, latency_ms: Optional[int] = None,
request_time: Optional[datetime] = None request_time: Optional[datetime] = None,
) -> bool: ) -> bool:
""" """
添加 API 请求日志 添加 API 请求日志
@@ -421,7 +426,7 @@ async def add_request_log(
api_key=api_key, api_key=api_key,
is_success=is_success, is_success=is_success,
status_code=status_code, status_code=status_code,
latency_ms=latency_ms latency_ms=latency_ms,
) )
await database.execute(query) await database.execute(query)
return True return True
@@ -432,6 +437,7 @@ async def add_request_log(
# ==================== 文件记录相关函数 ==================== # ==================== 文件记录相关函数 ====================
async def create_file_record( async def create_file_record(
name: str, name: str,
mime_type: str, mime_type: str,
@@ -445,11 +451,11 @@ async def create_file_record(
display_name: Optional[str] = None, display_name: Optional[str] = None,
sha256_hash: Optional[str] = None, sha256_hash: Optional[str] = None,
upload_url: Optional[str] = None, upload_url: Optional[str] = None,
user_token: Optional[str] = None user_token: Optional[str] = None,
) -> Dict[str, Any]: ) -> Dict[str, Any]:
""" """
创建文件记录 创建文件记录
Args: Args:
name: 文件名称(格式: files/{file_id} name: 文件名称(格式: files/{file_id}
mime_type: MIME 类型 mime_type: MIME 类型
@@ -463,7 +469,7 @@ async def create_file_record(
sha256_hash: SHA256 哈希值 sha256_hash: SHA256 哈希值
upload_url: 临时上传 URL upload_url: 临时上传 URL
user_token: 上传用户的 token user_token: 上传用户的 token
Returns: Returns:
Dict[str, Any]: 创建的文件记录 Dict[str, Any]: 创建的文件记录
""" """
@@ -481,10 +487,10 @@ async def create_file_record(
uri=uri, uri=uri,
api_key=api_key, api_key=api_key,
upload_url=upload_url, upload_url=upload_url,
user_token=user_token user_token=user_token,
) )
await database.execute(query) await database.execute(query)
# 返回创建的记录 # 返回创建的记录
return await get_file_record_by_name(name) return await get_file_record_by_name(name)
except Exception as e: except Exception as e:
@@ -495,10 +501,10 @@ async def create_file_record(
async def get_file_record_by_name(name: str) -> Optional[Dict[str, Any]]: async def get_file_record_by_name(name: str) -> Optional[Dict[str, Any]]:
""" """
根据文件名获取文件记录 根据文件名获取文件记录
Args: Args:
name: 文件名称(格式: files/{file_id} name: 文件名称(格式: files/{file_id}
Returns: Returns:
Optional[Dict[str, Any]]: 文件记录,如果不存在则返回 None Optional[Dict[str, Any]]: 文件记录,如果不存在则返回 None
""" """
@@ -511,24 +517,23 @@ async def get_file_record_by_name(name: str) -> Optional[Dict[str, Any]]:
raise raise
async def update_file_record_state( async def update_file_record_state(
file_name: str, file_name: str,
state: FileState, state: FileState,
update_time: Optional[datetime] = None, update_time: Optional[datetime] = None,
upload_completed: Optional[datetime] = None, upload_completed: Optional[datetime] = None,
sha256_hash: Optional[str] = None sha256_hash: Optional[str] = None,
) -> bool: ) -> bool:
""" """
更新文件记录状态 更新文件记录状态
Args: Args:
file_name: 文件名 file_name: 文件名
state: 新状态 state: 新状态
update_time: 更新时间 update_time: 更新时间
upload_completed: 上传完成时间 upload_completed: 上传完成时间
sha256_hash: SHA256 哈希值 sha256_hash: SHA256 哈希值
Returns: Returns:
bool: 是否更新成功 bool: 是否更新成功
""" """
@@ -540,14 +545,14 @@ async def update_file_record_state(
values["upload_completed"] = upload_completed values["upload_completed"] = upload_completed
if sha256_hash: if sha256_hash:
values["sha256_hash"] = sha256_hash values["sha256_hash"] = sha256_hash
query = update(FileRecord).where(FileRecord.name == file_name).values(**values) query = update(FileRecord).where(FileRecord.name == file_name).values(**values)
result = await database.execute(query) result = await database.execute(query)
if result: if result:
logger.info(f"Updated file record state for {file_name} to {state}") logger.info(f"Updated file record state for {file_name} to {state}")
return True return True
logger.warning(f"File record not found for update: {file_name}") logger.warning(f"File record not found for update: {file_name}")
return False return False
except Exception as e: except Exception as e:
@@ -559,31 +564,33 @@ async def list_file_records(
user_token: Optional[str] = None, user_token: Optional[str] = None,
api_key: Optional[str] = None, api_key: Optional[str] = None,
page_size: int = 10, page_size: int = 10,
page_token: Optional[str] = None page_token: Optional[str] = None,
) -> tuple[List[Dict[str, Any]], Optional[str]]: ) -> tuple[List[Dict[str, Any]], Optional[str]]:
""" """
列出文件记录 列出文件记录
Args: Args:
user_token: 用户 token(如果提供,只返回该用户的文件) user_token: 用户 token(如果提供,只返回该用户的文件)
api_key: API Key(如果提供,只返回使用该 key 的文件) api_key: API Key(如果提供,只返回使用该 key 的文件)
page_size: 每页大小 page_size: 每页大小
page_token: 分页标记(偏移量) page_token: 分页标记(偏移量)
Returns: Returns:
tuple[List[Dict[str, Any]], Optional[str]]: (文件列表, 下一页标记) tuple[List[Dict[str, Any]], Optional[str]]: (文件列表, 下一页标记)
""" """
try: try:
logger.debug(f"list_file_records called with page_size={page_size}, page_token={page_token}") logger.debug(
f"list_file_records called with page_size={page_size}, page_token={page_token}"
)
query = select(FileRecord).where( query = select(FileRecord).where(
FileRecord.expiration_time > datetime.now(timezone.utc) FileRecord.expiration_time > datetime.now(timezone.utc)
) )
if user_token: if user_token:
query = query.where(FileRecord.user_token == user_token) query = query.where(FileRecord.user_token == user_token)
if api_key: if api_key:
query = query.where(FileRecord.api_key == api_key) query = query.where(FileRecord.api_key == api_key)
# 使用偏移量进行分页 # 使用偏移量进行分页
offset = 0 offset = 0
if page_token: if page_token:
@@ -592,16 +599,18 @@ async def list_file_records(
except ValueError: except ValueError:
logger.warning(f"Invalid page token: {page_token}") logger.warning(f"Invalid page token: {page_token}")
offset = 0 offset = 0
# 按ID升序排列,使用 OFFSET 和 LIMIT # 按ID升序排列,使用 OFFSET 和 LIMIT
query = query.order_by(FileRecord.id).offset(offset).limit(page_size + 1) query = query.order_by(FileRecord.id).offset(offset).limit(page_size + 1)
results = await database.fetch_all(query) results = await database.fetch_all(query)
logger.debug(f"Query returned {len(results)} records") logger.debug(f"Query returned {len(results)} records")
if results: if results:
logger.debug(f"First record ID: {results[0]['id']}, Last record ID: {results[-1]['id']}") logger.debug(
f"First record ID: {results[0]['id']}, Last record ID: {results[-1]['id']}"
)
# 处理分页 # 处理分页
has_next = len(results) > page_size has_next = len(results) > page_size
if has_next: if has_next:
@@ -609,11 +618,13 @@ async def list_file_records(
# 下一页的偏移量是当前偏移量加上本页返回的记录数 # 下一页的偏移量是当前偏移量加上本页返回的记录数
next_offset = offset + page_size next_offset = offset + page_size
next_page_token = str(next_offset) next_page_token = str(next_offset)
logger.debug(f"Has next page, offset={offset}, page_size={page_size}, next_page_token={next_page_token}") logger.debug(
f"Has next page, offset={offset}, page_size={page_size}, next_page_token={next_page_token}"
)
else: else:
next_page_token = None next_page_token = None
logger.debug(f"No next page, returning {len(results)} results") logger.debug(f"No next page, returning {len(results)} results")
return [dict(row) for row in results], next_page_token return [dict(row) for row in results], next_page_token
except Exception as e: except Exception as e:
logger.error(f"Failed to list file records: {str(e)}") logger.error(f"Failed to list file records: {str(e)}")
@@ -623,10 +634,10 @@ async def list_file_records(
async def delete_file_record(name: str) -> bool: async def delete_file_record(name: str) -> bool:
""" """
删除文件记录 删除文件记录
Args: Args:
name: 文件名称 name: 文件名称
Returns: Returns:
bool: 是否删除成功 bool: 是否删除成功
""" """
@@ -642,7 +653,7 @@ async def delete_file_record(name: str) -> bool:
async def delete_expired_file_records() -> List[Dict[str, Any]]: async def delete_expired_file_records() -> List[Dict[str, Any]]:
""" """
删除已过期的文件记录 删除已过期的文件记录
Returns: Returns:
List[Dict[str, Any]]: 删除的记录列表 List[Dict[str, Any]]: 删除的记录列表
""" """
@@ -652,16 +663,16 @@ async def delete_expired_file_records() -> List[Dict[str, Any]]:
FileRecord.expiration_time <= datetime.now(timezone.utc) FileRecord.expiration_time <= datetime.now(timezone.utc)
) )
expired_records = await database.fetch_all(query) expired_records = await database.fetch_all(query)
if not expired_records: if not expired_records:
return [] return []
# 执行删除 # 执行删除
delete_query = delete(FileRecord).where( delete_query = delete(FileRecord).where(
FileRecord.expiration_time <= datetime.now(timezone.utc) FileRecord.expiration_time <= datetime.now(timezone.utc)
) )
await database.execute(delete_query) await database.execute(delete_query)
logger.info(f"Deleted {len(expired_records)} expired file records") logger.info(f"Deleted {len(expired_records)} expired file records")
return [dict(record) for record in expired_records] return [dict(record) for record in expired_records]
except Exception as e: except Exception as e:
@@ -672,17 +683,17 @@ async def delete_expired_file_records() -> List[Dict[str, Any]]:
async def get_file_api_key(name: str) -> Optional[str]: async def get_file_api_key(name: str) -> Optional[str]:
""" """
获取文件对应的 API Key 获取文件对应的 API Key
Args: Args:
name: 文件名称 name: 文件名称
Returns: Returns:
Optional[str]: API Key,如果文件不存在或已过期则返回 None Optional[str]: API Key,如果文件不存在或已过期则返回 None
""" """
try: try:
query = select(FileRecord.api_key).where( query = select(FileRecord.api_key).where(
(FileRecord.name == name) & (FileRecord.name == name)
(FileRecord.expiration_time > datetime.now(timezone.utc)) & (FileRecord.expiration_time > datetime.now(timezone.utc))
) )
result = await database.fetch_one(query) result = await database.fetch_one(query)
return result["api_key"] if result else None return result["api_key"] if result else None
+315
View File
@@ -0,0 +1,315 @@
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 300;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2JL7SUc.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa0ZL7SUc.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2ZL7SUc.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1pL7SUc.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa2pL7SUc.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa25L7SUc.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v19/UcC73FwrK3iLTeHuS_nVMrMxCp50SjIa1ZL7.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -11,14 +11,14 @@
<meta name="apple-mobile-web-app-title" content="GBalance" /> <meta name="apple-mobile-web-app-title" content="GBalance" />
<link rel="icon" href="/static/icons/icon-192x192.png" /> <link rel="icon" href="/static/icons/icon-192x192.png" />
<link <link
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" href="/static/css/fonts.css"
rel="stylesheet" rel="stylesheet"
/> />
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/> />
<script src="https://cdn.tailwindcss.com"></script> <script src="/static/js/tailwindcss.js"></script>
<script> <script>
tailwind.config = { tailwind.config = {
theme: { theme: {