+70
-2
@@ -1,14 +1,17 @@
|
||||
"""对外 HTTP 接口。业务规则在 services 中,这里只做参数转发。"""
|
||||
|
||||
from datetime import date, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.clock import utcnow
|
||||
from app.database import get_db
|
||||
from app.models import ComputeTask
|
||||
from app.models import ComputeNode, ComputeTask, NodeStatus, NodeYield, User, WithdrawalLog, WithdrawalStatus, YieldStatus
|
||||
from app.money import money, to_decimal
|
||||
from app.schemas import (
|
||||
BalanceOut,
|
||||
ContributionOut,
|
||||
@@ -16,7 +19,9 @@ from app.schemas import (
|
||||
HeartbeatOut,
|
||||
NodeCreate,
|
||||
NodeOut,
|
||||
NodeView,
|
||||
SettleOut,
|
||||
SummaryOut,
|
||||
TaskOut,
|
||||
TaskReportIn,
|
||||
UserCreate,
|
||||
@@ -25,6 +30,7 @@ from app.schemas import (
|
||||
WithdrawalCreate,
|
||||
WithdrawalOut,
|
||||
WithdrawalReject,
|
||||
WithdrawalView,
|
||||
YieldOut,
|
||||
)
|
||||
from app.services.common import get_node_by_sn, get_user_by_wallet
|
||||
@@ -38,11 +44,51 @@ router = APIRouter(prefix="/api/v1")
|
||||
Db = Annotated[Session, Depends(get_db)]
|
||||
|
||||
|
||||
@router.get("/summary", response_model=SummaryOut)
|
||||
def summary(db: Db) -> SummaryOut:
|
||||
users = db.scalar(select(func.count()).select_from(User)) or 0
|
||||
nodes = db.scalar(select(func.count()).select_from(ComputeNode)) or 0
|
||||
online = db.scalar(
|
||||
select(func.count()).select_from(ComputeNode).where(ComputeNode.status == NodeStatus.ONLINE)
|
||||
) or 0
|
||||
authorized = _sum(db.scalars(select(ComputeNode.purchase_amount)).all())
|
||||
settled = _sum(
|
||||
db.scalars(select(NodeYield.points_delta).where(NodeYield.status == YieldStatus.SETTLED)).all()
|
||||
)
|
||||
pending = _sum(
|
||||
db.scalars(
|
||||
select(WithdrawalLog.amount).where(WithdrawalLog.status == WithdrawalStatus.PENDING)
|
||||
).all()
|
||||
)
|
||||
return SummaryOut(
|
||||
users=users,
|
||||
nodes=nodes,
|
||||
online=online,
|
||||
offline=nodes - online,
|
||||
authorized_amount=authorized,
|
||||
settled_points=settled,
|
||||
pending_withdrawals=pending,
|
||||
)
|
||||
|
||||
|
||||
@router.get("/users", response_model=list[UserOut])
|
||||
def list_users(db: Db) -> list[User]:
|
||||
return list(db.scalars(select(User).order_by(User.id.desc())).all())
|
||||
|
||||
|
||||
@router.post("/users", response_model=UserOut)
|
||||
def create_user(body: UserCreate, db: Db) -> UserOut:
|
||||
return register_user(db, body.wallet_address)
|
||||
|
||||
|
||||
@router.get("/nodes", response_model=list[NodeView])
|
||||
def list_nodes(db: Db) -> list[NodeView]:
|
||||
rows = db.scalars(
|
||||
select(ComputeNode).options(selectinload(ComputeNode.user)).order_by(ComputeNode.id.desc())
|
||||
).all()
|
||||
return [_node_view(row) for row in rows]
|
||||
|
||||
|
||||
@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)
|
||||
@@ -99,6 +145,20 @@ def yields(sn: str, db: Db) -> list[YieldOut]:
|
||||
return list_node_yields(db, sn)
|
||||
|
||||
|
||||
@router.get("/withdrawals", response_model=list[WithdrawalView])
|
||||
def list_withdrawals(db: Db) -> list[WithdrawalView]:
|
||||
rows = db.scalars(
|
||||
select(WithdrawalLog).options(selectinload(WithdrawalLog.user)).order_by(WithdrawalLog.id.desc())
|
||||
).all()
|
||||
return [
|
||||
WithdrawalView(
|
||||
**WithdrawalOut.model_validate(row).model_dump(),
|
||||
wallet_address=row.user.wallet_address,
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
|
||||
@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)
|
||||
@@ -121,6 +181,14 @@ def reject(withdrawal_id: int, body: WithdrawalReject, db: Db) -> WithdrawalOut:
|
||||
return reject_withdrawal(db, withdrawal_id, body.note)
|
||||
|
||||
|
||||
def _node_view(node: ComputeNode) -> NodeView:
|
||||
return NodeView(**NodeOut.model_validate(node).model_dump(), wallet_address=node.user.wallet_address)
|
||||
|
||||
|
||||
def _sum(values: list[object]) -> Decimal:
|
||||
return money(sum((to_decimal(item) for item in values), Decimal("0")))
|
||||
|
||||
|
||||
def _task_out(task: ComputeTask) -> TaskOut:
|
||||
return TaskOut(
|
||||
task_id=task.id,
|
||||
|
||||
+12
-2
@@ -4,9 +4,11 @@ from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.responses import FileResponse, JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
|
||||
import app.models # noqa: F401 注册全部表
|
||||
from app.api import router
|
||||
@@ -27,8 +29,16 @@ async def lifespan(_: FastAPI):
|
||||
stop_scheduler()
|
||||
|
||||
|
||||
app = FastAPI(title="DePIN 算力节点与任务调度", version="1.0.0", lifespan=lifespan)
|
||||
WEB_DIR = Path(__file__).resolve().parent.parent / "web"
|
||||
|
||||
app = FastAPI(title="HyperNode-AI", version="1.0.0", lifespan=lifespan)
|
||||
app.include_router(router)
|
||||
app.mount("/static", StaticFiles(directory=WEB_DIR), name="static")
|
||||
|
||||
|
||||
@app.get("/")
|
||||
def console() -> FileResponse:
|
||||
return FileResponse(WEB_DIR / "index.html")
|
||||
|
||||
|
||||
@app.exception_handler(AppError)
|
||||
|
||||
@@ -49,6 +49,10 @@ class NodeOut(APIModel):
|
||||
callback_url: str | None
|
||||
|
||||
|
||||
class NodeView(NodeOut):
|
||||
wallet_address: str
|
||||
|
||||
|
||||
class HeartbeatIn(APIModel):
|
||||
sn: SN
|
||||
|
||||
@@ -151,6 +155,20 @@ class WithdrawalOut(APIModel):
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class WithdrawalView(WithdrawalOut):
|
||||
wallet_address: str
|
||||
|
||||
|
||||
class BalanceOut(APIModel):
|
||||
wallet_address: str
|
||||
available_points: MoneyText
|
||||
|
||||
|
||||
class SummaryOut(APIModel):
|
||||
users: int
|
||||
nodes: int
|
||||
online: int
|
||||
offline: int
|
||||
authorized_amount: MoneyText
|
||||
settled_points: MoneyText
|
||||
pending_withdrawals: MoneyText
|
||||
|
||||
Reference in New Issue
Block a user