"""节点授权订单。付款凭证先入库,管理员通过后才开通节点。""" from __future__ import annotations import json import secrets from sqlalchemy import case, select from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session, selectinload from app.clock import utcnow from app.config import settings from app.errors import AppError from app.models import LicenseOrder, OrderStatus from app.money import money from app.services.registry import register_node def place_order(session: Session, user_id: int, tx_id: str) -> LicenseOrder: digest = tx_id.strip() if session.scalar(select(LicenseOrder.id).where(LicenseOrder.tx_id == digest)) is not None: raise AppError("这笔交易已经提交过", 409) order = LicenseOrder( user_id=user_id, amount=money(settings.license_price), asset_symbol=settings.license_asset.strip().upper() or "USDT", status=OrderStatus.PENDING, tx_id=digest, created_at=utcnow(), ) session.add(order) try: session.flush() except IntegrityError as exc: session.rollback() raise AppError("这笔交易已经提交过", 409) from exc return order def approve_order(session: Session, order_id: int) -> LicenseOrder: order = _pending_order(session, order_id) token = secrets.token_urlsafe(24) node = register_node( session, order.user.wallet_address, f"HN{order.id:06d}", order.amount, node_token=token, ) order.status = OrderStatus.APPROVED order.node = node order.reviewed_at = utcnow() session.flush() return order def reject_order(session: Session, order_id: int, note: str | None = None) -> LicenseOrder: order = _pending_order(session, order_id) order.status = OrderStatus.REJECTED order.note = note order.reviewed_at = utcnow() session.flush() return order def list_orders(session: Session, user_id: int | None = None) -> list[LicenseOrder]: pending_first = case((LicenseOrder.status == OrderStatus.PENDING, 0), else_=1) query = select(LicenseOrder).options(selectinload(LicenseOrder.user), selectinload(LicenseOrder.node)) if user_id is not None: query = query.where(LicenseOrder.user_id == user_id) return list(session.scalars(query.order_by(pending_first, LicenseOrder.id.desc())).all()) def render_client(sn: str, token: str, base_url: str) -> str: config = json.dumps({"sn": sn, "token": token, "base": base_url.rstrip("/")}, ensure_ascii=True) return f"""# HyperNode 挂机客户端。这个文件里有节点 Token,不要发给别人。 import json import time import urllib.error import urllib.request CONFIG = json.loads({config!r}) def beat(): body = json.dumps({{"sn": CONFIG["sn"], "token": CONFIG["token"]}}).encode() request = urllib.request.Request( CONFIG["base"] + "/api/v1/node/heartbeat", data=body, headers={{"Content-Type": "application/json"}}, ) with urllib.request.urlopen(request, timeout=20) as response: print(response.read().decode()) if __name__ == "__main__": while True: try: beat() except (urllib.error.URLError, TimeoutError, OSError) as exc: print(exc) time.sleep(60) """ def _pending_order(session: Session, order_id: int) -> LicenseOrder: order = session.scalar( select(LicenseOrder) .options(selectinload(LicenseOrder.user), selectinload(LicenseOrder.node)) .where(LicenseOrder.id == order_id) ) if order is None: raise AppError("订单不存在", 404) if order.status != OrderStatus.PENDING: raise AppError("只有待审核的订单可以处理", 409) return order