feat(newapi): 添加 NEWAPI 上传功能及服务管理接口

- 新增 `newapi_upload.py` 文件,包含上传到 NEWAPI 的功能。
- 在数据库模型中添加 `NewapiService` 表及相关字段。
- 更新 CRUD 操作以支持 NEWAPI 服务的创建、更新、查询和删除。
- 添加新的 API 路由以管理 NEWAPI 服务。
- 前端实现批量上传和单个账号上传到 NEWAPI 的功能。
- 更新相关页面以支持 NEWAPI 服务的选择和管理。
This commit is contained in:
Jay Hsueh
2026-03-20 15:05:21 +08:00
parent 99a6d6cc04
commit 05e480a756
14 changed files with 814 additions and 4 deletions

View File

@@ -0,0 +1,126 @@
"""
NEWAPI 上传功能 — 通过 PUT /api/channel/ 添加渠道
"""
import json
import logging
from datetime import datetime
from typing import List, Tuple
from curl_cffi import requests as cffi_requests
from ...database.models import Account
from ...database.session import get_db
logger = logging.getLogger(__name__)
NEWAPI_TYPE_OPENAI = 57
DEFAULT_BASE_URL = ""
DEFAULT_MODELS = "gpt-5.4,gpt-5,gpt-5-codex,gpt-5-codex-mini,gpt-5.1,gpt-5.1-codex,gpt-5.1-codex-max,gpt-5.1-codex-mini,gpt-5.2,gpt-5.2-codex,gpt-5.3-codex,gpt-5-openai-compact,gpt-5-codex-openai-compact,gpt-5-codex-mini-openai-compact,gpt-5.1-openai-compact,gpt-5.1-codex-openai-compact,gpt-5.1-codex-max-openai-compact,gpt-5.1-codex-mini-openai-compact,gpt-5.2-openai-compact,gpt-5.2-codex-openai-compact,gpt-5.3-codex-openai-compact"
def _normalize_base(api_url: str) -> str:
return (api_url or "").strip().rstrip("/")
def _build_headers(api_key: str) -> dict:
return {
"Authorization": f"Bearer {api_key}",
"New-Api-User": "1",
"Content-Type": "application/json",
}
def _extract_error(resp) -> str:
error_msg = f"上传失败: HTTP {resp.status_code}"
try:
detail = resp.json()
if isinstance(detail, dict):
error_msg = detail.get("message", error_msg)
except Exception:
error_msg = f"{error_msg} - {resp.text[:200]}"
return error_msg
def upload_to_newapi(
account: Account,
api_url: str,
api_key: str,
) -> Tuple[bool, str]:
base = _normalize_base(api_url)
if not base:
return False, "NEWAPI API URL 未配置"
if not api_key:
return False, "NEWAPI API Key 未配置"
if not account.access_token:
return False, "账号缺少 access_token"
url = f"{base}/api/channel/"
account_name = account.email or ""
channel = {
"auto_ban": 1,
"name": account.email or "",
"type": NEWAPI_TYPE_OPENAI,
"key": json.dumps({"access_token": account.access_token or "", "account_id": account_name}, ensure_ascii=False),
"base_url": DEFAULT_BASE_URL,
"models": DEFAULT_MODELS,
"multi_key_mode": "random",
"group": "default",
"groups": ["default"],
"priority": 0,
"weight": 0,
}
try:
resp = cffi_requests.post(
url,
headers=_build_headers(api_key),
json={"mode": "single", "channel": channel},
proxies=None,
timeout=30,
impersonate="chrome110",
)
if resp.status_code in (200, 201):
return True, "上传成功"
return False, _extract_error(resp)
except Exception as e:
logger.error("NEWAPI 上传异常: %s", e)
return False, f"上传异常: {str(e)}"
def batch_upload_to_newapi(
account_ids: List[int],
api_url: str,
api_key: str,
) -> dict:
results = {
"success_count": 0,
"failed_count": 0,
"skipped_count": 0,
"details": [],
}
with get_db() as db:
for account_id in account_ids:
account = db.query(Account).filter(Account.id == account_id).first()
if not account:
results["failed_count"] += 1
results["details"].append({"id": account_id, "email": None, "success": False, "error": "账号不存在"})
continue
if not account.access_token:
results["skipped_count"] += 1
results["details"].append({"id": account_id, "email": account.email, "success": False, "error": "缺少 Token"})
continue
success, message = upload_to_newapi(account, api_url, api_key)
if success:
account.newapi_uploaded = True
account.newapi_uploaded_at = datetime.utcnow()
db.commit()
results["success_count"] += 1
results["details"].append({"id": account_id, "email": account.email, "success": True, "message": message})
else:
results["failed_count"] += 1
results["details"].append({"id": account_id, "email": account.email, "success": False, "error": message})
return results

View File

@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
from sqlalchemy.orm.attributes import flag_modified
from sqlalchemy import and_, or_, desc, asc, func
from .models import Account, EmailService, RegistrationTask, Setting, Proxy, CpaService, Sub2ApiService
from .models import Account, EmailService, RegistrationTask, Setting, Proxy, CpaService, Sub2ApiService, NewapiService
TOKEN_FIELD_NAMES = ("access_token", "refresh_token", "id_token", "session_token")
@@ -781,6 +781,57 @@ def delete_tm_service(db: Session, service_id: int) -> bool:
return True
def create_newapi_service(
db: Session,
name: str,
api_url: str,
api_key: str,
enabled: bool = True,
priority: int = 0,
) -> NewapiService:
svc = NewapiService(
name=name,
api_url=api_url,
api_key=api_key,
enabled=enabled,
priority=priority,
)
db.add(svc)
db.commit()
db.refresh(svc)
return svc
def get_newapi_service_by_id(db: Session, service_id: int) -> Optional[NewapiService]:
return db.query(NewapiService).filter(NewapiService.id == service_id).first()
def get_newapi_services(db: Session, enabled=None):
q = db.query(NewapiService)
if enabled is not None:
q = q.filter(NewapiService.enabled == enabled)
return q.order_by(NewapiService.priority.asc(), NewapiService.id.asc()).all()
def update_newapi_service(db: Session, service_id: int, **kwargs):
svc = get_newapi_service_by_id(db, service_id)
if not svc:
return None
for k, v in kwargs.items():
setattr(svc, k, v)
db.commit()
db.refresh(svc)
return svc
def delete_newapi_service(db: Session, service_id: int) -> bool:
svc = get_newapi_service_by_id(db, service_id)
if not svc:
return False
db.delete(svc)
db.commit()
return True
def update_outlook_refresh_token(db: Session, service_id: int, email: str, new_refresh_token: str):
"""更新 EmailService.config 中指定邮箱的 refresh_token"""
service = db.query(EmailService).filter(EmailService.id == service_id).first()

View File

@@ -54,6 +54,8 @@ class Account(Base):
extra_data = Column(JSONEncodedDict) # 额外信息存储
cpa_uploaded = Column(Boolean, default=False) # 是否已上传到 CPA
cpa_uploaded_at = Column(DateTime) # 上传时间
newapi_uploaded = Column(Boolean, default=False)
newapi_uploaded_at = Column(DateTime)
source = Column(String(20), default='register') # 'register' 或 'login',区分账号来源
subscription_type = Column(String(20)) # None / 'plus' / 'team'
subscription_at = Column(DateTime) # 订阅开通时间
@@ -78,6 +80,8 @@ class Account(Base):
'proxy_used': self.proxy_used,
'cpa_uploaded': self.cpa_uploaded,
'cpa_uploaded_at': self.cpa_uploaded_at.isoformat() if self.cpa_uploaded_at else None,
'newapi_uploaded': self.newapi_uploaded,
'newapi_uploaded_at': self.newapi_uploaded_at.isoformat() if self.newapi_uploaded_at else None,
'source': self.source,
'subscription_type': self.subscription_type,
'subscription_at': self.subscription_at.isoformat() if self.subscription_at else None,
@@ -177,6 +181,20 @@ class TeamManagerService(Base):
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class NewapiService(Base):
"""NEWAPI如 New API服务配置表"""
__tablename__ = 'newapi_services'
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(100), nullable=False)
api_url = Column(String(500), nullable=False)
api_key = Column(Text, nullable=False)
enabled = Column(Boolean, default=True)
priority = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class Proxy(Base):
"""代理列表表"""
__tablename__ = 'proxies'

View File

@@ -112,6 +112,8 @@ class DatabaseSessionManager:
("accounts", "cookies", "TEXT"),
("accounts", "token_sync_status", "VARCHAR(20) DEFAULT 'not_ready'"),
("accounts", "token_sync_updated_at", "DATETIME"),
("accounts", "newapi_uploaded", "BOOLEAN DEFAULT 0"),
("accounts", "newapi_uploaded_at", "DATETIME"),
("proxies", "is_default", "BOOLEAN DEFAULT 0"),
("cpa_services", "include_proxy_url", "BOOLEAN DEFAULT 0"),
]

View File

@@ -12,6 +12,7 @@ from .payment import router as payment_router
from .upload.cpa_services import router as cpa_services_router
from .upload.sub2api_services import router as sub2api_services_router
from .upload.tm_services import router as tm_services_router
from .upload.newapi_services import router as newapi_services_router
api_router = APIRouter()
@@ -24,3 +25,4 @@ api_router.include_router(payment_router, prefix="/payment", tags=["payment"])
api_router.include_router(cpa_services_router, prefix="/cpa-services", tags=["cpa-services"])
api_router.include_router(sub2api_services_router, prefix="/sub2api-services", tags=["sub2api-services"])
api_router.include_router(tm_services_router, prefix="/tm-services", tags=["tm-services"])
api_router.include_router(newapi_services_router, prefix="/newapi-services", tags=["newapi-services"])

View File

@@ -19,6 +19,7 @@ from ...core.openai.token_refresh import validate_account_token as do_validate
from ...core.upload.cpa_upload import generate_token_json, batch_upload_to_cpa, upload_to_cpa
from ...core.upload.team_manager_upload import upload_to_team_manager, batch_upload_to_team_manager
from ...core.upload.sub2api_upload import batch_upload_to_sub2api, upload_to_sub2api
from ...core.upload.newapi_upload import upload_to_newapi, batch_upload_to_newapi
from ...core.dynamic_proxy import get_proxy_url_for_task
from ...database import crud
@@ -61,6 +62,8 @@ class AccountResponse(BaseModel):
proxy_used: Optional[str] = None
cpa_uploaded: bool = False
cpa_uploaded_at: Optional[str] = None
newapi_uploaded: bool = False
newapi_uploaded_at: Optional[str] = None
cookies: Optional[str] = None
created_at: Optional[str] = None
updated_at: Optional[str] = None
@@ -140,6 +143,8 @@ def account_to_response(account: Account) -> AccountResponse:
proxy_used=account.proxy_used,
cpa_uploaded=account.cpa_uploaded or False,
cpa_uploaded_at=account.cpa_uploaded_at.isoformat() if account.cpa_uploaded_at else None,
newapi_uploaded=account.newapi_uploaded or False,
newapi_uploaded_at=account.newapi_uploaded_at.isoformat() if account.newapi_uploaded_at else None,
cookies=account.cookies,
created_at=account.created_at.isoformat() if account.created_at else None,
updated_at=account.updated_at.isoformat() if account.updated_at else None,
@@ -974,6 +979,71 @@ async def upload_account_to_tm(account_id: int, request: Optional[UploadTMReques
return {"success": success, "message": message}
# ============== NEWAPI 上传 ==============
class UploadNewapiRequest(BaseModel):
service_id: Optional[int] = None
class BatchUploadNewapiRequest(BaseModel):
ids: List[int] = []
select_all: bool = False
status_filter: Optional[str] = None
email_service_filter: Optional[str] = None
search_filter: Optional[str] = None
service_id: Optional[int] = None
@router.post("/batch-upload-newapi")
async def batch_upload_accounts_to_newapi(request: BatchUploadNewapiRequest):
with get_db() as db:
if request.service_id:
svc = crud.get_newapi_service_by_id(db, request.service_id)
else:
svcs = crud.get_newapi_services(db, enabled=True)
svc = svcs[0] if svcs else None
if not svc:
raise HTTPException(status_code=400, detail="未找到可用的 NEWAPI 服务,请先在设置中配置")
api_url = svc.api_url
api_key = svc.api_key
ids = resolve_account_ids(
db, request.ids, request.select_all,
request.status_filter, request.email_service_filter, request.search_filter
)
results = batch_upload_to_newapi(ids, api_url, api_key)
return results
@router.post("/{account_id}/upload-newapi")
async def upload_account_to_newapi(account_id: int, request: Optional[UploadNewapiRequest] = Body(default=None)):
service_id = request.service_id if request else None
with get_db() as db:
if service_id:
svc = crud.get_newapi_service_by_id(db, service_id)
else:
svcs = crud.get_newapi_services(db, enabled=True)
svc = svcs[0] if svcs else None
if not svc:
raise HTTPException(status_code=400, detail="未找到可用的 NEWAPI 服务,请先在设置中配置")
account = crud.get_account_by_id(db, account_id)
if not account:
raise HTTPException(status_code=404, detail="账号不存在")
success, message = upload_to_newapi(account, svc.api_url, svc.api_key)
if success:
account.newapi_uploaded = True
account.newapi_uploaded_at = datetime.utcnow()
db.commit()
return {"success": success, "message": message}
# ============== Inbox Code ==============
def _build_inbox_config(db, service_type, email: str) -> dict:

View File

@@ -115,6 +115,8 @@ class RegistrationTaskCreate(BaseModel):
sub2api_service_ids: List[int] = [] # 指定 Sub2API 服务 ID 列表
auto_upload_tm: bool = False
tm_service_ids: List[int] = [] # 指定 TM 服务 ID 列表
auto_upload_newapi: bool = False
newapi_service_ids: List[int] = []
class BatchRegistrationRequest(BaseModel):
@@ -134,6 +136,8 @@ class BatchRegistrationRequest(BaseModel):
sub2api_service_ids: List[int] = []
auto_upload_tm: bool = False
tm_service_ids: List[int] = []
auto_upload_newapi: bool = False
newapi_service_ids: List[int] = []
class MockRegistrationCreateRequest(BaseModel):
@@ -216,6 +220,8 @@ class OutlookBatchRegistrationRequest(BaseModel):
sub2api_service_ids: List[int] = []
auto_upload_tm: bool = False
tm_service_ids: List[int] = []
auto_upload_newapi: bool = False
newapi_service_ids: List[int] = []
class OutlookBatchRegistrationResponse(BaseModel):
@@ -527,7 +533,7 @@ def _build_email_service_candidates(
return candidates
def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy: Optional[str], email_service_config: Optional[dict], email_service_id: Optional[int] = None, log_prefix: str = "", batch_id: str = "", auto_upload_cpa: bool = False, cpa_service_ids: List[int] = None, auto_upload_sub2api: bool = False, sub2api_service_ids: List[int] = None, auto_upload_tm: bool = False, tm_service_ids: List[int] = None):
def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy: Optional[str], email_service_config: Optional[dict], email_service_id: Optional[int] = None, log_prefix: str = "", batch_id: str = "", auto_upload_cpa: bool = False, cpa_service_ids: List[int] = None, auto_upload_sub2api: bool = False, sub2api_service_ids: List[int] = None, auto_upload_tm: bool = False, tm_service_ids: List[int] = None, auto_upload_newapi: bool = False, newapi_service_ids: List[int] = None):
"""
在线程池中执行的同步注册任务
@@ -774,6 +780,36 @@ def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy:
except Exception as tm_err:
log_callback(f"[TM] 上传异常: {tm_err}")
if auto_upload_newapi:
try:
from ...core.upload.newapi_upload import upload_to_newapi
from ...database.models import Account as AccountModel
saved_account = db.query(AccountModel).filter_by(email=result.email).first()
if saved_account and saved_account.access_token:
_na_ids = newapi_service_ids or []
if not _na_ids:
_na_ids = [s.id for s in crud.get_newapi_services(db, enabled=True)]
if not _na_ids:
log_callback("[NEWAPI] 无可用 NEWAPI 服务,跳过上传")
for _sid in _na_ids:
try:
_svc = crud.get_newapi_service_by_id(db, _sid)
if not _svc:
continue
log_callback(f"[NEWAPI] 上传到服务: {_svc.name}")
_ok, _msg = upload_to_newapi(saved_account, _svc.api_url, _svc.api_key)
if _ok:
saved_account.newapi_uploaded = True
saved_account.newapi_uploaded_at = datetime.utcnow()
db.commit()
log_callback(f"[NEWAPI] 上传成功: {_svc.name}")
else:
log_callback(f"[NEWAPI] 上传失败({_svc.name}): {_msg}")
except Exception as _e:
log_callback(f"[NEWAPI] 异常({_sid}): {_e}")
except Exception as na_err:
log_callback(f"[NEWAPI] 上传异常: {na_err}")
# 更新任务状态
crud.update_registration_task(
db, task_uuid,
@@ -831,7 +867,7 @@ def _run_sync_registration_task(task_uuid: str, email_service_type: str, proxy:
pass
async def run_registration_task(task_uuid: str, email_service_type: str, proxy: Optional[str], email_service_config: Optional[dict], email_service_id: Optional[int] = None, log_prefix: str = "", batch_id: str = "", auto_upload_cpa: bool = False, cpa_service_ids: List[int] = None, auto_upload_sub2api: bool = False, sub2api_service_ids: List[int] = None, auto_upload_tm: bool = False, tm_service_ids: List[int] = None):
async def run_registration_task(task_uuid: str, email_service_type: str, proxy: Optional[str], email_service_config: Optional[dict], email_service_id: Optional[int] = None, log_prefix: str = "", batch_id: str = "", auto_upload_cpa: bool = False, cpa_service_ids: List[int] = None, auto_upload_sub2api: bool = False, sub2api_service_ids: List[int] = None, auto_upload_tm: bool = False, tm_service_ids: List[int] = None, auto_upload_newapi: bool = False, newapi_service_ids: List[int] = None):
"""
异步执行注册任务
@@ -864,6 +900,8 @@ async def run_registration_task(task_uuid: str, email_service_type: str, proxy:
sub2api_service_ids or [],
auto_upload_tm,
tm_service_ids or [],
auto_upload_newapi,
newapi_service_ids or [],
)
except Exception as e:
logger.error(f"线程池执行异常: {task_uuid}, 错误: {e}")
@@ -1170,6 +1208,8 @@ async def run_batch_parallel(
sub2api_service_ids: List[int] = None,
auto_upload_tm: bool = False,
tm_service_ids: List[int] = None,
auto_upload_newapi: bool = False,
newapi_service_ids: List[int] = None,
):
"""
并行模式所有任务同时提交Semaphore 控制最大并发数
@@ -1189,6 +1229,7 @@ async def run_batch_parallel(
auto_upload_cpa=auto_upload_cpa, cpa_service_ids=cpa_service_ids or [],
auto_upload_sub2api=auto_upload_sub2api, sub2api_service_ids=sub2api_service_ids or [],
auto_upload_tm=auto_upload_tm, tm_service_ids=tm_service_ids or [],
auto_upload_newapi=auto_upload_newapi, newapi_service_ids=newapi_service_ids or [],
)
with get_db() as db:
t = crud.get_registration_task(db, uuid)
@@ -1239,6 +1280,8 @@ async def run_batch_pipeline(
sub2api_service_ids: List[int] = None,
auto_upload_tm: bool = False,
tm_service_ids: List[int] = None,
auto_upload_newapi: bool = False,
newapi_service_ids: List[int] = None,
):
"""
流水线模式:每隔 interval 秒启动一个新任务Semaphore 限制最大并发数
@@ -1258,6 +1301,7 @@ async def run_batch_pipeline(
auto_upload_cpa=auto_upload_cpa, cpa_service_ids=cpa_service_ids or [],
auto_upload_sub2api=auto_upload_sub2api, sub2api_service_ids=sub2api_service_ids or [],
auto_upload_tm=auto_upload_tm, tm_service_ids=tm_service_ids or [],
auto_upload_newapi=auto_upload_newapi, newapi_service_ids=newapi_service_ids or [],
)
with get_db() as db:
t = crud.get_registration_task(db, uuid)
@@ -1332,6 +1376,8 @@ async def run_batch_registration(
sub2api_service_ids: List[int] = None,
auto_upload_tm: bool = False,
tm_service_ids: List[int] = None,
auto_upload_newapi: bool = False,
newapi_service_ids: List[int] = None,
):
"""根据 mode 分发到并行或流水线执行"""
if mode == "parallel":
@@ -1341,6 +1387,7 @@ async def run_batch_registration(
auto_upload_cpa=auto_upload_cpa, cpa_service_ids=cpa_service_ids,
auto_upload_sub2api=auto_upload_sub2api, sub2api_service_ids=sub2api_service_ids,
auto_upload_tm=auto_upload_tm, tm_service_ids=tm_service_ids,
auto_upload_newapi=auto_upload_newapi, newapi_service_ids=newapi_service_ids,
)
else:
await run_batch_pipeline(
@@ -1350,6 +1397,7 @@ async def run_batch_registration(
auto_upload_cpa=auto_upload_cpa, cpa_service_ids=cpa_service_ids,
auto_upload_sub2api=auto_upload_sub2api, sub2api_service_ids=sub2api_service_ids,
auto_upload_tm=auto_upload_tm, tm_service_ids=tm_service_ids,
auto_upload_newapi=auto_upload_newapi, newapi_service_ids=newapi_service_ids,
)
@@ -1451,6 +1499,8 @@ async def start_registration(
request.sub2api_service_ids,
request.auto_upload_tm,
request.tm_service_ids,
request.auto_upload_newapi,
request.newapi_service_ids,
)
return task_to_response(task)
@@ -1528,6 +1578,8 @@ async def start_batch_registration(
request.sub2api_service_ids,
request.auto_upload_tm,
request.tm_service_ids,
request.auto_upload_newapi,
request.newapi_service_ids,
)
return BatchRegistrationResponse(
@@ -1925,6 +1977,8 @@ async def run_outlook_batch_registration(
sub2api_service_ids: List[int] = None,
auto_upload_tm: bool = False,
tm_service_ids: List[int] = None,
auto_upload_newapi: bool = False,
newapi_service_ids: List[int] = None,
):
"""
异步执行 Outlook 批量注册任务,复用通用并发逻辑
@@ -1968,6 +2022,8 @@ async def run_outlook_batch_registration(
sub2api_service_ids=sub2api_service_ids,
auto_upload_tm=auto_upload_tm,
tm_service_ids=tm_service_ids,
auto_upload_newapi=auto_upload_newapi,
newapi_service_ids=newapi_service_ids,
)
@@ -2066,6 +2122,8 @@ async def start_outlook_batch_registration(
request.sub2api_service_ids,
request.auto_upload_tm,
request.tm_service_ids,
request.auto_upload_newapi,
request.newapi_service_ids,
)
return OutlookBatchRegistrationResponse(

View File

@@ -0,0 +1,118 @@
"""
NEWAPI 服务管理 API 路由
"""
from typing import List, Optional
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from ....database import crud
from ....database.session import get_db
router = APIRouter()
class NewapiServiceCreate(BaseModel):
name: str
api_url: str
api_key: str
enabled: bool = True
priority: int = 0
class NewapiServiceUpdate(BaseModel):
name: Optional[str] = None
api_url: Optional[str] = None
api_key: Optional[str] = None
enabled: Optional[bool] = None
priority: Optional[int] = None
class NewapiServiceResponse(BaseModel):
id: int
name: str
api_url: str
has_key: bool
enabled: bool
priority: int
created_at: Optional[str] = None
updated_at: Optional[str] = None
class Config:
from_attributes = True
def _to_response(svc) -> NewapiServiceResponse:
return NewapiServiceResponse(
id=svc.id,
name=svc.name,
api_url=svc.api_url,
has_key=bool(svc.api_key),
enabled=svc.enabled,
priority=svc.priority,
created_at=svc.created_at.isoformat() if svc.created_at else None,
updated_at=svc.updated_at.isoformat() if svc.updated_at else None,
)
@router.get("", response_model=List[NewapiServiceResponse])
async def list_newapi_services(enabled: Optional[bool] = None):
with get_db() as db:
services = crud.get_newapi_services(db, enabled=enabled)
return [_to_response(s) for s in services]
@router.post("", response_model=NewapiServiceResponse)
async def create_newapi_service(request: NewapiServiceCreate):
with get_db() as db:
svc = crud.create_newapi_service(
db,
name=request.name,
api_url=request.api_url,
api_key=request.api_key,
enabled=request.enabled,
priority=request.priority,
)
return _to_response(svc)
@router.get("/{service_id}", response_model=NewapiServiceResponse)
async def get_newapi_service(service_id: int):
with get_db() as db:
svc = crud.get_newapi_service_by_id(db, service_id)
if not svc:
raise HTTPException(status_code=404, detail="NEWAPI 服务不存在")
return _to_response(svc)
@router.patch("/{service_id}", response_model=NewapiServiceResponse)
async def update_newapi_service(service_id: int, request: NewapiServiceUpdate):
with get_db() as db:
svc = crud.get_newapi_service_by_id(db, service_id)
if not svc:
raise HTTPException(status_code=404, detail="NEWAPI 服务不存在")
update_data = {}
if request.name is not None:
update_data["name"] = request.name
if request.api_url is not None:
update_data["api_url"] = request.api_url
if request.api_key:
update_data["api_key"] = request.api_key
if request.enabled is not None:
update_data["enabled"] = request.enabled
if request.priority is not None:
update_data["priority"] = request.priority
svc = crud.update_newapi_service(db, service_id, **update_data)
return _to_response(svc)
@router.delete("/{service_id}")
async def delete_newapi_service(service_id: int):
with get_db() as db:
svc = crud.get_newapi_service_by_id(db, service_id)
if not svc:
raise HTTPException(status_code=404, detail="NEWAPI 服务不存在")
crud.delete_newapi_service(db, service_id)
return {"success": True, "message": f"NEWAPI 服务 {svc.name} 已删除"}