"""用户与节点登记。""" from __future__ import annotations from decimal import Decimal from urllib.parse import urlparse from sqlalchemy import select from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session from app.clock import add_months, utcnow from app.config import settings from app.errors import AppError from app.models import ComputeNode, NodeStatus, User, UserRole from app.money import money from app.services.common import get_user_by_wallet def register_user( session: Session, wallet_address: str, username: str | None = None, password_hash: str | None = None, role: UserRole = UserRole.USER, ) -> User: wallet = wallet_address.strip() if session.scalar(select(User).where(User.wallet_address == wallet)) is not None: raise AppError("钱包地址已注册", 409) if username and session.scalar(select(User).where(User.username == username)) is not None: raise AppError("用户名已占用", 409) user = User( wallet_address=wallet, username=username, password_hash=password_hash, role=role, created_at=utcnow(), ) session.add(user) try: session.flush() except IntegrityError as exc: session.rollback() raise AppError("钱包地址或用户名已注册", 409) from exc return user def register_node( session: Session, wallet_address: str, sn: str, purchase_amount: Decimal, callback_url: str | None = None, node_token: str | None = None, ) -> ComputeNode: user = get_user_by_wallet(session, wallet_address.strip()) serial = sn.strip() if session.scalar(select(ComputeNode).where(ComputeNode.sn == serial)) is not None: raise AppError("节点序列号已存在", 409) callback = _normalize_callback(callback_url) started = utcnow() node = ComputeNode( user_id=user.id, sn=serial, purchase_amount=money(purchase_amount), license_months=settings.license_months, licensed_at=started, license_expires_at=add_months(started, settings.license_months), status=NodeStatus.OFFLINE, callback_url=callback, node_token=node_token, created_at=started, updated_at=started, ) session.add(node) try: session.flush() except IntegrityError as exc: session.rollback() raise AppError("节点序列号已存在", 409) from exc return node def _normalize_callback(callback_url: str | None) -> str | None: if callback_url is None or callback_url.strip() == "": return None parsed = urlparse(callback_url.strip()) if parsed.scheme not in {"http", "https"} or not parsed.netloc: raise AppError("callback_url 必须是 http 或 https 地址") return callback_url.strip()