214 lines
7.4 KiB
Python
214 lines
7.4 KiB
Python
import asyncio
|
|
from typing import Dict, Optional
|
|
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from database.models import Card as DBCard
|
|
from services.database_service_base import DatabaseServiceBase
|
|
from utils.logger import session_logger
|
|
|
|
|
|
class Card(BaseModel):
|
|
card_id: Optional[int] = None
|
|
card_uuid: str
|
|
device_id: Optional[str] = None
|
|
card_name: Optional[str] = None
|
|
status: int = 0
|
|
total_swaps: int = 0
|
|
created_at: Optional[float] = None
|
|
updated_at: Optional[float] = None
|
|
|
|
@classmethod
|
|
def from_db_model(cls, db_model: DBCard):
|
|
return cls(
|
|
card_id=db_model.card_id,
|
|
card_uuid=db_model.card_uuid,
|
|
device_id=db_model.device_id,
|
|
card_name=db_model.card_name,
|
|
status=db_model.status,
|
|
total_swaps=db_model.total_swaps,
|
|
created_at=db_model.created_at.timestamp() if db_model.created_at else None,
|
|
updated_at=db_model.updated_at.timestamp() if db_model.updated_at else None,
|
|
)
|
|
|
|
|
|
class CardService(DatabaseServiceBase):
|
|
def __init__(self):
|
|
super().__init__(service_name="card")
|
|
self.cards: Dict[str, Card] = {}
|
|
self.lock = asyncio.Lock()
|
|
|
|
async def _cache_card(self, card: Card) -> None:
|
|
async with self.lock:
|
|
self.cards[card.card_uuid] = card
|
|
|
|
async def _load_card_from_db(self, card_uuid: str, async_session: AsyncSession) -> Optional[Card]:
|
|
try:
|
|
query = select(DBCard).where(DBCard.card_uuid == card_uuid)
|
|
result = await async_session.execute(query)
|
|
db_card = result.scalar_one_or_none()
|
|
|
|
if not db_card:
|
|
return None
|
|
|
|
card = Card.from_db_model(db_card)
|
|
await self._cache_card(card)
|
|
return card
|
|
except Exception as exc:
|
|
session_logger.error("card", "service", f"load card failed: {exc}")
|
|
return None
|
|
|
|
async def _save_card_to_db(self, card: Card, async_session: AsyncSession, commit: bool = True) -> None:
|
|
try:
|
|
query = select(DBCard).where(DBCard.card_uuid == card.card_uuid)
|
|
result = await async_session.execute(query)
|
|
existing_card = result.scalar_one_or_none()
|
|
|
|
if existing_card:
|
|
existing_card.device_id = card.device_id
|
|
existing_card.card_name = card.card_name
|
|
existing_card.status = card.status
|
|
existing_card.total_swaps = card.total_swaps
|
|
db_card = existing_card
|
|
else:
|
|
db_card = DBCard(
|
|
card_uuid=card.card_uuid,
|
|
device_id=card.device_id,
|
|
card_name=card.card_name,
|
|
status=card.status,
|
|
total_swaps=card.total_swaps,
|
|
)
|
|
async_session.add(db_card)
|
|
|
|
await async_session.flush()
|
|
if commit:
|
|
await async_session.commit()
|
|
|
|
card.card_id = db_card.card_id
|
|
await self._cache_card(card)
|
|
session_logger.info("card", "service", f"card saved: {card.card_uuid}")
|
|
except Exception as exc:
|
|
if commit:
|
|
await async_session.rollback()
|
|
session_logger.error("card", "service", f"save card failed: {exc}")
|
|
raise
|
|
|
|
async def get_card_by_uuid(
|
|
self,
|
|
card_uuid: str,
|
|
force_refresh: bool = False,
|
|
db_session: Optional[AsyncSession] = None,
|
|
) -> Optional[Card]:
|
|
await self._init_database()
|
|
|
|
card = None
|
|
if not force_refresh:
|
|
async with self.lock:
|
|
card = self.cards.get(card_uuid)
|
|
|
|
if card:
|
|
return card
|
|
|
|
if db_session is not None:
|
|
return await self._load_card_from_db(card_uuid, db_session)
|
|
|
|
session = await self.db_manager.get_session()
|
|
try:
|
|
return await self._load_card_from_db(card_uuid, session)
|
|
finally:
|
|
await session.close()
|
|
|
|
async def get_card_by_device_id(self, device_id: str) -> list[Card]:
|
|
await self._init_database()
|
|
|
|
db_session = await self.db_manager.get_session()
|
|
try:
|
|
query = select(DBCard).where(DBCard.device_id == device_id)
|
|
result = await db_session.execute(query)
|
|
db_cards = result.scalars().all()
|
|
|
|
cards = []
|
|
for db_card in db_cards:
|
|
card = Card.from_db_model(db_card)
|
|
await self._cache_card(card)
|
|
cards.append(card)
|
|
return cards
|
|
except Exception as exc:
|
|
session_logger.error(device_id, "card", f"load device cards failed: {exc}")
|
|
return []
|
|
finally:
|
|
await db_session.close()
|
|
|
|
async def activate_card(
|
|
self,
|
|
card_uuid: str,
|
|
device_id: str,
|
|
card_name: Optional[str] = None,
|
|
db_session: Optional[AsyncSession] = None,
|
|
) -> Card:
|
|
await self._init_database()
|
|
|
|
owns_session = db_session is None
|
|
if db_session is None:
|
|
db_session = await self.db_manager.get_session()
|
|
|
|
try:
|
|
existing_card = await self.get_card_by_uuid(card_uuid, db_session=db_session)
|
|
|
|
if existing_card:
|
|
if existing_card.device_id and existing_card.device_id != device_id:
|
|
raise ValueError("card is already bound to another device")
|
|
existing_card.device_id = device_id
|
|
existing_card.card_name = card_name
|
|
existing_card.status = 1
|
|
await self._save_card_to_db(existing_card, db_session, commit=owns_session)
|
|
session_logger.info(device_id, "card", f"card activated: {card_uuid}")
|
|
return existing_card
|
|
|
|
new_card = Card(
|
|
card_uuid=card_uuid,
|
|
device_id=device_id,
|
|
card_name=card_name,
|
|
status=1,
|
|
total_swaps=0,
|
|
)
|
|
await self._save_card_to_db(new_card, db_session, commit=owns_session)
|
|
session_logger.info(device_id, "card", f"new card activated: {card_uuid}")
|
|
return new_card
|
|
except Exception:
|
|
if owns_session:
|
|
await db_session.rollback()
|
|
raise
|
|
finally:
|
|
if owns_session:
|
|
await db_session.close()
|
|
|
|
async def increment_swap_count(self, card_uuid: str) -> Optional[Card]:
|
|
await self._init_database()
|
|
|
|
card = await self.get_card_by_uuid(card_uuid)
|
|
if not card:
|
|
return None
|
|
|
|
card.total_swaps += 1
|
|
db_session = await self.db_manager.get_session()
|
|
try:
|
|
await self._save_card_to_db(card, db_session)
|
|
session_logger.info("card", "service", f"swap count incremented: {card_uuid}, total={card.total_swaps}")
|
|
return card
|
|
finally:
|
|
await db_session.close()
|
|
|
|
async def check_card_ownership(self, card_uuid: str, device_id: str) -> bool:
|
|
card = await self.get_card_by_uuid(card_uuid)
|
|
return bool(card and card.device_id == device_id)
|
|
|
|
async def is_active_for_device(self, card_uuid: str, device_id: str) -> bool:
|
|
card = await self.get_card_by_uuid(card_uuid)
|
|
return bool(card and card.device_id == device_id and int(card.status) == 1)
|
|
|
|
|
|
card_service = CardService()
|