173 lines
5.4 KiB
Python
173 lines
5.4 KiB
Python
import logging
|
|
import os
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.config import settings
|
|
from app.core.database import get_db
|
|
from app.core.security import get_password_hash, verify_password
|
|
from app.dependencies.auth import get_current_active_user, get_current_admin
|
|
from app.models.user import User, UserRole
|
|
from app.schemas.user import (
|
|
User as UserSchema,
|
|
)
|
|
from app.schemas.user import (
|
|
UserPasswordChange,
|
|
UserUpdate,
|
|
UserWithAddress,
|
|
)
|
|
|
|
router = APIRouter()
|
|
logger = logging.getLogger(__name__)
|
|
|
|
@router.get("/", response_model=list[UserSchema])
|
|
async def get_users(
|
|
skip: int = 0,
|
|
limit: int = 100,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_admin)
|
|
):
|
|
"""
|
|
Get all users (admin only).
|
|
"""
|
|
users = db.query(User).offset(skip).limit(limit).all()
|
|
return users
|
|
|
|
@router.get("/{user_id}", response_model=UserWithAddress)
|
|
async def get_user(
|
|
user_id: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Get a specific user by ID.
|
|
Regular users can only get their own user details.
|
|
Admin users can get any user details.
|
|
"""
|
|
if current_user.id != user_id and current_user.role != UserRole.ADMIN:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions to access this user's data"
|
|
)
|
|
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
return user
|
|
|
|
@router.put("/me", response_model=UserWithAddress)
|
|
async def update_user_me(
|
|
user_update: UserUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Update current user information.
|
|
"""
|
|
for key, value in user_update.dict(exclude_unset=True).items():
|
|
setattr(current_user, key, value)
|
|
|
|
db.commit()
|
|
db.refresh(current_user)
|
|
logger.info(f"User {current_user.email} updated their profile")
|
|
return current_user
|
|
|
|
@router.post("/me/change-password", response_model=dict)
|
|
async def change_password(
|
|
password_data: UserPasswordChange,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Change current user's password.
|
|
"""
|
|
if not verify_password(password_data.current_password, current_user.hashed_password):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Incorrect current password"
|
|
)
|
|
|
|
current_user.hashed_password = get_password_hash(password_data.new_password)
|
|
db.commit()
|
|
logger.info(f"User {current_user.email} changed their password")
|
|
return {"message": "Password updated successfully"}
|
|
|
|
@router.post("/me/profile-image", response_model=dict)
|
|
async def upload_profile_image(
|
|
file: UploadFile = File(...),
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Upload a profile image for the current user.
|
|
"""
|
|
# Validate the file
|
|
content_type = file.content_type
|
|
if not content_type.startswith("image/"):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="File must be an image"
|
|
)
|
|
|
|
# Create user images directory if it doesn't exist
|
|
user_images_dir = settings.USER_IMAGES_DIR
|
|
user_images_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
# Generate a unique filename
|
|
file_extension = os.path.splitext(file.filename)[1]
|
|
unique_filename = f"{uuid.uuid4()}{file_extension}"
|
|
file_path = user_images_dir / unique_filename
|
|
|
|
# Save the file
|
|
with open(file_path, "wb") as buffer:
|
|
buffer.write(await file.read())
|
|
|
|
# Update the user's profile image in the database
|
|
relative_path = f"/storage/user_images/{unique_filename}"
|
|
current_user.profile_image = relative_path
|
|
db.commit()
|
|
|
|
logger.info(f"User {current_user.email} uploaded a new profile image")
|
|
return {"filename": unique_filename, "path": relative_path}
|
|
|
|
@router.delete("/{user_id}", response_model=dict)
|
|
async def delete_user(
|
|
user_id: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(get_current_active_user)
|
|
):
|
|
"""
|
|
Delete a user.
|
|
Regular users can only delete their own account.
|
|
Admin users can delete any user.
|
|
"""
|
|
if current_user.id != user_id and current_user.role != UserRole.ADMIN:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Not enough permissions to delete this user"
|
|
)
|
|
|
|
user = db.query(User).filter(User.id == user_id).first()
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="User not found"
|
|
)
|
|
|
|
# Soft delete the user by setting is_active to False
|
|
# In a real-world application, you might want to consider:
|
|
# 1. Hard deleting the user data for GDPR compliance
|
|
# 2. Anonymizing the user data instead of deleting
|
|
# 3. Setting up a scheduled task to actually delete inactive users after a certain period
|
|
user.is_active = False
|
|
db.commit()
|
|
|
|
logger.info(f"User {user.email} was marked as inactive (deleted)")
|
|
return {"message": "User successfully deleted"}
|