Files
HyperNode-AI/app/api.py
T
dekun 2bbdb00a4d 初始化 HyperNode-AI 控制面。
包含节点账本、每日清算,以及 Ubuntu 交互式部署脚本。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-10 21:53:20 +08:00

133 lines
4.5 KiB
Python

"""对外 HTTP 接口。业务规则在 services 中,这里只做参数转发。"""
from datetime import date, timedelta
from typing import Annotated
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.clock import utcnow
from app.database import get_db
from app.models import ComputeTask
from app.schemas import (
BalanceOut,
ContributionOut,
HeartbeatIn,
HeartbeatOut,
NodeCreate,
NodeOut,
SettleOut,
TaskOut,
TaskReportIn,
UserCreate,
UserOut,
WithdrawalConfirm,
WithdrawalCreate,
WithdrawalOut,
WithdrawalReject,
YieldOut,
)
from app.services.common import get_node_by_sn, get_user_by_wallet
from app.services.heartbeat import record_heartbeat
from app.services.registry import register_node, register_user
from app.services.tasks import list_pending_tasks, report_task
from app.services.withdrawals import available_balance, confirm_withdrawal, reject_withdrawal, request_withdrawal
from app.services.yield_engine import compute_node_yield, list_node_yields, settle_yield_date
router = APIRouter(prefix="/api/v1")
Db = Annotated[Session, Depends(get_db)]
@router.post("/users", response_model=UserOut)
def create_user(body: UserCreate, db: Db) -> UserOut:
return register_user(db, body.wallet_address)
@router.post("/nodes", response_model=NodeOut)
def create_node(body: NodeCreate, db: Db) -> NodeOut:
return register_node(db, body.wallet_address, body.sn, body.purchase_amount, body.callback_url)
@router.get("/nodes/{sn}", response_model=NodeOut)
def get_node(sn: str, db: Db) -> NodeOut:
return get_node_by_sn(db, sn)
@router.post("/node/heartbeat", response_model=HeartbeatOut)
def heartbeat(body: HeartbeatIn, db: Db) -> HeartbeatOut:
node = record_heartbeat(db, body.sn)
return HeartbeatOut(
sn=node.sn,
status=node.status,
last_heartbeat_at=node.last_heartbeat_at,
server_time=node.last_heartbeat_at,
)
@router.get("/node/{sn}/tasks", response_model=list[TaskOut])
def pending_tasks(sn: str, db: Db) -> list[TaskOut]:
return [_task_out(task) for task in list_pending_tasks(db, sn)]
@router.post("/node/task/report", response_model=TaskOut)
def report(body: TaskReportIn, db: Db) -> TaskOut:
return _task_out(report_task(db, body.sn, body.task_id, body.success, body.nonce))
@router.get("/nodes/{sn}/contribution", response_model=ContributionOut)
def contribution(sn: str, yield_date: date, db: Db) -> ContributionOut:
node = get_node_by_sn(db, sn)
computed = compute_node_yield(db, node, yield_date)
return ContributionOut(sn=node.sn, **computed.__dict__)
@router.post("/yield/settle", response_model=SettleOut)
def settle(db: Db, yield_date: date | None = None) -> SettleOut:
target = yield_date or (utcnow().date() - timedelta(days=1))
summary = settle_yield_date(db, target)
return SettleOut(
yield_date=summary.yield_date,
created=summary.created,
skipped=summary.skipped,
total_points=summary.total_points,
is_partial=summary.is_partial,
)
@router.get("/nodes/{sn}/yields", response_model=list[YieldOut])
def yields(sn: str, db: Db) -> list[YieldOut]:
return list_node_yields(db, sn)
@router.get("/users/{wallet_address}/balance", response_model=BalanceOut)
def balance(wallet_address: str, db: Db) -> BalanceOut:
user = get_user_by_wallet(db, wallet_address)
return BalanceOut(wallet_address=user.wallet_address, available_points=available_balance(db, user.id))
@router.post("/withdrawals", response_model=WithdrawalOut)
def create_withdrawal(body: WithdrawalCreate, db: Db) -> WithdrawalOut:
row, remaining = request_withdrawal(db, body.wallet_address, body.amount, body.asset_symbol)
return WithdrawalOut.model_validate(row).model_copy(update={"available_after": remaining})
@router.post("/withdrawals/{withdrawal_id}/confirm", response_model=WithdrawalOut)
def confirm(withdrawal_id: int, body: WithdrawalConfirm, db: Db) -> WithdrawalOut:
return confirm_withdrawal(db, withdrawal_id, body.tx_hash)
@router.post("/withdrawals/{withdrawal_id}/reject", response_model=WithdrawalOut)
def reject(withdrawal_id: int, body: WithdrawalReject, db: Db) -> WithdrawalOut:
return reject_withdrawal(db, withdrawal_id, body.note)
def _task_out(task: ComputeTask) -> TaskOut:
return TaskOut(
task_id=task.id,
task_type=task.task_type,
payload=task.payload,
status=task.status,
dispatched_at=task.dispatched_at,
deadline_at=task.deadline_at,
)