"""接口请求与响应。金额在 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 NodeStatus, TaskStatus, 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:_-]+$")] 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 created_at: datetime 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 purchase_amount: MoneyText license_months: int licensed_at: datetime license_expires_at: datetime status: NodeStatus last_heartbeat_at: datetime | None callback_url: str | None class HeartbeatIn(APIModel): sn: SN class HeartbeatOut(APIModel): sn: str status: NodeStatus last_heartbeat_at: datetime server_time: datetime 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) 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 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 BalanceOut(APIModel): wallet_address: str available_points: MoneyText