1
0
This commit is contained in:
2025-06-08 06:42:03 +09:00
parent e7948bf4cf
commit 2e55e6ce09
53 changed files with 3053 additions and 0 deletions

View File

@ -0,0 +1 @@
# Database Package

53
python/api/app/db/base.py Normal file
View File

@ -0,0 +1,53 @@
"""Database base configuration"""
import os
from pathlib import Path
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from app.core.config import settings
# Create base class for models
Base = declarative_base()
# Ensure database directory exists
db_path = Path.home() / ".config" / "syui" / "ai" / "card"
db_path.mkdir(parents=True, exist_ok=True)
# Select database URL based on configuration
database_url = settings.database_url_supabase if settings.use_supabase else settings.database_url
# Expand ~ in database URL
if database_url.startswith("sqlite"):
database_url = database_url.replace("~", str(Path.home()))
# Create async engine (SQLite-optimized settings)
if "sqlite" in database_url:
engine = create_async_engine(
database_url,
echo=settings.debug,
future=True,
# SQLite-specific optimizations
connect_args={"check_same_thread": False}
)
else:
# PostgreSQL settings (fallback)
engine = create_async_engine(
database_url,
echo=settings.debug,
future=True,
pool_pre_ping=True,
pool_size=5,
max_overflow=10
)
# Create async session factory
async_session = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
async def get_session() -> AsyncSession:
"""Dependency to get database session"""
async with async_session() as session:
yield session

121
python/api/app/db/models.py Normal file
View File

@ -0,0 +1,121 @@
"""Database models"""
from datetime import datetime
from sqlalchemy import (
Column, Integer, String, DateTime, Boolean,
Float, ForeignKey, UniqueConstraint, Index,
Enum as SQLEnum
)
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
import uuid
import enum
from app.db.base import Base
from app.models.card import CardRarity
class User(Base):
"""ユーザーモデル"""
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
did = Column(String, unique=True, nullable=False, index=True)
handle = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# Relationships
cards = relationship("UserCard", back_populates="owner")
draws = relationship("DrawHistory", back_populates="user")
class CardMaster(Base):
"""カードマスタデータ"""
__tablename__ = "card_master"
id = Column(Integer, primary_key=True) # 0-15
name = Column(String, nullable=False)
base_cp_min = Column(Integer, nullable=False)
base_cp_max = Column(Integer, nullable=False)
color = Column(String, nullable=False)
description = Column(String)
# Relationships
user_cards = relationship("UserCard", back_populates="card_info")
class UserCard(Base):
"""ユーザー所有カード"""
__tablename__ = "user_cards"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
card_id = Column(Integer, ForeignKey("card_master.id"), nullable=False)
cp = Column(Integer, nullable=False)
status = Column(SQLEnum(CardRarity), nullable=False)
skill = Column(String, nullable=True)
obtained_at = Column(DateTime, default=datetime.utcnow)
is_unique = Column(Boolean, default=False)
unique_id = Column(UUID(as_uuid=True), nullable=True, unique=True)
# Relationships
owner = relationship("User", back_populates="cards")
card_info = relationship("CardMaster", back_populates="user_cards")
# Indexes
__table_args__ = (
Index('idx_user_cards', 'user_id', 'card_id'),
)
class UniqueCardRegistry(Base):
"""uniqueカードのグローバルレジストリ"""
__tablename__ = "unique_card_registry"
id = Column(Integer, primary_key=True)
unique_id = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, nullable=False)
card_id = Column(Integer, ForeignKey("card_master.id"), nullable=False)
owner_did = Column(String, ForeignKey("users.did"), nullable=False)
obtained_at = Column(DateTime, default=datetime.utcnow)
verse_skill_id = Column(String, nullable=True) # ai.verse連携用
# Unique constraint: 各card_idは1人のみ所有可能
__table_args__ = (
UniqueConstraint('card_id', name='unique_card_per_type'),
)
class DrawHistory(Base):
"""ガチャ履歴"""
__tablename__ = "draw_history"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
card_id = Column(Integer, nullable=False)
status = Column(SQLEnum(CardRarity), nullable=False)
cp = Column(Integer, nullable=False)
is_paid = Column(Boolean, default=False)
drawn_at = Column(DateTime, default=datetime.utcnow)
# Relationships
user = relationship("User", back_populates="draws")
# Indexes
__table_args__ = (
Index('idx_draw_history_user', 'user_id', 'drawn_at'),
)
class GachaPool(Base):
"""ガチャプール(ピックアップ管理)"""
__tablename__ = "gacha_pools"
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False)
description = Column(String)
is_active = Column(Boolean, default=True)
start_at = Column(DateTime, nullable=False)
end_at = Column(DateTime, nullable=True)
pickup_card_ids = Column(String) # JSON array of card IDs
rate_up_multiplier = Column(Float, default=1.0)
created_at = Column(DateTime, default=datetime.utcnow)