### Major Changes: - **Rust Migration**: Move api-rs to root directory, rename binary to 'aicard' - **MCP Integration**: Add card tools to ai.gpt MCP server (get_user_cards, draw_card, get_draw_status) - **Daily Limit System**: Implement 2-day interval card drawing limits in API and iOS - **iOS Enhancements**: Add DrawStatusView, backup functionality, and limit integration ### Technical Details: - ai.gpt MCP now has 20 tools including 3 card-related tools - ServiceClient enhanced with missing card API methods - iOS app includes daily limit UI and atproto OAuth backup features - Database migration for last_draw_date field - Complete feature parity between web and iOS implementations 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
97 lines
3.2 KiB
Python
97 lines
3.2 KiB
Python
"""User repository"""
|
|
from typing import Optional
|
|
from datetime import datetime, timedelta
|
|
from sqlalchemy import select, update
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy.orm import selectinload
|
|
|
|
from app.repositories.base import BaseRepository
|
|
from app.db.models import User
|
|
|
|
|
|
class UserRepository(BaseRepository[User]):
|
|
"""User repository with custom methods"""
|
|
|
|
def __init__(self, session: AsyncSession):
|
|
super().__init__(User, session)
|
|
|
|
async def get_by_did(self, did: str) -> Optional[User]:
|
|
"""Get user by DID"""
|
|
result = await self.session.execute(
|
|
select(User).where(User.did == did)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def get_or_create(self, did: str, handle: Optional[str] = None) -> User:
|
|
"""Get existing user or create new one"""
|
|
user = await self.get_by_did(did)
|
|
if not user:
|
|
user = await self.create(did=did, handle=handle)
|
|
return user
|
|
|
|
async def get_with_cards(self, user_id: int) -> Optional[User]:
|
|
"""Get user with all their cards"""
|
|
result = await self.session.execute(
|
|
select(User)
|
|
.options(selectinload(User.cards))
|
|
.where(User.id == user_id)
|
|
)
|
|
return result.scalar_one_or_none()
|
|
|
|
async def can_draw_card(self, did: str, draw_limit_days: int = 2) -> bool:
|
|
"""
|
|
Check if user can draw a card based on daily limit
|
|
|
|
Args:
|
|
did: User's DID
|
|
draw_limit_days: Number of days between allowed draws (default: 2 days)
|
|
|
|
Returns:
|
|
True if user can draw, False if still in cooldown period
|
|
"""
|
|
user = await self.get_by_did(did)
|
|
if not user:
|
|
# New user can always draw
|
|
return True
|
|
|
|
if not user.last_draw_date:
|
|
# User has never drawn before
|
|
return True
|
|
|
|
# Check if enough time has passed since last draw
|
|
time_since_last_draw = datetime.utcnow() - user.last_draw_date
|
|
return time_since_last_draw >= timedelta(days=draw_limit_days)
|
|
|
|
async def get_next_draw_time(self, did: str, draw_limit_days: int = 2) -> Optional[datetime]:
|
|
"""
|
|
Get the next time the user can draw a card
|
|
|
|
Args:
|
|
did: User's DID
|
|
draw_limit_days: Number of days between allowed draws
|
|
|
|
Returns:
|
|
Next draw time if user is in cooldown, None if user can draw now
|
|
"""
|
|
user = await self.get_by_did(did)
|
|
if not user or not user.last_draw_date:
|
|
return None
|
|
|
|
next_draw_time = user.last_draw_date + timedelta(days=draw_limit_days)
|
|
if datetime.utcnow() >= next_draw_time:
|
|
return None
|
|
|
|
return next_draw_time
|
|
|
|
async def update_last_draw_date(self, user_id: int) -> None:
|
|
"""
|
|
Update user's last draw date to current time
|
|
|
|
Args:
|
|
user_id: User's database ID
|
|
"""
|
|
await self.session.execute(
|
|
update(User)
|
|
.where(User.id == user_id)
|
|
.values(last_draw_date=datetime.utcnow())
|
|
) |