Files
PROJECT-CONTORL/backend/app/api/departments/router.py
beabigegg 1fda7da2c2 feat: implement user authentication module
- Backend (FastAPI):
  - External API authentication (pj-auth-api.vercel.app)
  - JWT token validation with Redis session storage
  - RBAC with department isolation
  - User, Role, Department models with pjctrl_ prefix
  - Alembic migrations with project-specific version table
  - Complete test coverage (13 tests)

- Frontend (React + Vite):
  - AuthContext for state management
  - Login page with error handling
  - Protected route component
  - Dashboard with user info display

- OpenSpec:
  - 7 capability specs defined
  - add-user-auth change archived

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 23:41:37 +08:00

153 lines
4.7 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List
from app.core.database import get_db
from app.models.department import Department
from app.models.user import User
from app.schemas.department import DepartmentCreate, DepartmentUpdate, DepartmentResponse
from app.middleware.auth import require_permission, require_system_admin
router = APIRouter()
@router.get("", response_model=List[DepartmentResponse])
async def list_departments(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("users.read")),
):
"""
List all departments.
"""
departments = db.query(Department).offset(skip).limit(limit).all()
return departments
@router.get("/{department_id}", response_model=DepartmentResponse)
async def get_department(
department_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(require_permission("users.read")),
):
"""
Get a specific department by ID.
"""
department = db.query(Department).filter(Department.id == department_id).first()
if not department:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Department not found",
)
return department
@router.post("", response_model=DepartmentResponse, status_code=status.HTTP_201_CREATED)
async def create_department(
department_data: DepartmentCreate,
db: Session = Depends(get_db),
current_user: User = Depends(require_system_admin),
):
"""
Create a new department. Requires system admin.
"""
# Check if parent exists if specified
if department_data.parent_id:
parent = db.query(Department).filter(
Department.id == department_data.parent_id
).first()
if not parent:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Parent department not found",
)
department = Department(**department_data.model_dump())
db.add(department)
db.commit()
db.refresh(department)
return department
@router.patch("/{department_id}", response_model=DepartmentResponse)
async def update_department(
department_id: str,
department_update: DepartmentUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(require_system_admin),
):
"""
Update a department. Requires system admin.
"""
department = db.query(Department).filter(Department.id == department_id).first()
if not department:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Department not found",
)
# Check if new parent exists if specified
update_data = department_update.model_dump(exclude_unset=True)
if "parent_id" in update_data and update_data["parent_id"]:
# Prevent circular reference
if update_data["parent_id"] == department_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Department cannot be its own parent",
)
parent = db.query(Department).filter(
Department.id == update_data["parent_id"]
).first()
if not parent:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Parent department not found",
)
for field, value in update_data.items():
setattr(department, field, value)
db.commit()
db.refresh(department)
return department
@router.delete("/{department_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_department(
department_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(require_system_admin),
):
"""
Delete a department. Requires system admin.
"""
department = db.query(Department).filter(Department.id == department_id).first()
if not department:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Department not found",
)
# Check if department has users
user_count = db.query(User).filter(User.department_id == department_id).count()
if user_count > 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Cannot delete department with {user_count} users",
)
# Check if department has children
child_count = db.query(Department).filter(
Department.parent_id == department_id
).count()
if child_count > 0:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Cannot delete department with {child_count} child departments",
)
db.delete(department)
db.commit()