Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled
- 🎨 UI/UX 개선: 데본씽크 스타일 모던 디자인 적용 - 📁 컴포넌트 구조 개선: 폴더별 체계적 관리 (common/, bom/, materials/) - 🔧 BOM 관리 페이지 리팩토링: NewMaterialsPage → BOMManagementPage + 카테고리별 컴포넌트 분리 - 💾 구매신청 기능 개선: 선택된 자재 비활성화, 제목 편집, 엑셀 다운로드 - 📊 자재 표시 개선: 타입/서브타입 컬럼 정리, 상세 정보 복원 - 🐛 CSS 빌드 오류 수정: NewMaterialsPage.css 문법 오류 해결 - 📚 문서화: PAGES_GUIDE.md 추가, README에 Docker 캐시 문제 해결 가이드 추가 - 🔄 API 개선: 구매신청 자재 조회, 제목 수정 엔드포인트 추가
338 lines
11 KiB
Python
338 lines
11 KiB
Python
"""
|
|
회원가입 요청 및 관리자 승인 API
|
|
"""
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy import text
|
|
from pydantic import BaseModel
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
|
|
from ..database import get_db
|
|
from .auth_service import AuthService
|
|
from .models import UserRepository
|
|
from .middleware import get_current_user
|
|
from ..utils.logger import get_logger
|
|
|
|
logger = get_logger(__name__)
|
|
router = APIRouter(prefix="/auth", tags=["signup"])
|
|
|
|
|
|
class SignupRequest(BaseModel):
|
|
username: str
|
|
password: str
|
|
name: str
|
|
email: Optional[str] = None
|
|
department: Optional[str] = None
|
|
position: Optional[str] = None
|
|
phone: Optional[str] = None
|
|
reason: Optional[str] = None # 가입 사유
|
|
|
|
|
|
@router.post("/signup-request")
|
|
async def signup_request(
|
|
signup_data: SignupRequest,
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
회원가입 요청 (관리자 승인 대기)
|
|
|
|
Args:
|
|
signup_data: 가입 신청 정보
|
|
|
|
Returns:
|
|
dict: 요청 결과
|
|
"""
|
|
try:
|
|
# 중복 사용자명 확인
|
|
user_repo = UserRepository(db)
|
|
existing_user = user_repo.find_by_username(signup_data.username)
|
|
|
|
if existing_user:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 존재하는 사용자명입니다"
|
|
)
|
|
|
|
# 중복 이메일 확인
|
|
if signup_data.email:
|
|
check_email = text("SELECT id FROM users WHERE email = :email")
|
|
existing_email = db.execute(check_email, {"email": signup_data.email}).fetchone()
|
|
|
|
if existing_email:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="이미 등록된 이메일입니다"
|
|
)
|
|
|
|
# 비밀번호 해싱
|
|
import bcrypt
|
|
hashed_password = bcrypt.hashpw(
|
|
signup_data.password.encode('utf-8'),
|
|
bcrypt.gensalt()
|
|
).decode('utf-8')
|
|
|
|
# 승인 대기 상태로 사용자 생성
|
|
new_user = user_repo.create_user({
|
|
'username': signup_data.username,
|
|
'password': hashed_password, # 필드명은 'password'
|
|
'name': signup_data.name,
|
|
'email': signup_data.email,
|
|
'access_level': 'worker', # 기본 레벨 (승인 시 변경 가능)
|
|
'department': signup_data.department,
|
|
'position': signup_data.position,
|
|
'phone': signup_data.phone,
|
|
'role': 'user',
|
|
'is_active': False, # 하위 호환성
|
|
'status': 'pending' # 새로운 status 체계: 승인 대기
|
|
})
|
|
|
|
# 가입 사유 저장 (notes 컬럼 활용)
|
|
if signup_data.reason:
|
|
update_notes = text("UPDATE users SET notes = :reason WHERE user_id = :user_id")
|
|
db.execute(update_notes, {"reason": signup_data.reason, "user_id": new_user.user_id})
|
|
db.commit()
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "회원가입 요청이 전송되었습니다. 관리자 승인 후 이용 가능합니다.",
|
|
"user_id": new_user.user_id,
|
|
"username": new_user.username,
|
|
"status": "pending_approval"
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"회원가입 요청 실패: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"회원가입 요청 처리 중 오류가 발생했습니다: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/signup-requests")
|
|
async def get_signup_requests(
|
|
current_user: dict = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
회원가입 요청 목록 조회 (관리자 전용)
|
|
|
|
Returns:
|
|
dict: 승인 대기 중인 사용자 목록
|
|
"""
|
|
try:
|
|
# 관리자 권한 확인
|
|
if current_user.get('role') not in ['admin', 'system']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="관리자만 접근 가능합니다"
|
|
)
|
|
|
|
# 승인 대기 중인 사용자 조회 (status='pending'인 사용자)
|
|
query = text("""
|
|
SELECT
|
|
user_id, username, name, email, department, position,
|
|
phone, created_at, role, is_active, status
|
|
FROM users
|
|
WHERE status = 'pending'
|
|
ORDER BY created_at DESC
|
|
""")
|
|
|
|
results = db.execute(query).fetchall()
|
|
|
|
pending_users = []
|
|
for row in results:
|
|
pending_users.append({
|
|
"user_id": row.user_id,
|
|
"id": row.user_id, # 호환성을 위해 둘 다 제공
|
|
"username": row.username,
|
|
"name": row.name,
|
|
"email": row.email,
|
|
"department": row.department,
|
|
"position": row.position,
|
|
"phone": row.phone,
|
|
"role": row.role,
|
|
"created_at": row.created_at.isoformat() if row.created_at else None,
|
|
"requested_at": row.created_at.isoformat() if row.created_at else None,
|
|
"is_active": row.is_active
|
|
})
|
|
|
|
return {
|
|
"success": True,
|
|
"requests": pending_users,
|
|
"count": len(pending_users)
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"가입 요청 목록 조회 실패: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"가입 요청 목록 조회 실패: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/pending-signups/count")
|
|
async def get_pending_signups_count(
|
|
current_user: dict = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
승인 대기 중인 회원가입 수 조회 (관리자 전용)
|
|
|
|
Returns:
|
|
dict: 승인 대기 중인 사용자 수
|
|
"""
|
|
try:
|
|
# 관리자 권한 확인
|
|
if current_user.get('role') not in ['admin', 'system']:
|
|
return {"count": 0} # 관리자가 아니면 0 반환
|
|
|
|
# 승인 대기 중인 사용자 수 조회
|
|
query = text("""
|
|
SELECT COUNT(*) as count
|
|
FROM users
|
|
WHERE status = 'pending'
|
|
""")
|
|
|
|
result = db.execute(query).fetchone()
|
|
count = result.count if result else 0
|
|
|
|
return {"count": count}
|
|
|
|
except Exception as e:
|
|
logger.error(f"승인 대기 회원가입 수 조회 실패: {str(e)}")
|
|
return {"count": 0} # 오류 시 0 반환
|
|
|
|
|
|
@router.post("/approve-signup/{user_id}")
|
|
async def approve_signup(
|
|
user_id: int,
|
|
access_level: str = 'worker',
|
|
current_user: dict = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
회원가입 승인 (관리자 전용)
|
|
|
|
Args:
|
|
user_id: 승인할 사용자 ID
|
|
access_level: 부여할 접근 레벨 (worker, manager, admin)
|
|
|
|
Returns:
|
|
dict: 승인 결과
|
|
"""
|
|
try:
|
|
# 관리자 권한 확인
|
|
if current_user.get('role') not in ['admin', 'system']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="관리자만 접근 가능합니다"
|
|
)
|
|
|
|
# 사용자 활성화 및 접근 레벨 설정
|
|
update_query = text("""
|
|
UPDATE users
|
|
SET is_active = TRUE,
|
|
status = 'active',
|
|
access_level = :access_level,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
WHERE user_id = :user_id AND status = 'pending'
|
|
RETURNING user_id as id, username, name
|
|
""")
|
|
|
|
result = db.execute(update_query, {
|
|
"user_id": user_id,
|
|
"access_level": access_level
|
|
}).fetchone()
|
|
|
|
if not result:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="승인 대기 중인 사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
db.commit()
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"{result.name}님의 가입이 승인되었습니다",
|
|
"user": {
|
|
"id": result.id,
|
|
"username": result.username,
|
|
"name": result.name,
|
|
"access_level": access_level
|
|
}
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"가입 승인 실패: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"가입 승인 처리 중 오류가 발생했습니다: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.delete("/reject-signup/{user_id}")
|
|
async def reject_signup(
|
|
user_id: int,
|
|
current_user: dict = Depends(get_current_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""
|
|
회원가입 거부 (관리자 전용)
|
|
|
|
Args:
|
|
user_id: 거부할 사용자 ID
|
|
|
|
Returns:
|
|
dict: 거부 결과
|
|
"""
|
|
try:
|
|
# 관리자 권한 확인
|
|
if current_user.get('role') not in ['admin', 'system']:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="관리자만 접근 가능합니다"
|
|
)
|
|
|
|
# 승인 대기 사용자 삭제
|
|
delete_query = text("""
|
|
DELETE FROM users
|
|
WHERE user_id = :user_id AND is_active = FALSE
|
|
RETURNING username, name
|
|
""")
|
|
|
|
result = db.execute(delete_query, {"user_id": user_id}).fetchone()
|
|
|
|
if not result:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="승인 대기 중인 사용자를 찾을 수 없습니다"
|
|
)
|
|
|
|
db.commit()
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"{result.name}님의 가입 요청이 거부되었습니다"
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"가입 거부 실패: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"가입 거부 처리 중 오류가 발생했습니다: {str(e)}"
|
|
)
|
|
|