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>
This commit is contained in:
@ -34,6 +34,9 @@ class Settings(BaseSettings):
|
||||
# Unique card settings
|
||||
max_unique_cards: int = 1000 # Maximum number of unique cards
|
||||
|
||||
# Draw limit settings
|
||||
draw_limit_days: int = 2 # Number of days between allowed draws (every other day)
|
||||
|
||||
# CORS
|
||||
cors_origins: list[str] = [
|
||||
"http://localhost:3000",
|
||||
|
@ -23,6 +23,7 @@ class User(Base):
|
||||
handle = Column(String, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
last_draw_date = Column(DateTime, nullable=True)
|
||||
|
||||
# Relationships
|
||||
cards = relationship("UserCard", back_populates="owner")
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""User repository"""
|
||||
from typing import Optional
|
||||
from sqlalchemy import select
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy import select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
@ -35,4 +36,62 @@ class UserRepository(BaseRepository[User]):
|
||||
.options(selectinload(User.cards))
|
||||
.where(User.id == user_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
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())
|
||||
)
|
@ -56,11 +56,38 @@ async def draw_card(
|
||||
await db.commit()
|
||||
return result
|
||||
|
||||
except ValueError as e:
|
||||
# Handle daily limit error
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=429, detail=str(e))
|
||||
except Exception as e:
|
||||
await db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/draw-status/{user_did}")
|
||||
async def get_draw_status(
|
||||
user_did: str,
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
ユーザーのガチャ実行状況を取得
|
||||
|
||||
- **user_did**: ユーザーのatproto DID
|
||||
"""
|
||||
from app.core.config import settings
|
||||
|
||||
user_repo = UserRepository(db)
|
||||
can_draw = await user_repo.can_draw_card(user_did, settings.draw_limit_days)
|
||||
next_draw_time = await user_repo.get_next_draw_time(user_did, settings.draw_limit_days)
|
||||
|
||||
return {
|
||||
"can_draw": can_draw,
|
||||
"next_draw_time": next_draw_time.isoformat() if next_draw_time else None,
|
||||
"draw_limit_days": settings.draw_limit_days
|
||||
}
|
||||
|
||||
|
||||
@router.get("/user/{user_did}", response_model=List[Card])
|
||||
async def get_user_cards(
|
||||
user_did: str,
|
||||
|
@ -34,7 +34,19 @@ class GachaService:
|
||||
|
||||
Returns:
|
||||
(Card, is_unique): 抽選されたカードとuniqueかどうか
|
||||
|
||||
Raises:
|
||||
ValueError: If user cannot draw due to daily limit
|
||||
"""
|
||||
# Check if user can draw (daily limit)
|
||||
can_draw = await self.user_repo.can_draw_card(user_did, settings.draw_limit_days)
|
||||
if not can_draw:
|
||||
next_draw_time = await self.user_repo.get_next_draw_time(user_did, settings.draw_limit_days)
|
||||
if next_draw_time:
|
||||
raise ValueError(f"カードを引けるのは{next_draw_time.strftime('%Y-%m-%d %H:%M:%S')}以降です。")
|
||||
else:
|
||||
raise ValueError("現在カードを引くことができません。")
|
||||
|
||||
# Get or create user
|
||||
user = await self.user_repo.get_or_create(user_did)
|
||||
# レアリティ抽選
|
||||
@ -78,6 +90,9 @@ class GachaService:
|
||||
)
|
||||
self.session.add(draw_history)
|
||||
|
||||
# Update user's last draw date
|
||||
await self.user_repo.update_last_draw_date(user.id)
|
||||
|
||||
# API用のCardモデルに変換
|
||||
card = Card(
|
||||
id=card_id,
|
||||
|
Reference in New Issue
Block a user