File size: 3,415 Bytes
fd48bc8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
from fastapi import APIRouter, Depends, HTTPException, Request, status
from pydantic import ValidationError
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from api.deps import get_admin_user
from core.database import get_db
from core.models import User
from core.schemas import MessageResponse, UserCreateRequest, UserOut, UserUpdateRequest
from core.security import hash_password

router = APIRouter(tags=["admin"])


@router.get("/admin/users", response_model=list[UserOut])
async def list_users(
    _: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    stmt = select(User).order_by(User.created_at.asc())
    rows = (await db.scalars(stmt)).all()
    users: list[UserOut] = []
    for row in rows:
        try:
            users.append(
                UserOut(
                    id=row.id,
                    email=row.email,
                    role=row.role,
                    is_active=row.is_active,
                    created_at=row.created_at,
                )
            )
        except ValidationError:
            # Skip malformed legacy rows so admin endpoints remain available.
            continue

    return users


@router.post("/admin/users", response_model=MessageResponse)
async def create_user(
    payload: UserCreateRequest,
    _: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    existing = await db.scalar(select(User).where(User.email == payload.email.lower()))
    if existing:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="User already exists",
        )

    user = User(
        email=payload.email.lower(),
        password_hash=hash_password(payload.password),
        role="user",
        is_active=True,
    )
    db.add(user)
    await db.commit()

    return MessageResponse(message=f"User {user.email} created")


@router.patch("/admin/users/{user_id}", response_model=MessageResponse)
async def update_user(
    user_id: str,
    payload: UserUpdateRequest,
    _: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    if payload.email:
        conflict = await db.scalar(
            select(User).where(User.email == payload.email.lower(), User.id != user.id)
        )
        if conflict:
            raise HTTPException(status_code=400, detail="Email already in use")
        user.email = payload.email.lower()

    if payload.password:
        user.password_hash = hash_password(payload.password)

    if not payload.email and not payload.password:
        raise HTTPException(status_code=400, detail="No changes provided")

    await db.commit()
    return MessageResponse(message="User updated")


@router.delete("/admin/users/{user_id}", response_model=MessageResponse)
async def delete_user(
    user_id: str,
    current_admin: User = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
):
    if str(current_admin.id) == user_id:
        raise HTTPException(status_code=400, detail="Cannot delete your own account")

    user = await db.get(User, user_id)
    if not user:
        raise HTTPException(status_code=404, detail="User not found")

    await db.delete(user)
    await db.commit()
    return MessageResponse(message="User deleted")