184 lines
6.0 KiB
Python
184 lines
6.0 KiB
Python
"""Card synchronization service for atproto"""
|
|
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.repositories.card import CardRepository
|
|
from app.repositories.user import UserRepository
|
|
from app.models.card import Card as CardModel
|
|
from app.db.models import UserCard
|
|
from app.core.config import settings
|
|
|
|
|
|
class CardSyncService:
|
|
"""Service for syncing cards between database and atproto PDS"""
|
|
|
|
def __init__(self, session: AsyncSession, atproto_session: Optional[str] = None):
|
|
self.db_session = session
|
|
self.card_repo = CardRepository(session)
|
|
self.user_repo = UserRepository(session)
|
|
self.atproto_service = AtprotoService()
|
|
|
|
# Restore atproto session if provided
|
|
if atproto_session:
|
|
self.atproto_service.restore_session(atproto_session)
|
|
|
|
async def sync_card_to_pds(self, user_card: UserCard, user_did: str) -> Optional[str]:
|
|
"""
|
|
Sync a single card to user's PDS
|
|
|
|
Args:
|
|
user_card: Card from database
|
|
user_did: User's DID
|
|
|
|
Returns:
|
|
Record URI if successful
|
|
"""
|
|
if not settings.atproto_handle or not settings.atproto_password:
|
|
# Skip if atproto credentials not configured
|
|
return None
|
|
|
|
try:
|
|
# Login if not already
|
|
if not self.atproto_service.client:
|
|
await self.atproto_service.login(
|
|
settings.atproto_handle,
|
|
settings.atproto_password
|
|
)
|
|
|
|
# Convert to API model
|
|
card_model = CardModel(
|
|
id=user_card.card_id,
|
|
cp=user_card.cp,
|
|
status=user_card.status,
|
|
skill=user_card.skill,
|
|
owner_did=user_did,
|
|
obtained_at=user_card.obtained_at,
|
|
is_unique=user_card.is_unique,
|
|
unique_id=str(user_card.unique_id) if user_card.unique_id else None
|
|
)
|
|
|
|
# Create record in PDS
|
|
uri = await self.atproto_service.create_card_record(
|
|
did=user_did,
|
|
card=card_model,
|
|
collection=CardLexicon.LEXICON_ID
|
|
)
|
|
|
|
# Store URI in database for future reference
|
|
# (You might want to add a field to UserCard model for this)
|
|
|
|
return uri
|
|
|
|
except Exception as e:
|
|
print(f"Failed to sync card to PDS: {e}")
|
|
return None
|
|
|
|
async def sync_all_user_cards(self, user_id: int, user_did: str) -> int:
|
|
"""
|
|
Sync all user's cards to PDS
|
|
|
|
Args:
|
|
user_id: Database user ID
|
|
user_did: User's DID
|
|
|
|
Returns:
|
|
Number of cards synced
|
|
"""
|
|
# Get all user cards from database
|
|
user_cards = await self.card_repo.get_user_cards(user_id)
|
|
|
|
synced_count = 0
|
|
for card in user_cards:
|
|
uri = await self.sync_card_to_pds(card, user_did)
|
|
if uri:
|
|
synced_count += 1
|
|
|
|
return synced_count
|
|
|
|
async def import_cards_from_pds(self, user_did: str) -> int:
|
|
"""
|
|
Import cards from user's PDS to database
|
|
|
|
Args:
|
|
user_did: User's DID
|
|
|
|
Returns:
|
|
Number of cards imported
|
|
"""
|
|
if not self.atproto_service.client:
|
|
return 0
|
|
|
|
# Get user from database
|
|
user = await self.user_repo.get_by_did(user_did)
|
|
if not user:
|
|
return 0
|
|
|
|
# Get cards from PDS
|
|
pds_cards = await self.atproto_service.get_user_cards(
|
|
did=user_did,
|
|
collection=CardLexicon.LEXICON_ID
|
|
)
|
|
|
|
imported_count = 0
|
|
for pds_card in pds_cards:
|
|
# Check if card already exists
|
|
existing_count = await self.card_repo.count_user_cards(
|
|
user.id,
|
|
pds_card.get("cardId")
|
|
)
|
|
|
|
if existing_count == 0:
|
|
# Import card
|
|
await self.card_repo.create_user_card(
|
|
user_id=user.id,
|
|
card_id=pds_card.get("cardId"),
|
|
cp=pds_card.get("cp"),
|
|
status=pds_card.get("status"),
|
|
skill=pds_card.get("skill"),
|
|
is_unique=pds_card.get("isUnique", False)
|
|
)
|
|
imported_count += 1
|
|
|
|
await self.db_session.commit()
|
|
return imported_count
|
|
|
|
async def verify_card_ownership(
|
|
self,
|
|
user_did: str,
|
|
card_id: int,
|
|
unique_id: Optional[str] = None
|
|
) -> bool:
|
|
"""
|
|
Verify user owns a card by checking both database and PDS
|
|
|
|
Args:
|
|
user_did: User's DID
|
|
card_id: Card type ID
|
|
unique_id: Unique ID for unique cards
|
|
|
|
Returns:
|
|
True if user owns the card
|
|
"""
|
|
# Check database first
|
|
user = await self.user_repo.get_by_did(user_did)
|
|
if user:
|
|
user_cards = await self.card_repo.get_user_cards(user.id)
|
|
for card in user_cards:
|
|
if card.card_id == card_id:
|
|
if not unique_id or str(card.unique_id) == unique_id:
|
|
return True
|
|
|
|
# Check PDS if configured
|
|
if self.atproto_service.client:
|
|
try:
|
|
pds_cards = await self.atproto_service.get_user_cards(user_did)
|
|
for card in pds_cards:
|
|
if card.get("cardId") == card_id:
|
|
if not unique_id or card.get("uniqueId") == unique_id:
|
|
return True
|
|
except Exception:
|
|
pass
|
|
|
|
return False |