- Add main application files (main.py, models.py, schemas.py, etc.) - Add routers for all features (waiting, attendance, members, etc.) - Add HTML templates for admin and user interfaces - Add migration scripts and utility files - Add Docker configuration - Add documentation files - Add .gitignore to exclude database and cache files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
310 lines
9.0 KiB
Python
310 lines
9.0 KiB
Python
"""
|
|
사용자 관리 라우터
|
|
- 사용자 목록 조회
|
|
- 사용자 생성
|
|
- 사용자 상세 조회
|
|
- 사용자 수정
|
|
- 사용자 비활성화
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from datetime import datetime
|
|
from typing import List
|
|
|
|
from database import get_db
|
|
from models import User, Store
|
|
from schemas import User as UserSchema, UserCreate, UserUpdate
|
|
from auth import get_current_user, require_franchise_admin, get_password_hash
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=List[UserSchema])
|
|
async def get_users(
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 목록 조회
|
|
|
|
프랜차이즈 관리자만 접근 가능
|
|
자신의 프랜차이즈에 속한 사용자만 조회
|
|
|
|
Returns:
|
|
List[User]: 프랜차이즈의 모든 사용자 목록
|
|
"""
|
|
users = db.query(User).filter(
|
|
User.franchise_id == current_user.franchise_id
|
|
).order_by(User.created_at.desc()).all()
|
|
|
|
return users
|
|
|
|
|
|
@router.post("/", response_model=UserSchema, status_code=status.HTTP_201_CREATED)
|
|
async def create_user(
|
|
user_create: UserCreate,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 생성
|
|
|
|
프랜차이즈 관리자만 접근 가능
|
|
|
|
Args:
|
|
user_create: 생성할 사용자 정보
|
|
|
|
Returns:
|
|
User: 생성된 사용자 정보
|
|
"""
|
|
# 사용자명 중복 체크
|
|
existing_user = db.query(User).filter(User.username == user_create.username).first()
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 존재하는 사용자명입니다"
|
|
)
|
|
|
|
# 역할 검증
|
|
if user_create.role not in ['franchise_admin', 'store_admin', 'franchise_manager']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="올바르지 않은 역할입니다."
|
|
)
|
|
|
|
# 매장 관리자인 경우 매장 ID 필수
|
|
if user_create.role == 'store_admin' and not user_create.store_id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="매장 관리자는 매장 ID가 필요합니다"
|
|
)
|
|
|
|
# 매장 ID가 있는 경우 해당 매장이 자신의 프랜차이즈에 속하는지 확인
|
|
if user_create.store_id:
|
|
store = db.query(Store).filter(
|
|
Store.id == user_create.store_id,
|
|
Store.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없거나 접근 권한이 없습니다"
|
|
)
|
|
|
|
# 사용자 생성
|
|
password_hash = get_password_hash(user_create.password)
|
|
|
|
new_user = User(
|
|
username=user_create.username,
|
|
password_hash=password_hash,
|
|
role=user_create.role,
|
|
franchise_id=current_user.franchise_id,
|
|
store_id=user_create.store_id,
|
|
is_active=True
|
|
)
|
|
|
|
# 중간 관리자의 매장 권한 설정
|
|
if user_create.role == 'franchise_manager' and user_create.managed_store_ids:
|
|
stores = db.query(Store).filter(
|
|
Store.id.in_(user_create.managed_store_ids),
|
|
Store.franchise_id == current_user.franchise_id
|
|
).all()
|
|
|
|
if len(stores) != len(user_create.managed_store_ids):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="일부 매장을 찾을 수 없거나 접근 권한이 없습니다"
|
|
)
|
|
new_user.managed_stores = stores
|
|
|
|
db.add(new_user)
|
|
db.commit()
|
|
db.refresh(new_user)
|
|
|
|
return new_user
|
|
|
|
|
|
@router.get("/{user_id}", response_model=UserSchema)
|
|
async def get_user(
|
|
user_id: int,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 상세 조회
|
|
|
|
Args:
|
|
user_id: 사용자 ID
|
|
|
|
Returns:
|
|
User: 사용자 상세 정보
|
|
"""
|
|
user = db.query(User).filter(
|
|
User.id == user_id,
|
|
User.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
return user
|
|
|
|
|
|
@router.put("/{user_id}", response_model=UserSchema)
|
|
async def update_user(
|
|
user_id: int,
|
|
user_update: UserUpdate,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 정보 수정
|
|
|
|
Args:
|
|
user_id: 사용자 ID
|
|
user_update: 수정할 사용자 정보
|
|
|
|
Returns:
|
|
User: 수정된 사용자 정보
|
|
"""
|
|
user = db.query(User).filter(
|
|
User.id == user_id,
|
|
User.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
# 사용자명 변경 시 중복 체크
|
|
if user_update.username and user_update.username != user.username:
|
|
existing_user = db.query(User).filter(User.username == user_update.username).first()
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 존재하는 사용자명입니다"
|
|
)
|
|
|
|
# 역할 변경 시 검증
|
|
if user_update.role and user_update.role not in ['franchise_admin', 'store_admin', 'franchise_manager']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="올바르지 않은 역할입니다."
|
|
)
|
|
|
|
# 매장 ID 변경 시 검증
|
|
if user_update.store_id:
|
|
store = db.query(Store).filter(
|
|
Store.id == user_update.store_id,
|
|
Store.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not store:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="매장을 찾을 수 없거나 접근 권한이 없습니다"
|
|
)
|
|
|
|
# 수정
|
|
update_data = user_update.dict(exclude_unset=True, exclude={'password', 'managed_store_ids'})
|
|
for key, value in update_data.items():
|
|
setattr(user, key, value)
|
|
|
|
# 중간 관리자 매장 권한 업데이트
|
|
if user_update.role == 'franchise_manager' or (not user_update.role and user.role == 'franchise_manager'):
|
|
if user_update.managed_store_ids is not None:
|
|
stores = db.query(Store).filter(
|
|
Store.id.in_(user_update.managed_store_ids),
|
|
Store.franchise_id == current_user.franchise_id
|
|
).all()
|
|
user.managed_stores = stores
|
|
elif user_update.role and user_update.role != 'franchise_manager':
|
|
# 다른 역할로 변경 시 관리 매장 초기화
|
|
user.managed_stores = []
|
|
|
|
# 비밀번호 변경이 있는 경우
|
|
if user_update.password:
|
|
user.password_hash = get_password_hash(user_update.password)
|
|
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(user)
|
|
|
|
return user
|
|
|
|
|
|
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_user(
|
|
user_id: int,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 비활성화
|
|
|
|
실제로 삭제하지 않고 is_active를 False로 변경
|
|
자기 자신은 비활성화할 수 없음
|
|
|
|
Args:
|
|
user_id: 사용자 ID
|
|
"""
|
|
# 자기 자신은 비활성화할 수 없음
|
|
if user_id == current_user.id:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="자기 자신을 비활성화할 수 없습니다"
|
|
)
|
|
|
|
user = db.query(User).filter(
|
|
User.id == user_id,
|
|
User.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
user.is_active = False
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
|
|
|
|
@router.post("/{user_id}/activate", response_model=UserSchema)
|
|
async def activate_user(
|
|
user_id: int,
|
|
current_user: User = Depends(require_franchise_admin),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""사용자 활성화
|
|
|
|
Args:
|
|
user_id: 사용자 ID
|
|
|
|
Returns:
|
|
User: 활성화된 사용자 정보
|
|
"""
|
|
user = db.query(User).filter(
|
|
User.id == user_id,
|
|
User.franchise_id == current_user.franchise_id
|
|
).first()
|
|
|
|
if not user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
user.is_active = True
|
|
user.updated_at = datetime.now()
|
|
|
|
db.commit()
|
|
db.refresh(user)
|
|
|
|
return user
|