Files
HyperNode-AI/app/schemas.py
T

361 lines
8.9 KiB
Python

"""接口请求与响应。金额在 JSON 里用字符串,避免浮点误差。"""
from __future__ import annotations
from datetime import date, datetime
from decimal import Decimal
from typing import Annotated
from pydantic import BaseModel, ConfigDict, Field, PlainSerializer
from app.models import DatacenterStatus, NodeStatus, OrderStatus, TaskStatus, UserRole, WithdrawalStatus, YieldStatus
SN = Annotated[str, Field(min_length=4, max_length=64, pattern=r"^[A-Za-z0-9_-]+$")]
WALLET = Annotated[str, Field(min_length=4, max_length=128, pattern=r"^[A-Za-z0-9:_-]+$")]
USERNAME = Annotated[str, Field(min_length=3, max_length=32, pattern=r"^[A-Za-z0-9_]+$")]
MoneyText = Annotated[Decimal, PlainSerializer(lambda value: format(value, "f"), return_type=str)]
class APIModel(BaseModel):
model_config = ConfigDict(from_attributes=True)
class UserCreate(APIModel):
wallet_address: WALLET
class UserOut(APIModel):
id: int
wallet_address: str
username: str | None = None
role: UserRole = UserRole.USER
created_at: datetime
class RegisterIn(APIModel):
wallet_address: WALLET
username: USERNAME | None = None
password: str | None = Field(default=None, max_length=72)
class LoginIn(APIModel):
wallet_address: str | None = Field(default=None, max_length=128)
username: str | None = Field(default=None, max_length=32)
password: str | None = Field(default=None, max_length=72)
class TokenOut(APIModel):
access_token: str
token_type: str = "bearer"
role: UserRole
wallet_address: str
username: str | None = None
class AdminUserOut(APIModel):
id: int
wallet_address: str
username: str | None
role: UserRole
created_at: datetime
node_count: int
available_points: MoneyText
class NodeCreate(APIModel):
wallet_address: WALLET
sn: SN
purchase_amount: Decimal = Field(gt=0, max_digits=24, decimal_places=8)
callback_url: str | None = None
class NodeOut(APIModel):
id: int
user_id: int
sn: str
node_index: int = 0
display_node_id: int = 0
genesis: bool = False
purchase_amount: MoneyText
license_months: int
licensed_at: datetime
license_expires_at: datetime
status: NodeStatus
last_heartbeat_at: datetime | None
callback_url: str | None
gpu_model: str | None = None
vram_gb: float | None = None
cpu_count: int | None = None
memory_gb: float | None = None
hardware_weight: float = 0.05
gpu_generation: str = "10"
recovery_days: int = 540
generation_locked: bool = False
recoverable_stake_amount: MoneyText | None = None
is_whitelist: bool = False
is_banned: bool = False
client_ip: str | None = None
hardware_fingerprint: str | None = None
class NodeView(NodeOut):
wallet_address: str
online_rate: MoneyText = Decimal("0")
today_points: MoneyText = Decimal("0")
node_token: str | None = None
activated: bool = False
class HeartbeatIn(APIModel):
sn: SN
token: str | None = Field(default=None, max_length=128)
gpu_model: str | None = Field(default=None, max_length=128)
vram_gb: float | None = Field(default=None, ge=0, le=1024)
cpu_count: int | None = Field(default=None, ge=1, le=1024)
memory_gb: float | None = Field(default=None, ge=0, le=4096)
hardware_fingerprint: str | None = Field(default=None, max_length=64, pattern=r"^[a-fA-F0-9]{64}$")
class HeartbeatOut(APIModel):
sn: str
display_node_id: int = 0
status: NodeStatus
last_heartbeat_at: datetime
server_time: datetime
hardware_weight: float = 0.05
gpu_generation: str = "10"
recovery_days: int = 540
recoverable_stake_amount: MoneyText | None = None
purchase_amount: MoneyText | None = None
estimated_daily_points: MoneyText | None = None
class TaskOut(APIModel):
task_id: str
task_type: str
payload: dict
status: TaskStatus
dispatched_at: datetime
deadline_at: datetime
class TaskReportIn(APIModel):
sn: SN
task_id: str = Field(min_length=36, max_length=36)
success: bool
nonce: str = Field(min_length=8, max_length=128)
token: str | None = Field(default=None, max_length=128)
result: dict | None = None
class TaskCreate(APIModel):
task_type: str = Field(min_length=1, max_length=32)
target_node_sn: str | None = Field(default=None, max_length=64)
payload: dict = Field(default_factory=dict)
class AdminTaskOut(APIModel):
task_id: str
task_type: str
target_node_sn: str
display_node_id: int
status: TaskStatus
payload: dict
result: dict | None = None
result_preview: str = ""
dispatched_at: datetime
deadline_at: datetime
responded_at: datetime | None = None
class ContributionOut(APIModel):
sn: str
yield_date: date
eligible: bool
online_seconds: int
eligible_seconds: int
online_rate: MoneyText
tasks_dispatched: int
tasks_completed: int
task_response_rate: MoneyText
online_weight: MoneyText
task_weight: MoneyText
performance: MoneyText
compute_output: MoneyText
points_delta: MoneyText
cycle_days: int
class YieldOut(APIModel):
id: int
node_id: int
user_id: int
yield_date: date
compute_output: MoneyText
points_delta: MoneyText
online_seconds: int
eligible_seconds: int
online_rate: MoneyText
tasks_dispatched: int
tasks_completed: int
task_response_rate: MoneyText
online_weight: MoneyText
task_weight: MoneyText
performance: MoneyText
cycle_days: int
status: YieldStatus
is_partial: bool
created_at: datetime
class SettleOut(APIModel):
yield_date: date
created: int
skipped: int
total_points: MoneyText
is_partial: bool
class WithdrawalCreate(APIModel):
wallet_address: WALLET
amount: Decimal = Field(gt=0, max_digits=24, decimal_places=8)
asset_symbol: str = Field(default="POINT", min_length=1, max_length=32)
class MeWithdrawalCreate(APIModel):
amount: Decimal = Field(gt=0, max_digits=24, decimal_places=8)
asset_symbol: str = Field(default="POINT", min_length=1, max_length=32)
class MeNodeCreate(APIModel):
sn: SN
purchase_amount: Decimal = Field(gt=0, max_digits=24, decimal_places=8)
callback_url: str | None = None
class OrderCreate(APIModel):
tx_id: str = Field(min_length=6, max_length=128, pattern=r"^[A-Za-z0-9]+$")
class OrderOut(APIModel):
id: int
user_id: int
amount: MoneyText
asset_symbol: str
status: OrderStatus
tx_id: str
created_at: datetime
reviewed_at: datetime | None = None
sn: str | None = None
display_node_id: int | None = None
class OrderView(OrderOut):
wallet_address: str
class WithdrawalConfirm(APIModel):
tx_hash: str = Field(min_length=6, max_length=128, pattern=r"^[A-Za-z0-9]+$")
class WithdrawalReject(APIModel):
note: str | None = Field(default=None, max_length=500)
class WithdrawalOut(APIModel):
id: int
user_id: int
amount: MoneyText
asset_symbol: str
tx_hash: str | None
status: WithdrawalStatus
note: str | None
available_after: MoneyText | None = None
created_at: datetime
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
class DashboardOut(APIModel):
wallet_address: str
username: str | None
role: UserRole
available_points: MoneyText
settled_points: MoneyText
today_points: MoneyText
license_price: MoneyText
license_asset: str
network_nodes: int = 0
network_online: int = 0
nodes: list[NodeView]
orders: list[OrderOut]
withdrawals: list[WithdrawalOut]
datacenter_applications: list[DatacenterOut] = []
class WhitelistToggle(APIModel):
is_whitelist: bool
class BanToggle(APIModel):
is_banned: bool
class IPWhitelistCreate(APIModel):
ip_address: str = Field(min_length=3, max_length=64)
note: str | None = Field(default=None, max_length=128)
class IPWhitelistOut(APIModel):
id: int
ip_address: str
note: str | None = None
created_at: datetime
class DatacenterApply(APIModel):
contact: str = Field(min_length=4, max_length=128)
node_count: int = Field(ge=1, le=10000)
subnet_ip: str = Field(min_length=3, max_length=64)
hardware_proof: str = Field(min_length=4, max_length=2000)
gpu_generation: str = Field(pattern=r"^(10|30|40)$")
class DatacenterOut(APIModel):
id: int
user_id: int
contact: str
node_count: int
subnet_ip: str
hardware_proof: str
gpu_generation: str
status: DatacenterStatus
note: str | None = None
created_at: datetime
reviewed_at: datetime | None = None
class DatacenterView(DatacenterOut):
wallet_address: str
DashboardOut.model_rebuild()