""" Token Repository - Data Access Layer for Token operations Following Python Quick Start Guide: - Repository layer contains ONLY database operations - No business logic (that goes in services) - Simple CRUD operations and queries """ from typing import Optional, List, Dict, Any from datetime import datetime from sqlalchemy import func from app.core.database import db from models import AuthorizationCode, AccessToken, User, Client class TokenRepository: """ Token repository - handles all token database operations. Responsibilities: - CRUD operations for authorization codes and access tokens - Database queries - No business logic """ def __init__(self, db_session=None): """Initialize repository with database session.""" self.db = db_session or db.session # Authorization Code operations def find_auth_code_by_code(self, code: str) -> Optional[AuthorizationCode]: """Find authorization code by code value.""" return AuthorizationCode.query.filter_by(code=code).first() def create_auth_code(self, auth_code: AuthorizationCode) -> AuthorizationCode: """ Create a new authorization code. Args: auth_code: AuthorizationCode object to create Returns: Created authorization code """ self.db.add(auth_code) self.db.commit() return auth_code def update_auth_code(self, auth_code: AuthorizationCode) -> AuthorizationCode: """ Update an existing authorization code. Args: auth_code: AuthorizationCode object with updated fields Returns: Updated authorization code """ self.db.commit() return auth_code # Access Token operations def find_access_token_by_token(self, token: str) -> Optional[AccessToken]: """Find access token by token value.""" return AccessToken.query.filter_by(token=token).first() def create_access_token(self, access_token: AccessToken) -> AccessToken: """ Create a new access token. Args: access_token: AccessToken object to create Returns: Created access token """ self.db.add(access_token) self.db.commit() return access_token def update_access_token(self, access_token: AccessToken) -> AccessToken: """ Update an existing access token. Args: access_token: AccessToken object with updated fields Returns: Updated access token """ self.db.commit() return access_token def rollback(self) -> None: """Rollback current transaction.""" self.db.rollback() # Analytics operations def get_active_tokens_by_client(self) -> List[Dict[str, Any]]: """ Get all active (non-expired, non-revoked) tokens grouped by client. Returns: List of dicts with client_id, client_name, user_id, username, email, created_at """ now = datetime.utcnow() query = ( self.db.query( AccessToken.client_id, Client.client_name, AccessToken.user_id, User.username, User.email, AccessToken.created_at, AccessToken.expires_at ) .join(User, AccessToken.user_id == User.id) .outerjoin(Client, AccessToken.client_id == Client.client_id) .filter(AccessToken.revoked == False) .filter(AccessToken.expires_at > now) .order_by(Client.client_name, User.username) ) results = [] for row in query.all(): results.append({ 'client_id': row.client_id, 'client_name': row.client_name or 'Unknown Client', 'user_id': row.user_id, 'username': row.username, 'email': row.email, 'created_at': row.created_at, 'expires_at': row.expires_at }) return results def get_active_sessions_summary(self) -> List[Dict[str, Any]]: """ Get summary of active sessions grouped by client. Returns: List of dicts with client_id, client_name, active_users_count, total_tokens """ now = datetime.utcnow() query = ( self.db.query( AccessToken.client_id, Client.client_name, func.count(func.distinct(AccessToken.user_id)).label('active_users'), func.count(AccessToken.id).label('total_tokens') ) .outerjoin(Client, AccessToken.client_id == Client.client_id) .filter(AccessToken.revoked == False) .filter(AccessToken.expires_at > now) .group_by(AccessToken.client_id, Client.client_name) .order_by(func.count(func.distinct(AccessToken.user_id)).desc()) ) results = [] for row in query.all(): results.append({ 'client_id': row.client_id or 'unknown', 'client_name': row.client_name or 'Unknown Client', 'active_users_count': row.active_users, 'total_tokens': row.total_tokens }) return results