feat(backend): complete modular monolith with ledger, fastapi and websocket
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
import jwt
|
||||
from datetime import datetime, timedelta
|
||||
from typing import AsyncGenerator, Optional
|
||||
from fastapi import Depends, HTTPException, Header, status
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker
|
||||
from app.config import settings
|
||||
|
||||
# Database Engine & Session
|
||||
engine = create_async_engine(
|
||||
settings.DATABASE_URL,
|
||||
echo=settings.DEBUG,
|
||||
future=True,
|
||||
pool_pre_ping=True
|
||||
)
|
||||
|
||||
AsyncSessionLocal = async_sessionmaker(
|
||||
bind=engine,
|
||||
class_=AsyncSession,
|
||||
expire_on_commit=False,
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
)
|
||||
|
||||
async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
|
||||
async with AsyncSessionLocal() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
|
||||
def create_access_token(address: str) -> str:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
payload = {"sub": address.lower(), "exp": expire}
|
||||
return jwt.encode(payload, settings.JWT_SECRET, algorithm=settings.JWT_ALGORITHM)
|
||||
|
||||
async def get_current_user(authorization: Optional[str] = Header(None)) -> str:
|
||||
if not authorization or not authorization.startswith("Bearer "):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="缺少有效的认证 Token",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
token = authorization.split(" ")[1]
|
||||
try:
|
||||
payload = jwt.decode(token, settings.JWT_SECRET, algorithms=[settings.JWT_ALGORITHM])
|
||||
address = payload.get("sub")
|
||||
if not address:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效的用户凭证")
|
||||
return address
|
||||
except jwt.PyJWTError:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token 已过期或无效")
|
||||
@@ -0,0 +1,118 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from typing import List, Optional
|
||||
from app.api.dependencies import get_db_session, get_current_user, create_access_token
|
||||
from app.schemas.dtos import (
|
||||
UserAuthRequest,
|
||||
TokenResponse,
|
||||
AccountBalanceDTO,
|
||||
LedgerEntryDTO,
|
||||
MarketDTO,
|
||||
CreateOrderRequest,
|
||||
OrderDTO
|
||||
)
|
||||
from app.domains.ledger.service import LedgerService
|
||||
from app.domains.market.service import MarketService
|
||||
from app.domains.trading.service import TradingService
|
||||
|
||||
api_router = APIRouter(prefix="/v1")
|
||||
|
||||
# ================= Auth Routes =================
|
||||
@api_router.post("/auth/login", response_model=TokenResponse)
|
||||
async def login_with_wallet(req: UserAuthRequest, session: AsyncSession = Depends(get_db_session)):
|
||||
"""Web3 SIWE 钱包快速认证"""
|
||||
token = create_access_token(req.address)
|
||||
return TokenResponse(access_token=token, address=req.address.lower())
|
||||
|
||||
# ================= Market Routes =================
|
||||
@api_router.get("/markets", response_model=List[MarketDTO])
|
||||
async def get_markets(
|
||||
category: Optional[str] = None,
|
||||
status: Optional[str] = None,
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""获取预测市场列表"""
|
||||
market_service = MarketService(session)
|
||||
markets = await market_service.list_markets(category, status)
|
||||
return [
|
||||
MarketDTO(
|
||||
market_address=m.market_address,
|
||||
question_id=m.question_id,
|
||||
title=m.title,
|
||||
description=m.description,
|
||||
category=m.category,
|
||||
tier=m.tier,
|
||||
creator=m.creator,
|
||||
status=m.status.value if hasattr(m.status, 'value') else str(m.status),
|
||||
outcomes=m.outcomes,
|
||||
winning_outcome=m.winning_outcome,
|
||||
resolution_time=m.resolution_time,
|
||||
created_at=m.created_at
|
||||
) for m in markets
|
||||
]
|
||||
|
||||
# ================= Orders & Trading =================
|
||||
@api_router.post("/orders", response_model=OrderDTO)
|
||||
async def place_order(
|
||||
req: CreateOrderRequest,
|
||||
current_user: str = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""创建并执行交易委托"""
|
||||
trading_service = TradingService(session)
|
||||
order = await trading_service.place_order(
|
||||
user_address=current_user,
|
||||
market_address=req.market_address,
|
||||
outcome_index=req.outcome_index,
|
||||
side=req.side,
|
||||
amount=req.amount,
|
||||
price=req.price
|
||||
)
|
||||
return OrderDTO(
|
||||
order_id=order.order_id,
|
||||
market_address=order.market_address,
|
||||
user_address=order.user_address,
|
||||
side=order.side,
|
||||
outcome_index=order.outcome_index,
|
||||
amount=order.amount,
|
||||
status=order.status,
|
||||
fee=order.fee,
|
||||
created_at=order.created_at
|
||||
)
|
||||
|
||||
# ================= Ledger & Balances =================
|
||||
@api_router.get("/wallet/balance", response_model=AccountBalanceDTO)
|
||||
async def get_balance(
|
||||
current_user: str = Depends(get_current_user),
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""查询用户资金与冻结余额"""
|
||||
ledger_service = LedgerService(session)
|
||||
bal = await ledger_service.get_or_create_balance(current_user)
|
||||
return AccountBalanceDTO(
|
||||
address=bal.address,
|
||||
collateral_symbol=bal.collateral_symbol,
|
||||
available_balance=bal.available_balance,
|
||||
frozen_balance=bal.frozen_balance
|
||||
)
|
||||
|
||||
@api_router.get("/wallet/ledger", response_model=List[LedgerEntryDTO])
|
||||
async def get_ledger_history(
|
||||
current_user: str = Depends(get_current_user),
|
||||
limit: int = 50,
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""查询不可变复式记账流水"""
|
||||
ledger_service = LedgerService(session)
|
||||
entries = await ledger_service.list_user_ledger(current_user, limit)
|
||||
return [
|
||||
LedgerEntryDTO(
|
||||
id=e.id,
|
||||
entry_type=e.entry_type.value if hasattr(e.entry_type, 'value') else str(e.entry_type),
|
||||
amount=e.amount,
|
||||
balance_after=e.balance_after,
|
||||
frozen_after=e.frozen_after,
|
||||
related_market=e.related_market,
|
||||
created_at=e.created_at
|
||||
) for e in entries
|
||||
]
|
||||
Reference in New Issue
Block a user