1
0
syui 9f9208e160
Integrate ai.card Rust API with ai.gpt MCP and implement daily limit system
### 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>
2025-06-08 10:35:43 +09:00

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())
)