"""一次性任务 CRUD:列表状态 + 领奖。 领奖原子化:写 user_task(唯一约束防重复)+ 发金币,同一事务 commit。 """ from __future__ import annotations from dataclasses import dataclass from sqlalchemy import select from sqlalchemy.orm import Session from app.core.rewards import TASK_REWARDS from app.repositories import wallet as crud_wallet from app.models.task import UserTask class UnknownTaskError(Exception): """task_key 不在已知任务表里。""" class AlreadyClaimedError(Exception): """该任务已经领过奖了。""" @dataclass class TaskState: task_key: str coin: int claimed: bool def list_tasks(db: Session, user_id: int) -> list[TaskState]: """返回所有已知一次性任务及其领取状态。""" stmt = select(UserTask.task_key).where(UserTask.user_id == user_id) claimed_keys = set(db.execute(stmt).scalars().all()) return [ TaskState(task_key=key, coin=coin, claimed=key in claimed_keys) for key, coin in TASK_REWARDS.items() ] def claim_task(db: Session, user_id: int, task_key: str) -> tuple[int, int]: """领取一次性任务奖励。返回 (发放金币, 领奖后余额)。 未知任务抛 UnknownTaskError;重复领取抛 AlreadyClaimedError。 """ if task_key not in TASK_REWARDS: raise UnknownTaskError existing = db.execute( select(UserTask).where( UserTask.user_id == user_id, UserTask.task_key == task_key ) ).scalar_one_or_none() if existing is not None: raise AlreadyClaimedError coin = TASK_REWARDS[task_key] db.add( UserTask( user_id=user_id, task_key=task_key, status="completed", coin_awarded=coin, ) ) acc, _ = crud_wallet.grant_coins( db, user_id, coin, biz_type=f"task_{task_key}", ref_id=task_key ) db.commit() return coin, acc.coin_balance