Fix events

This commit is contained in:
Urtzi Alfaro
2025-07-17 19:03:11 +02:00
parent 013d32d00f
commit 599e335fbb
3 changed files with 355 additions and 36 deletions

View File

@@ -0,0 +1,119 @@
"""
User management API routes
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from typing import List
import logging
from app.core.database import get_db
from app.schemas.auth import UserResponse, PasswordChangeRequest
from app.services.user_service import UserService
from app.core.auth import get_current_user
from app.models.users import User
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/me", response_model=UserResponse)
async def get_current_user_info(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get current user information"""
try:
return UserResponse(
id=str(current_user.id),
email=current_user.email,
full_name=current_user.full_name,
is_active=current_user.is_active,
is_verified=current_user.is_verified,
tenant_id=str(current_user.tenant_id) if current_user.tenant_id else None,
role=current_user.role,
phone=current_user.phone,
language=current_user.language,
timezone=current_user.timezone,
created_at=current_user.created_at,
last_login=current_user.last_login
)
except Exception as e:
logger.error(f"Get current user error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get user information"
)
@router.put("/me", response_model=UserResponse)
async def update_current_user(
user_update: dict,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Update current user information"""
try:
updated_user = await UserService.update_user(current_user.id, user_update, db)
return UserResponse(
id=str(updated_user.id),
email=updated_user.email,
full_name=updated_user.full_name,
is_active=updated_user.is_active,
is_verified=updated_user.is_verified,
tenant_id=str(updated_user.tenant_id) if updated_user.tenant_id else None,
role=updated_user.role,
phone=updated_user.phone,
language=updated_user.language,
timezone=updated_user.timezone,
created_at=updated_user.created_at,
last_login=updated_user.last_login
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Update user error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update user"
)
@router.post("/change-password")
async def change_password(
password_data: PasswordChangeRequest,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Change user password"""
try:
await UserService.change_password(
current_user.id,
password_data.current_password,
password_data.new_password,
db
)
return {"message": "Password changed successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Password change error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to change password"
)
@router.delete("/me")
async def delete_current_user(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Delete current user account"""
try:
await UserService.delete_user(current_user.id, db)
return {"message": "User account deleted successfully"}
except HTTPException:
raise
except Exception as e:
logger.error(f"Delete user error: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete user account"
)

View File

@@ -0,0 +1,151 @@
"""
User service for managing user operations
"""
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete
from fastapi import HTTPException, status
from passlib.context import CryptContext
import logging
from app.models.users import User
from app.core.config import settings
logger = logging.getLogger(__name__)
# Password hashing
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class UserService:
"""Service for user management operations"""
@staticmethod
async def get_user_by_id(user_id: str, db: AsyncSession) -> User:
"""Get user by ID"""
try:
result = await db.execute(
select(User).where(User.id == user_id)
)
user = result.scalar_one_or_none()
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
)
return user
except Exception as e:
logger.error(f"Error getting user by ID {user_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get user"
)
@staticmethod
async def update_user(user_id: str, user_data: dict, db: AsyncSession) -> User:
"""Update user information"""
try:
# Get current user
user = await UserService.get_user_by_id(user_id, db)
# Update fields
update_data = {}
allowed_fields = ['full_name', 'phone', 'language', 'timezone']
for field in allowed_fields:
if field in user_data:
update_data[field] = user_data[field]
if update_data:
await db.execute(
update(User)
.where(User.id == user_id)
.values(**update_data)
)
await db.commit()
# Refresh user object
await db.refresh(user)
return user
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating user {user_id}: {e}")
await db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update user"
)
@staticmethod
async def change_password(
user_id: str,
current_password: str,
new_password: str,
db: AsyncSession
):
"""Change user password"""
try:
# Get current user
user = await UserService.get_user_by_id(user_id, db)
# Verify current password
if not pwd_context.verify(current_password, user.hashed_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Current password is incorrect"
)
# Hash new password
new_hashed_password = pwd_context.hash(new_password)
# Update password
await db.execute(
update(User)
.where(User.id == user_id)
.values(hashed_password=new_hashed_password)
)
await db.commit()
logger.info(f"Password changed for user {user_id}")
except HTTPException:
raise
except Exception as e:
logger.error(f"Error changing password for user {user_id}: {e}")
await db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to change password"
)
@staticmethod
async def delete_user(user_id: str, db: AsyncSession):
"""Delete user account"""
try:
# Get current user first
user = await UserService.get_user_by_id(user_id, db)
# Soft delete by deactivating
await db.execute(
update(User)
.where(User.id == user_id)
.values(is_active=False)
)
await db.commit()
logger.info(f"User {user_id} deactivated (soft delete)")
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting user {user_id}: {e}")
await db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to delete user"
)

View File

@@ -1,73 +1,122 @@
"""
Event definitions for microservices communication
- Simple class-based approach to avoid dataclass issues
"""
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Any, Optional
import uuid
@dataclass
class BaseEvent:
"""Base event class"""
event_id: str
event_type: str
service_name: str
timestamp: datetime
data: Dict[str, Any]
correlation_id: Optional[str] = None
def __post_init__(self):
if not self.event_id:
self.event_id = str(uuid.uuid4())
if not self.timestamp:
self.timestamp = datetime.now(datetime.timezone.utc)
def __init__(self, service_name: str, data: Dict[str, Any], event_type: str = "", correlation_id: Optional[str] = None):
self.service_name = service_name
self.data = data
self.event_type = event_type
self.event_id = str(uuid.uuid4())
self.timestamp = datetime.utcnow()
self.correlation_id = correlation_id
# Training Events
@dataclass
class TrainingStartedEvent(BaseEvent):
event_type: str = "training.started"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="training.started",
correlation_id=correlation_id
)
@dataclass
class TrainingCompletedEvent(BaseEvent):
event_type: str = "training.completed"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="training.completed",
correlation_id=correlation_id
)
@dataclass
class TrainingFailedEvent(BaseEvent):
event_type: str = "training.failed"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="training.failed",
correlation_id=correlation_id
)
# Forecasting Events
@dataclass
class ForecastGeneratedEvent(BaseEvent):
event_type: str = "forecast.generated"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="forecast.generated",
correlation_id=correlation_id
)
@dataclass
class ForecastRequestedEvent(BaseEvent):
event_type: str = "forecast.requested"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="forecast.requested",
correlation_id=correlation_id
)
# User Events
@dataclass
class UserRegisteredEvent(BaseEvent):
event_type: str = "user.registered"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="user.registered",
correlation_id=correlation_id
)
@dataclass
class UserLoginEvent(BaseEvent):
event_type: str = "user.login"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="user.login",
correlation_id=correlation_id
)
# Tenant Events
@dataclass
class TenantCreatedEvent(BaseEvent):
event_type: str = "tenant.created"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="tenant.created",
correlation_id=correlation_id
)
@dataclass
class TenantUpdatedEvent(BaseEvent):
event_type: str = "tenant.updated"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="tenant.updated",
correlation_id=correlation_id
)
# Notification Events
@dataclass
class NotificationSentEvent(BaseEvent):
event_type: str = "notification.sent"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="notification.sent",
correlation_id=correlation_id
)
@dataclass
class NotificationFailedEvent(BaseEvent):
event_type: str = "notification.failed"
def __init__(self, service_name: str, data: Dict[str, Any], correlation_id: Optional[str] = None):
super().__init__(
service_name=service_name,
data=data,
event_type="notification.failed",
correlation_id=correlation_id
)