This commit is contained in:
Your Name
2026-04-16 13:05:07 +08:00
commit 32efff1670
99 changed files with 9974 additions and 0 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,23 @@
from fastapi import APIRouter
from app.core.config import settings
from app.schemas.auth import LoginRequest
from app.schemas.common import ApiResponse
router = APIRouter(tags=["auth"])
@router.post("/auth/login", response_model=ApiResponse)
def login(payload: LoginRequest) -> ApiResponse:
if payload.username != settings.admin_username or payload.password != settings.admin_password:
return ApiResponse(code=1, message="账号或密码错误", data=None)
token = f"domain-web-token-{payload.username}"
return ApiResponse(
data={
"access_token": token,
"user": {
"username": payload.username,
"display_name": "管理员" if payload.username == "admin" else payload.username,
},
}
)

View File

@@ -0,0 +1,11 @@
from fastapi import APIRouter
from app.schemas.common import ApiResponse
from app.services.dashboard import fetch_overview
router = APIRouter(tags=["dashboard"])
@router.get("/dashboard/overview", response_model=ApiResponse)
def overview() -> ApiResponse:
return ApiResponse(data=fetch_overview())

View File

@@ -0,0 +1,40 @@
from fastapi import APIRouter
from app.schemas.common import ApiResponse
from app.services.detect_service import get_detect_status
from app.services.worker_control_service import start_worker, stop_worker
router = APIRouter(tags=["detect"])
@router.get("/detect/status", response_model=ApiResponse)
def detect_status() -> ApiResponse:
return ApiResponse(data=get_detect_status())
@router.post("/detect/start", response_model=ApiResponse)
def start_detect() -> ApiResponse:
ok, message = start_worker()
return ApiResponse(
code=0 if ok else 1,
message=message,
data={
"action": "start",
"poll_after_seconds": 2,
"refresh_status": True,
},
)
@router.post("/detect/stop", response_model=ApiResponse)
def stop_detect() -> ApiResponse:
ok, message = stop_worker()
return ApiResponse(
code=0 if ok else 1,
message=message,
data={
"action": "stop",
"poll_after_seconds": 2,
"refresh_status": True,
},
)

View File

@@ -0,0 +1,50 @@
from fastapi import APIRouter, Query
from app.schemas.common import ApiResponse
from app.services.domains_service import batch_update_domains, domain_filter_options, fetch_domains
router = APIRouter(tags=["domains"])
@router.get("/domains/filters", response_model=ApiResponse)
def domain_filters() -> ApiResponse:
return ApiResponse(data=domain_filter_options())
@router.get("/domains", response_model=ApiResponse)
def domain_list(
page: int = Query(default=1, ge=1),
page_size: int = Query(default=20, ge=1, le=200),
domain_keyword: str | None = Query(default=None),
register_status: int | None = Query(default=None),
detect_status: int | None = Query(default=None),
use_status: int | None = Query(default=None),
review_status: int | None = Query(default=None),
has_beian: int | None = Query(default=None),
beian_year: int | None = Query(default=None),
snapshot_year: str | None = Query(default=None),
website_url: str | None = Query(default=None),
backlink_gt_10: bool | None = Query(default=None),
) -> ApiResponse:
return ApiResponse(
data=fetch_domains(
page=page,
page_size=page_size,
domain_keyword=domain_keyword,
register_status=register_status,
detect_status=detect_status,
use_status=use_status,
review_status=review_status,
has_beian=has_beian,
beian_year=beian_year,
snapshot_year=snapshot_year,
website_url=website_url,
backlink_gt_10=backlink_gt_10,
)
)
@router.post("/domains/batch-update", response_model=ApiResponse)
def domain_batch_update(payload: dict) -> ApiResponse:
result = batch_update_domains(payload.get("domain_ids", []), payload.get("updates", {}))
return ApiResponse(message=f"成功更新 {result['updated_count']} 个域名", data=result)

View File

@@ -0,0 +1,29 @@
from pathlib import Path
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from app.schemas.common import ApiResponse
from app.services.export_service import create_export_file, list_exports
from app.core.files import exports_root
router = APIRouter(tags=["exports"])
@router.get("/exports", response_model=ApiResponse)
def export_list() -> ApiResponse:
return ApiResponse(data=list_exports())
@router.post("/exports/run", response_model=ApiResponse)
def export_run(payload: dict) -> ApiResponse:
record = create_export_file(payload)
return ApiResponse(message="导出文件已生成", data=record)
@router.get("/exports/download/{filename}")
def export_download(filename: str):
path = exports_root() / Path(filename).name
if not path.exists():
raise HTTPException(status_code=404, detail="文件不存在")
return FileResponse(path, filename=path.name)

View File

@@ -0,0 +1,30 @@
from fastapi import APIRouter, File, UploadFile
from app.schemas.common import ApiResponse
from app.services.import_task_service import create_import_task, list_import_tasks, retry_import_task
from app.services.imports_service import get_import_summary
router = APIRouter(tags=["imports"])
@router.get("/imports/summary", response_model=ApiResponse)
def imports_summary() -> ApiResponse:
return ApiResponse(data=get_import_summary())
@router.get("/imports/tasks", response_model=ApiResponse)
def import_tasks() -> ApiResponse:
return ApiResponse(data=list_import_tasks())
@router.post("/imports/upload", response_model=ApiResponse)
async def upload_import(file: UploadFile = File(...)) -> ApiResponse:
content = await file.read()
task = create_import_task(content, file.filename or "domains.txt")
return ApiResponse(message="导入任务已创建", data=task)
@router.post("/imports/tasks/{task_id}/retry", response_model=ApiResponse)
def import_task_retry(task_id: str) -> ApiResponse:
task = retry_import_task(task_id)
return ApiResponse(message="导入任务已重新加入队列", data=task)

View File

@@ -0,0 +1,18 @@
from fastapi import APIRouter
from fastapi.responses import FileResponse
from app.schemas.common import ApiResponse
from app.services.logs_service import build_diagnostic_bundle, latest_logs as get_latest_logs
router = APIRouter(tags=["logs"])
@router.get("/logs/latest", response_model=ApiResponse)
def latest_logs() -> ApiResponse:
return ApiResponse(data=get_latest_logs())
@router.get("/logs/bundle")
def download_logs_bundle() -> FileResponse:
path, filename = build_diagnostic_bundle()
return FileResponse(path=path, filename=filename, media_type="application/zip")

View File

@@ -0,0 +1,23 @@
from fastapi import APIRouter
from app.schemas.common import ApiResponse
from app.services.runtime_control_service import runtime_action
from app.services.runtime_status_service import get_runtime_preflight, get_runtime_status
router = APIRouter(tags=["runtime"])
@router.get("/runtime/status", response_model=ApiResponse)
def runtime_status() -> ApiResponse:
return ApiResponse(data=get_runtime_status())
@router.get("/runtime/preflight", response_model=ApiResponse)
def runtime_preflight() -> ApiResponse:
return ApiResponse(data=get_runtime_preflight())
@router.post("/runtime/actions/{action}", response_model=ApiResponse)
def runtime_action_trigger(action: str) -> ApiResponse:
ok, message, data = runtime_action(action)
return ApiResponse(code=0 if ok else 1, message=message, data=data)

View File

@@ -0,0 +1,69 @@
from pathlib import Path
from fastapi import APIRouter, HTTPException
from fastapi.responses import FileResponse
from app.core.files import settings_backup_root
from app.schemas.common import ApiResponse
from app.services.settings_service import (
backup_current_settings,
export_settings_snapshot,
get_settings_payload,
import_settings_snapshot,
list_settings_backups,
update_settings_payload,
validate_settings_payload,
)
router = APIRouter(tags=["settings"])
@router.get("/settings", response_model=ApiResponse)
def get_settings() -> ApiResponse:
return ApiResponse(data=get_settings_payload())
@router.put("/settings", response_model=ApiResponse)
def update_settings(payload: dict) -> ApiResponse:
return ApiResponse(message="settings updated", data=update_settings_payload(payload))
@router.get("/settings/export", response_model=ApiResponse)
def export_settings() -> ApiResponse:
return ApiResponse(message="settings exported", data=export_settings_snapshot())
@router.post("/settings/import", response_model=ApiResponse)
def import_settings(payload: dict) -> ApiResponse:
try:
settings_payload = import_settings_snapshot(payload)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return ApiResponse(message="settings imported", data=settings_payload)
@router.post("/settings/validate-import", response_model=ApiResponse)
def validate_import_settings(payload: dict) -> ApiResponse:
try:
validate_settings_payload(payload.get("settings", payload))
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc)) from exc
return ApiResponse(message="settings payload valid", data={"valid": True})
@router.post("/settings/backup", response_model=ApiResponse)
def create_settings_backup() -> ApiResponse:
return ApiResponse(message="settings backed up", data=backup_current_settings("manual"))
@router.get("/settings/backups", response_model=ApiResponse)
def get_settings_backups() -> ApiResponse:
return ApiResponse(data=list_settings_backups())
@router.get("/settings/backups/download/{filename}")
def download_settings_backup(filename: str):
path = settings_backup_root() / Path(filename).name
if not path.exists():
raise HTTPException(status_code=404, detail="backup not found")
return FileResponse(path, filename=path.name)