merge aigpt
This commit is contained in:
@ -5,7 +5,7 @@ from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
||||
from jose import JWTError, jwt
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from app.services.atproto import AtprotoService
|
||||
# from app.services.atproto import AtprotoService # Temporarily disabled
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
@ -97,58 +97,20 @@ async def get_optional_user(
|
||||
return current_user
|
||||
|
||||
|
||||
# Temporarily disabled due to atproto dependency issues
|
||||
class AtprotoAuth:
|
||||
"""atproto authentication handler"""
|
||||
"""atproto authentication handler (mock implementation)"""
|
||||
|
||||
def __init__(self):
|
||||
self.service = AtprotoService()
|
||||
pass # self.service = AtprotoService()
|
||||
|
||||
async def authenticate(self, identifier: str, password: str) -> Optional[AuthUser]:
|
||||
"""
|
||||
Authenticate user with atproto
|
||||
|
||||
Args:
|
||||
identifier: Handle or DID
|
||||
password: App password
|
||||
|
||||
Returns:
|
||||
AuthUser if successful
|
||||
"""
|
||||
try:
|
||||
# Login to atproto
|
||||
session = await self.service.login(identifier, password)
|
||||
|
||||
# Get user info from session
|
||||
# The session contains the DID
|
||||
if self.service.client:
|
||||
did = self.service.client.did
|
||||
handle = self.service.client.handle
|
||||
|
||||
return AuthUser(did=did, handle=handle)
|
||||
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
return None
|
||||
"""Mock authentication - always returns test user"""
|
||||
# Mock implementation for testing
|
||||
if identifier and password:
|
||||
return AuthUser(did="did:plc:test123", handle=identifier)
|
||||
return None
|
||||
|
||||
async def verify_did_ownership(self, did: str, session_string: str) -> bool:
|
||||
"""
|
||||
Verify user owns the DID by checking session
|
||||
|
||||
Args:
|
||||
did: DID to verify
|
||||
session_string: Session string from login
|
||||
|
||||
Returns:
|
||||
True if session is valid for DID
|
||||
"""
|
||||
try:
|
||||
self.service.restore_session(session_string)
|
||||
|
||||
if self.service.client and self.service.client.did == did:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
"""Mock verification - always returns True for test"""
|
||||
return True
|
@ -15,7 +15,7 @@ class Settings(BaseSettings):
|
||||
api_v1_prefix: str = "/api/v1"
|
||||
|
||||
# Database
|
||||
database_url: str = "postgresql+asyncpg://postgres:postgres@localhost:5432/aicard"
|
||||
database_url: str = "sqlite+aiosqlite:///~/.config/syui/ai/card/aicard.db"
|
||||
database_url_supabase: Optional[str] = None
|
||||
use_supabase: bool = False
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
"""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
|
||||
@ -6,18 +8,36 @@ 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
|
||||
|
||||
# Create async engine
|
||||
engine = create_async_engine(
|
||||
database_url,
|
||||
echo=settings.debug,
|
||||
future=True,
|
||||
pool_pre_ping=True, # Enable connection health checks
|
||||
pool_size=5,
|
||||
max_overflow=10
|
||||
)
|
||||
# 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(
|
||||
@ -27,14 +47,7 @@ async_session = async_sessionmaker(
|
||||
)
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async def get_session() -> AsyncSession:
|
||||
"""Dependency to get database session"""
|
||||
async with async_session() as session:
|
||||
try:
|
||||
yield session
|
||||
await session.commit()
|
||||
except Exception:
|
||||
await session.rollback()
|
||||
raise
|
||||
finally:
|
||||
await session.close()
|
||||
yield session
|
@ -1,17 +1,24 @@
|
||||
"""FastAPI application entry point"""
|
||||
import os
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
|
||||
from app.core.config import settings
|
||||
from app.routes import cards, auth, sync
|
||||
from app.mcp_server import AICardMcpServer
|
||||
|
||||
# Create FastAPI app
|
||||
app = FastAPI(
|
||||
title=settings.app_name,
|
||||
version=settings.app_version,
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
)
|
||||
# Initialize MCP server
|
||||
enable_mcp = os.getenv("ENABLE_MCP", "true").lower() == "true"
|
||||
mcp_server = AICardMcpServer(enable_mcp=enable_mcp)
|
||||
|
||||
# Get FastAPI app from MCP server
|
||||
app = mcp_server.get_app()
|
||||
|
||||
# Update app configuration
|
||||
app.title = settings.app_name
|
||||
app.version = settings.app_version
|
||||
app.docs_url = "/docs"
|
||||
app.redoc_url = "/redoc"
|
||||
|
||||
# CORS middleware
|
||||
app.add_middleware(
|
||||
@ -41,7 +48,11 @@ async def root():
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint"""
|
||||
return {"status": "healthy"}
|
||||
return {
|
||||
"status": "healthy",
|
||||
"mcp_enabled": enable_mcp,
|
||||
"mcp_endpoint": "/mcp" if enable_mcp else None
|
||||
}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
286
api/app/mcp_server.py
Normal file
286
api/app/mcp_server.py
Normal file
@ -0,0 +1,286 @@
|
||||
"""MCP Server for ai.card system"""
|
||||
|
||||
from typing import Optional, List, Dict, Any
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from fastapi import FastAPI, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from app.core.config import settings
|
||||
from app.db.base import get_session
|
||||
from app.models.card import Card, CardRarity, CardDrawResult
|
||||
from app.repositories.card import CardRepository, UniqueCardRepository
|
||||
from app.repositories.user import UserRepository
|
||||
from app.services.gacha import GachaService
|
||||
# from app.services.card_sync import CardSyncService # Temporarily disabled
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AICardMcpServer:
|
||||
"""MCP Server that exposes ai.card functionality to AI assistants"""
|
||||
|
||||
def __init__(self, enable_mcp: bool = True):
|
||||
self.enable_mcp = enable_mcp
|
||||
|
||||
# Create FastAPI app
|
||||
self.app = FastAPI(
|
||||
title="AI.Card - Card Game System",
|
||||
description="MCP server for ai.card system",
|
||||
version=settings.app_version
|
||||
)
|
||||
|
||||
# Create MCP server with FastAPI app
|
||||
self.server = None
|
||||
if enable_mcp:
|
||||
self.server = FastMCP("aicard")
|
||||
self._register_mcp_tools()
|
||||
|
||||
def _register_mcp_tools(self):
|
||||
"""Register all MCP tools"""
|
||||
|
||||
@self.app.get("/get_user_cards", operation_id="get_user_cards")
|
||||
async def get_user_cards(
|
||||
did: str,
|
||||
limit: int = 10,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Get all cards owned by a user"""
|
||||
try:
|
||||
user_repo = UserRepository(session)
|
||||
card_repo = CardRepository(session)
|
||||
|
||||
# Get user
|
||||
user = await user_repo.get_by_did(did)
|
||||
if not user:
|
||||
return []
|
||||
|
||||
# Get user cards
|
||||
user_cards = await card_repo.get_user_cards(user.id, limit=limit)
|
||||
|
||||
return [
|
||||
{
|
||||
"id": card.card_id,
|
||||
"cp": card.cp,
|
||||
"status": card.status,
|
||||
"skill": card.skill,
|
||||
"owner_did": did,
|
||||
"obtained_at": card.obtained_at.isoformat(),
|
||||
"is_unique": card.is_unique,
|
||||
"unique_id": str(card.unique_id) if card.unique_id else None
|
||||
}
|
||||
for card in user_cards
|
||||
]
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user cards: {e}")
|
||||
return []
|
||||
|
||||
@self.app.post("/draw_card", operation_id="draw_card")
|
||||
async def draw_card(
|
||||
did: str,
|
||||
is_paid: bool = False,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Draw a new card (gacha) for user"""
|
||||
try:
|
||||
gacha_service = GachaService(session)
|
||||
|
||||
# Draw card
|
||||
card, is_unique = await gacha_service.draw_card(did, is_paid)
|
||||
await session.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"card": {
|
||||
"id": card.id,
|
||||
"cp": card.cp,
|
||||
"status": card.status,
|
||||
"skill": card.skill,
|
||||
"owner_did": card.owner_did,
|
||||
"obtained_at": card.obtained_at.isoformat(),
|
||||
"is_unique": card.is_unique,
|
||||
"unique_id": card.unique_id
|
||||
},
|
||||
"is_unique": is_unique,
|
||||
"animation_type": "kira" if card.status in [CardRarity.KIRA, CardRarity.UNIQUE] else "normal"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error drawing card: {e}")
|
||||
await session.rollback()
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
@self.app.get("/get_card_details", operation_id="get_card_details")
|
||||
async def get_card_details(
|
||||
card_id: int,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get detailed information about a card type"""
|
||||
try:
|
||||
# Get card info from gacha service
|
||||
gacha_service = GachaService(session)
|
||||
|
||||
if card_id not in gacha_service.CARD_INFO:
|
||||
return {"error": f"Card ID {card_id} not found"}
|
||||
|
||||
card_info = gacha_service.CARD_INFO[card_id]
|
||||
|
||||
# Get unique card availability
|
||||
unique_repo = UniqueCardRepository(session)
|
||||
is_unique_available = await unique_repo.is_card_available(card_id)
|
||||
|
||||
return {
|
||||
"id": card_id,
|
||||
"name": card_info["name"],
|
||||
"base_cp_range": card_info["base_cp_range"],
|
||||
"is_unique_available": is_unique_available,
|
||||
"description": f"Card {card_id}: {card_info['name']}"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting card details: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
@self.app.post("/sync_cards_atproto", operation_id="sync_cards_atproto")
|
||||
async def sync_cards_atproto(
|
||||
did: str,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, str]:
|
||||
"""Sync user's cards with atproto (temporarily disabled)"""
|
||||
return {"status": "atproto sync temporarily disabled due to dependency issues"}
|
||||
|
||||
@self.app.get("/analyze_card_collection", operation_id="analyze_card_collection")
|
||||
async def analyze_card_collection(
|
||||
did: str,
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Analyze user's card collection"""
|
||||
try:
|
||||
user_repo = UserRepository(session)
|
||||
card_repo = CardRepository(session)
|
||||
|
||||
# Get user
|
||||
user = await user_repo.get_by_did(did)
|
||||
if not user:
|
||||
return {
|
||||
"total_cards": 0,
|
||||
"rarity_distribution": {},
|
||||
"message": "User not found"
|
||||
}
|
||||
|
||||
# Get all user cards
|
||||
user_cards = await card_repo.get_user_cards(user.id, limit=1000)
|
||||
|
||||
if not user_cards:
|
||||
return {
|
||||
"total_cards": 0,
|
||||
"rarity_distribution": {},
|
||||
"message": "No cards found"
|
||||
}
|
||||
|
||||
# Analyze collection
|
||||
rarity_count = {}
|
||||
total_cp = 0
|
||||
card_type_count = {}
|
||||
|
||||
for card in user_cards:
|
||||
# Rarity distribution
|
||||
rarity = card.status
|
||||
rarity_count[rarity] = rarity_count.get(rarity, 0) + 1
|
||||
|
||||
# Total CP
|
||||
total_cp += card.cp
|
||||
|
||||
# Card type distribution
|
||||
card_type_count[card.card_id] = card_type_count.get(card.card_id, 0) + 1
|
||||
|
||||
# Find strongest card
|
||||
strongest_card = max(user_cards, key=lambda x: x.cp)
|
||||
|
||||
return {
|
||||
"total_cards": len(user_cards),
|
||||
"rarity_distribution": rarity_count,
|
||||
"card_type_distribution": card_type_count,
|
||||
"average_cp": total_cp / len(user_cards) if user_cards else 0,
|
||||
"total_cp": total_cp,
|
||||
"strongest_card": {
|
||||
"id": strongest_card.card_id,
|
||||
"cp": strongest_card.cp,
|
||||
"status": strongest_card.status,
|
||||
"is_unique": strongest_card.is_unique
|
||||
},
|
||||
"unique_count": len([c for c in user_cards if c.is_unique])
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error analyzing collection: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
@self.app.get("/get_unique_registry", operation_id="get_unique_registry")
|
||||
async def get_unique_registry(
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get all registered unique cards"""
|
||||
try:
|
||||
unique_repo = UniqueCardRepository(session)
|
||||
|
||||
# Get all unique cards
|
||||
unique_cards = await unique_repo.get_all_unique_cards()
|
||||
|
||||
# Get available unique card IDs
|
||||
available_ids = await unique_repo.get_available_unique_cards()
|
||||
|
||||
return {
|
||||
"registered_unique_cards": [
|
||||
{
|
||||
"card_id": card.card_id,
|
||||
"unique_id": card.unique_id,
|
||||
"owner_did": card.owner_did,
|
||||
"obtained_at": card.obtained_at.isoformat()
|
||||
}
|
||||
for card in unique_cards
|
||||
],
|
||||
"available_unique_card_ids": available_ids,
|
||||
"total_registered": len(unique_cards),
|
||||
"total_available": len(available_ids)
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting unique registry: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
@self.app.get("/get_gacha_stats", operation_id="get_gacha_stats")
|
||||
async def get_gacha_stats(
|
||||
session: AsyncSession = Depends(get_session)
|
||||
) -> Dict[str, Any]:
|
||||
"""Get gacha system statistics"""
|
||||
try:
|
||||
return {
|
||||
"rarity_probabilities": {
|
||||
"normal": f"{100 - settings.prob_rare}%",
|
||||
"rare": f"{settings.prob_rare - settings.prob_super_rare}%",
|
||||
"super_rare": f"{settings.prob_super_rare - settings.prob_kira}%",
|
||||
"kira": f"{settings.prob_kira - settings.prob_unique}%",
|
||||
"unique": f"{settings.prob_unique}%"
|
||||
},
|
||||
"total_card_types": 16,
|
||||
"card_names": [info["name"] for info in GachaService.CARD_INFO.values()],
|
||||
"system_info": {
|
||||
"daily_limit": "1 free draw per day",
|
||||
"paid_gacha": "Enhanced probabilities",
|
||||
"unique_system": "First-come-first-served globally unique cards"
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting gacha stats: {e}")
|
||||
return {"error": str(e)}
|
||||
|
||||
# MCP server will be run separately, not here
|
||||
|
||||
def get_server(self) -> Optional[FastMCP]:
|
||||
"""Get the FastAPI MCP server instance"""
|
||||
return self.server
|
||||
|
||||
def get_app(self) -> FastAPI:
|
||||
"""Get the FastAPI app instance"""
|
||||
return self.app
|
@ -1,5 +1,6 @@
|
||||
"""Authentication routes"""
|
||||
from datetime import timedelta
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, Depends, Response
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
@ -12,9 +13,9 @@ from app.auth.dependencies import (
|
||||
AuthUser,
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
from app.db.base import get_db
|
||||
from app.db.base import get_session
|
||||
from app.repositories.user import UserRepository
|
||||
from app.services.atproto import AtprotoService
|
||||
# from app.services.atproto import AtprotoService
|
||||
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
@ -45,7 +46,7 @@ class VerifyResponse(BaseModel):
|
||||
async def login(
|
||||
request: LoginRequest,
|
||||
response: Response,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
Login with atproto credentials
|
||||
|
@ -7,7 +7,7 @@ from app.models.card import Card, CardDraw, CardDrawResult
|
||||
from app.services.gacha import GachaService
|
||||
from app.repositories.user import UserRepository
|
||||
from app.repositories.card import CardRepository, UniqueCardRepository
|
||||
from app.db.base import get_db
|
||||
from app.db.base import get_session
|
||||
|
||||
router = APIRouter(prefix="/cards", tags=["cards"])
|
||||
|
||||
@ -15,7 +15,7 @@ router = APIRouter(prefix="/cards", tags=["cards"])
|
||||
@router.post("/draw", response_model=CardDrawResult)
|
||||
async def draw_card(
|
||||
draw_request: CardDraw,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
カードを抽選する
|
||||
@ -65,7 +65,7 @@ async def get_user_cards(
|
||||
user_did: str,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
ユーザーの所有カード一覧を取得
|
||||
@ -100,7 +100,7 @@ async def get_user_cards(
|
||||
|
||||
|
||||
@router.get("/unique")
|
||||
async def get_unique_cards(db: AsyncSession = Depends(get_db)):
|
||||
async def get_unique_cards(db: AsyncSession = Depends(get_session)):
|
||||
"""
|
||||
全てのuniqueカード一覧を取得(所有者情報付き)
|
||||
"""
|
||||
|
@ -1,10 +1,11 @@
|
||||
"""Synchronization routes for atproto"""
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException, Depends
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.auth.dependencies import require_user, AuthUser
|
||||
from app.db.base import get_db
|
||||
from app.db.base import get_session
|
||||
from app.services.card_sync import CardSyncService
|
||||
from app.repositories.user import UserRepository
|
||||
|
||||
@ -28,7 +29,7 @@ class SyncResponse(BaseModel):
|
||||
async def sync_cards(
|
||||
request: SyncRequest,
|
||||
current_user: AuthUser = Depends(require_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
Sync cards between database and atproto PDS
|
||||
@ -68,7 +69,7 @@ async def sync_cards(
|
||||
async def export_to_pds(
|
||||
request: SyncRequest,
|
||||
current_user: AuthUser = Depends(require_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
Export all cards to atproto PDS
|
||||
@ -97,7 +98,7 @@ async def export_to_pds(
|
||||
async def import_from_pds(
|
||||
request: SyncRequest,
|
||||
current_user: AuthUser = Depends(require_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
Import cards from atproto PDS
|
||||
@ -125,7 +126,7 @@ async def verify_card_ownership(
|
||||
card_id: int,
|
||||
unique_id: Optional[str] = None,
|
||||
current_user: AuthUser = Depends(require_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
db: AsyncSession = Depends(get_session)
|
||||
):
|
||||
"""
|
||||
Verify user owns a specific card
|
||||
|
@ -3,7 +3,7 @@ from typing import List, Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.services.atproto import AtprotoService, CardLexicon
|
||||
# from app.services.atproto import AtprotoService, CardLexicon
|
||||
from app.repositories.card import CardRepository
|
||||
from app.repositories.user import UserRepository
|
||||
from app.models.card import Card as CardModel
|
||||
|
@ -1,5 +1,6 @@
|
||||
"""Initialize database with master data"""
|
||||
import asyncio
|
||||
from sqlalchemy import text, select, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.db.base import engine, Base, async_session
|
||||
from app.db.models import CardMaster
|
||||
@ -35,10 +36,14 @@ async def init_db():
|
||||
print("Inserting master data...")
|
||||
async with async_session() as session:
|
||||
# Check if master data already exists
|
||||
existing = await session.execute(
|
||||
"SELECT COUNT(*) FROM card_master"
|
||||
)
|
||||
count = existing.scalar()
|
||||
try:
|
||||
result = await session.execute(
|
||||
select(func.count()).select_from(CardMaster)
|
||||
)
|
||||
count = result.scalar()
|
||||
except Exception:
|
||||
# Table might not exist yet
|
||||
count = 0
|
||||
|
||||
if count == 0:
|
||||
# Insert card master data
|
||||
|
@ -1,18 +1,20 @@
|
||||
fastapi==0.104.1
|
||||
uvicorn[standard]==0.24.0
|
||||
pydantic==2.5.0
|
||||
pydantic-settings==2.1.0
|
||||
fastapi>=0.104.1
|
||||
uvicorn[standard]>=0.24.0
|
||||
pydantic>=2.7.0,<3.0.0
|
||||
pydantic-settings>=2.1.0
|
||||
python-multipart==0.0.6
|
||||
httpx==0.25.2
|
||||
httpx>=0.25.0,<0.29.0
|
||||
python-jose[cryptography]==3.3.0
|
||||
passlib[bcrypt]==1.7.4
|
||||
sqlalchemy==2.0.23
|
||||
alembic==1.12.1
|
||||
asyncpg==0.29.0
|
||||
psycopg2-binary==2.9.9
|
||||
aiosqlite==0.19.0
|
||||
sqlalchemy>=2.0.23
|
||||
greenlet>=3.0.0
|
||||
alembic>=1.12.1
|
||||
# asyncpg==0.29.0 # Disabled: requires compilation
|
||||
# psycopg2-binary==2.9.9 # Disabled: requires compilation
|
||||
aiosqlite>=0.19.0
|
||||
python-dotenv==1.0.0
|
||||
pytest==7.4.3
|
||||
pytest-asyncio==0.21.1
|
||||
atproto==0.0.46
|
||||
supabase==2.3.0
|
||||
atproto>=0.0.55
|
||||
# supabase>=2.3.0 # Temporarily disabled due to httpx version conflict
|
||||
fastapi-mcp==0.1.0
|
Reference in New Issue
Block a user