Files
beabigegg 4b5a9c1d0a feat: complete LOW priority code quality improvements
Backend:
- LOW-002: Add Query validation with max page size limits (100)
- LOW-003: Replace magic strings with TaskStatus.is_done flag
- LOW-004: Add 'creation' trigger type validation
- Add action_executor.py with UpdateFieldAction and AutoAssignAction

Frontend:
- LOW-005: Replace TypeScript 'any' with 'unknown' + type guards
- LOW-006: Add ConfirmModal component with A11Y support
- LOW-007: Add ToastContext for user feedback notifications
- LOW-009: Add Skeleton components (17 loading states replaced)
- LOW-010: Setup Vitest with 21 tests for ConfirmModal and Skeleton

Components updated:
- App.tsx, ProtectedRoute.tsx, Spaces.tsx, Projects.tsx, Tasks.tsx
- ProjectSettings.tsx, AuditPage.tsx, WorkloadPage.tsx, ProjectHealthPage.tsx
- Comments.tsx, AttachmentList.tsx, TriggerList.tsx, TaskDetailModal.tsx
- NotificationBell.tsx, BlockerDialog.tsx, CalendarView.tsx, WorkloadUserDetail.tsx

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-07 21:24:36 +08:00

153 lines
4.9 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status, Query
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 = Query(0, ge=0, description="Number of departments to skip"),
limit: int = Query(100, ge=1, le=200, description="Max departments to return"),
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()