Files
syndarix/backend/app/services/session_cleanup.py
Felipe Cardoso 98b455fdc3 refactor(backend): enforce route→service→repo layered architecture
- introduce custom repository exception hierarchy (DuplicateEntryError,
  IntegrityConstraintError, InvalidInputError) replacing raw ValueError
- eliminate all direct repository imports and raw SQL from route layer
- add UserService, SessionService, OrganizationService to service layer
- add get_stats/get_org_distribution service methods replacing admin inline SQL
- fix timing side-channel in authenticate_user via dummy bcrypt check
- replace SHA-256 client secret fallback with explicit InvalidClientError
- replace assert with InvalidGrantError in authorization code exchange
- replace N+1 token revocation loops with bulk UPDATE statements
- rename oauth account token fields (drop misleading 'encrypted' suffix)
- add Alembic migration 0003 for token field column rename
- add 45 new service/repository tests; 975 passing, 94% coverage
2026-02-27 09:32:57 +01:00

89 lines
2.5 KiB
Python
Executable File

"""
Background job for cleaning up expired sessions.
This service runs periodically to remove old session records from the database.
"""
import logging
from datetime import UTC, datetime
from app.core.database import SessionLocal
from app.repositories.session import session_repo as session_crud
logger = logging.getLogger(__name__)
async def cleanup_expired_sessions(keep_days: int = 30) -> int:
"""
Clean up expired and inactive sessions.
This removes sessions that are:
- Inactive (is_active=False) AND
- Expired (expires_at < now) AND
- Older than keep_days
Args:
keep_days: Keep inactive sessions for this many days for audit purposes
Returns:
Number of sessions deleted
"""
logger.info("Starting session cleanup job...")
async with SessionLocal() as db:
try:
# Use CRUD method to cleanup
count = await session_crud.cleanup_expired(db, keep_days=keep_days)
logger.info(f"Session cleanup complete: {count} sessions deleted")
return count
except Exception as e:
logger.error(f"Error during session cleanup: {e!s}", exc_info=True)
return 0
async def get_session_statistics() -> dict:
"""
Get statistics about current sessions.
Returns:
Dictionary with session stats
"""
async with SessionLocal() as db:
try:
from sqlalchemy import func, select
from app.models.user_session import UserSession
total_result = await db.execute(select(func.count(UserSession.id)))
total_sessions = total_result.scalar_one()
active_result = await db.execute(
select(func.count(UserSession.id)).where(UserSession.is_active)
)
active_sessions = active_result.scalar_one()
expired_result = await db.execute(
select(func.count(UserSession.id)).where(
UserSession.expires_at < datetime.now(UTC)
)
)
expired_sessions = expired_result.scalar_one()
stats = {
"total": total_sessions,
"active": active_sessions,
"inactive": total_sessions - active_sessions,
"expired": expired_sessions,
}
logger.info(f"Session statistics: {stats}")
return stats
except Exception as e:
logger.error(f"Error getting session statistics: {e!s}", exc_info=True)
return {}