feat: integrate invite API and improve notification handling
- Add invite code display and binding functionality via API - Fix notification unread count sync on auth state change - Improve notification mark read with server state validation - Add auth state listener to trigger notification refresh - Add YaoCoinConverter for coin-to-yao type conversion - Remove YaoLegend from divination screens (UI cleanup) - Abbreviate relation labels in yao detail view - Add re-register notice to account delete screen - Update 'coins' terminology to 'points' in localization - Fix backend points consumption to only run in CHAT mode - Add HttpxAuthNoiseFilter to suppress auth endpoint logging - Fix notification static_schema import path - Add test coverage for notification bloc error handling - Update AGENTS.md page header rules and image handling - Delete deprecated run-dev.sh script
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
from core.db import get_db
|
||||
from v1.invite.repository import InviteCodeRepository
|
||||
from v1.invite.service import InviteCodeService
|
||||
from v1.users.dependencies import get_current_user
|
||||
|
||||
|
||||
def get_invite_code_repository(
|
||||
session: Annotated[AsyncSession, Depends(get_db)],
|
||||
) -> InviteCodeRepository:
|
||||
return InviteCodeRepository(session)
|
||||
|
||||
|
||||
def get_invite_code_service(
|
||||
repository: Annotated[InviteCodeRepository, Depends(get_invite_code_repository)],
|
||||
) -> InviteCodeService:
|
||||
return InviteCodeService(repository=repository)
|
||||
|
||||
|
||||
def get_current_user_for_invite(
|
||||
current_user: Annotated[CurrentUser, Depends(get_current_user)],
|
||||
) -> CurrentUser:
|
||||
return current_user
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from models.invite_code import InviteCode
|
||||
|
||||
|
||||
class InviteCodeRepository:
|
||||
def __init__(self, session: AsyncSession) -> None:
|
||||
self._session = session
|
||||
|
||||
async def get_by_owner_id(self, *, owner_id: UUID) -> InviteCode | None:
|
||||
stmt = (
|
||||
select(InviteCode)
|
||||
.where(InviteCode.owner_id == owner_id)
|
||||
.order_by(InviteCode.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
return (await self._session.execute(stmt)).scalar_one_or_none()
|
||||
@@ -0,0 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from core.auth.models import CurrentUser
|
||||
from v1.invite.dependencies import (
|
||||
get_current_user_for_invite,
|
||||
get_invite_code_service,
|
||||
)
|
||||
from v1.invite.schemas import MyInviteCodeResponse
|
||||
from v1.invite.service import InviteCodeService
|
||||
|
||||
|
||||
router = APIRouter(prefix="/invite", tags=["invite"])
|
||||
|
||||
|
||||
@router.get("/me", response_model=MyInviteCodeResponse)
|
||||
async def get_my_invite_code(
|
||||
current_user: Annotated[CurrentUser, Depends(get_current_user_for_invite)],
|
||||
service: Annotated[InviteCodeService, Depends(get_invite_code_service)],
|
||||
) -> MyInviteCodeResponse:
|
||||
return await service.get_my_invite_code(user_id=current_user.id)
|
||||
@@ -0,0 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, ConfigDict
|
||||
|
||||
|
||||
class MyInviteCodeResponse(BaseModel):
|
||||
model_config = ConfigDict(extra="forbid")
|
||||
|
||||
code: str
|
||||
used_count: int
|
||||
@@ -0,0 +1,28 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from uuid import UUID
|
||||
|
||||
from core.http.errors import ApiProblemError, problem_payload
|
||||
from v1.invite.repository import InviteCodeRepository
|
||||
from v1.invite.schemas import MyInviteCodeResponse
|
||||
|
||||
|
||||
@dataclass
|
||||
class InviteCodeService:
|
||||
repository: InviteCodeRepository
|
||||
|
||||
async def get_my_invite_code(self, *, user_id: UUID) -> MyInviteCodeResponse:
|
||||
invite_code = await self.repository.get_by_owner_id(owner_id=user_id)
|
||||
if invite_code is None:
|
||||
raise ApiProblemError(
|
||||
status_code=404,
|
||||
detail=problem_payload(
|
||||
code="INVITE_CODE_NOT_FOUND",
|
||||
detail="Invite code not found for current user",
|
||||
),
|
||||
)
|
||||
return MyInviteCodeResponse(
|
||||
code=invite_code.code,
|
||||
used_count=invite_code.used_count,
|
||||
)
|
||||
Reference in New Issue
Block a user