rag-api-node-1 / src /api /routes /accounts.py
Peterase's picture
feat(rag): implement hybrid search with live sources and production-grade intent classification
a63c61f
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from src.infrastructure.database import get_db
from src.core.domain.schemas import UserCreate, UserResponse, UserUpdate, PasswordChange, AdminUserUpdate
from src.core.domain.db_models import User, UserRole
from src.core.security import (
get_password_hash, verify_password,
get_current_user, require_super_admin
)
router = APIRouter()
# ── Public ────────────────────────────────────────────────────────────────────
@router.post("/register", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
def register(user: UserCreate, db: Session = Depends(get_db)):
"""Register a new user account (role defaults to 'user')."""
existing = db.query(User).filter(
(User.username == user.username) | (User.email == user.email)
).first()
if existing:
raise HTTPException(status_code=400, detail="Username or email already registered")
new_user = User(
username=user.username,
email=user.email,
full_name=user.full_name,
hashed_password=get_password_hash(user.password),
role=UserRole.user,
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
# ── Authenticated user ────────────────────────────────────────────────────────
@router.patch("/me", response_model=UserResponse)
def update_profile(
body: UserUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Update own profile (username, full_name, email)."""
if body.username and body.username != current_user.username:
if db.query(User).filter(User.username == body.username).first():
raise HTTPException(status_code=400, detail="Username already taken")
current_user.username = body.username
if body.email and body.email != current_user.email:
if db.query(User).filter(User.email == body.email).first():
raise HTTPException(status_code=400, detail="Email already in use")
current_user.email = body.email
if body.full_name is not None:
current_user.full_name = body.full_name
db.commit()
db.refresh(current_user)
return current_user
@router.post("/me/change-password", status_code=status.HTTP_204_NO_CONTENT)
def change_password(
body: PasswordChange,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Change own password."""
if not verify_password(body.current_password, current_user.hashed_password):
raise HTTPException(status_code=400, detail="Current password is incorrect")
current_user.hashed_password = get_password_hash(body.new_password)
db.commit()
# ── Super admin only ──────────────────────────────────────────────────────────
@router.get("/users", response_model=List[UserResponse])
def list_users(
skip: int = 0,
limit: int = 50,
_admin: User = Depends(require_super_admin),
db: Session = Depends(get_db)
):
"""List all users (super_admin only)."""
return db.query(User).offset(skip).limit(limit).all()
@router.get("/users/{user_id}", response_model=UserResponse)
def get_user(
user_id: int,
_admin: User = Depends(require_super_admin),
db: Session = Depends(get_db)
):
"""Get a specific user by ID (super_admin only)."""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
@router.patch("/users/{user_id}", response_model=UserResponse)
def admin_update_user(
user_id: int,
body: AdminUserUpdate,
_admin: User = Depends(require_super_admin),
db: Session = Depends(get_db)
):
"""Update a user's role or active status (super_admin only)."""
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
if body.is_active is not None:
user.is_active = body.is_active
if body.role is not None:
try:
user.role = UserRole(body.role)
except ValueError:
raise HTTPException(status_code=400, detail="Invalid role. Must be 'super_admin' or 'user'")
db.commit()
db.refresh(user)
return user
@router.delete("/users/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_user(
user_id: int,
admin: User = Depends(require_super_admin),
db: Session = Depends(get_db)
):
"""Delete a user (super_admin only). Cannot delete yourself."""
if user_id == admin.id:
raise HTTPException(status_code=400, detail="Cannot delete your own account")
user = db.query(User).filter(User.id == user_id).first()
if not user:
raise HTTPException(status_code=404, detail="User not found")
db.delete(user)
db.commit()