Add subcription feature 5

This commit is contained in:
Urtzi Alfaro
2026-01-16 09:55:54 +01:00
parent 483a9f64cd
commit 6b43116efd
51 changed files with 1428 additions and 312 deletions

View File

@@ -0,0 +1,124 @@
# services/auth/app/repositories/password_reset_repository.py
"""
Password reset token repository
Repository for password reset token operations
"""
from typing import Optional, Dict, Any
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_, text
from datetime import datetime, timezone
import structlog
import uuid
from .base import AuthBaseRepository
from app.models.password_reset_tokens import PasswordResetToken
from shared.database.exceptions import DatabaseError
logger = structlog.get_logger()
class PasswordResetTokenRepository(AuthBaseRepository):
"""Repository for password reset token operations"""
def __init__(self, session: AsyncSession):
super().__init__(PasswordResetToken, session)
async def create_token(self, user_id: str, token: str, expires_at: datetime) -> PasswordResetToken:
"""Create a new password reset token"""
try:
token_data = {
"user_id": user_id,
"token": token,
"expires_at": expires_at,
"is_used": False
}
reset_token = await self.create(token_data)
logger.debug("Password reset token created",
user_id=user_id,
token_id=reset_token.id,
expires_at=expires_at)
return reset_token
except Exception as e:
logger.error("Failed to create password reset token",
user_id=user_id,
error=str(e))
raise DatabaseError(f"Failed to create password reset token: {str(e)}")
async def get_token_by_value(self, token: str) -> Optional[PasswordResetToken]:
"""Get password reset token by token value"""
try:
stmt = select(PasswordResetToken).where(
and_(
PasswordResetToken.token == token,
PasswordResetToken.is_used == False,
PasswordResetToken.expires_at > datetime.now(timezone.utc)
)
)
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
except Exception as e:
logger.error("Failed to get password reset token by value", error=str(e))
raise DatabaseError(f"Failed to get password reset token: {str(e)}")
async def mark_token_as_used(self, token_id: str) -> Optional[PasswordResetToken]:
"""Mark a password reset token as used"""
try:
return await self.update(token_id, {
"is_used": True,
"used_at": datetime.now(timezone.utc)
})
except Exception as e:
logger.error("Failed to mark password reset token as used",
token_id=token_id,
error=str(e))
raise DatabaseError(f"Failed to mark token as used: {str(e)}")
async def cleanup_expired_tokens(self) -> int:
"""Clean up expired password reset tokens"""
try:
now = datetime.now(timezone.utc)
# Delete expired tokens
query = text("""
DELETE FROM password_reset_tokens
WHERE expires_at < :now OR is_used = true
""")
result = await self.session.execute(query, {"now": now})
deleted_count = result.rowcount
logger.info("Cleaned up expired password reset tokens",
deleted_count=deleted_count)
return deleted_count
except Exception as e:
logger.error("Failed to cleanup expired password reset tokens", error=str(e))
raise DatabaseError(f"Token cleanup failed: {str(e)}")
async def get_valid_token_for_user(self, user_id: str) -> Optional[PasswordResetToken]:
"""Get a valid (unused, not expired) password reset token for a user"""
try:
stmt = select(PasswordResetToken).where(
and_(
PasswordResetToken.user_id == user_id,
PasswordResetToken.is_used == False,
PasswordResetToken.expires_at > datetime.now(timezone.utc)
)
).order_by(PasswordResetToken.created_at.desc())
result = await self.session.execute(stmt)
return result.scalar_one_or_none()
except Exception as e:
logger.error("Failed to get valid token for user",
user_id=user_id,
error=str(e))
raise DatabaseError(f"Failed to get valid token for user: {str(e)}")