feat: 완전한 사용자 관리 및 로그 모니터링 시스템 구현
Some checks failed
SonarQube Analysis / SonarQube Scan (push) Has been cancelled

- 시스템 관리자/관리자 권한별 대시보드 기능 추가
- 사용자 관리 페이지: 계정 생성, 역할 변경, 사용자 삭제
- 시스템 로그 페이지: 로그인 로그, 시스템 오류 로그 조회
- 로그 모니터링 대시보드: 실시간 통계, 최근 활동, 오류 모니터링
- 프론트엔드 ErrorBoundary 및 오류 로깅 시스템 통합
- 계정 설정 페이지: 프로필 업데이트, 비밀번호 변경
- 3단계 권한 시스템 (system/admin/user) 완전 구현
- 시스템 관리자 계정 생성 기능 (hyungi/000000)
- 로그인 페이지 테스트 계정 안내 제거
- API 오류 수정: CORS, 이메일 검증, User 모델 import 등
This commit is contained in:
Hyungi Ahn
2025-09-09 12:58:14 +09:00
parent 881fc13580
commit 529777aa14
16 changed files with 4519 additions and 450 deletions

View File

@@ -6,6 +6,7 @@ TK-MP-Project 인증 시스템의 모든 컴포넌트를 노출
from .jwt_service import jwt_service, JWTService
from .auth_service import AuthService, get_auth_service
from .auth_controller import router as auth_router
from .setup_controller import router as setup_router
from .middleware import (
auth_middleware,
get_current_user,
@@ -39,6 +40,7 @@ __all__ = [
# 라우터
'auth_router',
'setup_router',
# 미들웨어 및 의존성
'auth_middleware',

View File

@@ -5,7 +5,7 @@ TK-FB-Project의 authController.js를 참고하여 FastAPI용으로 구현
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from pydantic import BaseModel, EmailStr, validator
from typing import Optional, List, Dict, Any
from ..database import get_db
@@ -29,11 +29,21 @@ class RegisterRequest(BaseModel):
username: str
password: str
name: str
email: Optional[EmailStr] = None
email: Optional[str] = None
access_level: str = 'worker'
department: Optional[str] = None
position: Optional[str] = None
phone: Optional[str] = None
role: str = "user"
@validator('email', pre=True)
def validate_email(cls, v):
if v == '' or v is None:
return None
# 간단한 이메일 형식 검증
if '@' not in v or '.' not in v.split('@')[-1]:
raise ValueError('올바른 이메일 형식을 입력해주세요')
return v
class RefreshTokenRequest(BaseModel):
@@ -112,27 +122,47 @@ async def login(
@router.post("/register", response_model=RegisterResponse)
async def register(
register_data: RegisterRequest,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
사용자 등록
사용자 등록 (시스템 관리자만 가능)
Args:
register_data: 등록 정보
credentials: JWT 토큰 (시스템 관리자 권한 필요)
db: 데이터베이스 세션
Returns:
RegisterResponse: 등록 결과
"""
try:
# 토큰 검증 및 권한 확인
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
# 시스템 관리자 권한 확인
if payload['role'] != 'system':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="계정 생성은 시스템 관리자만 가능합니다"
)
auth_service = get_auth_service(db)
result = await auth_service.register(register_data.dict())
logger.info(f"User registered by system admin: {register_data.username} (created by: {payload['username']})")
return RegisterResponse(**result)
except HTTPException:
raise
except Exception as e:
logger.error(f"Register endpoint error: {str(e)}")
raise
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="사용자 등록 중 오류가 발생했습니다"
)
@router.post("/refresh", response_model=RefreshTokenResponse)
@@ -307,7 +337,7 @@ async def get_all_users(
if payload['role'] not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자 권한이 필요합니다"
detail="관리자 이상의 권한이 필요합니다"
)
# 사용자 목록 조회
@@ -350,10 +380,10 @@ async def delete_user(
try:
# 토큰 검증 및 권한 확인
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] not in ['admin', 'system']:
if payload['role'] != 'system':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="관리자 권한이 필요합니다"
detail="사용자 삭제는 시스템 관리자만 가능합니다"
)
# 자기 자신 삭제 방지
@@ -393,7 +423,453 @@ async def delete_user(
)
# 로그 관리 API (관리자 이상)
@router.get("/logs/login")
async def get_login_logs(
skip: int = 0,
limit: int = 100,
user_id: Optional[int] = None,
status: Optional[str] = None,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
로그인 로그 조회 (관리자 이상)
Args:
skip: 건너뛸 레코드 수
limit: 조회할 레코드 수
user_id: 특정 사용자 ID 필터
status: 로그인 상태 필터 (success/failed)
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 로그인 로그 목록
"""
try:
# 토큰 검증 및 권한 확인
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="로그 조회는 관리자 이상의 권한이 필요합니다"
)
# 로그인 로그 조회
from .models import LoginLog, User
query = db.query(LoginLog).join(User)
if user_id:
query = query.filter(LoginLog.user_id == user_id)
if status:
query = query.filter(LoginLog.login_status == status)
logs = query.order_by(LoginLog.login_time.desc()).offset(skip).limit(limit).all()
return {
'success': True,
'logs': [
{
'log_id': log.log_id,
'user_id': log.user_id,
'username': log.user.username,
'name': log.user.name,
'login_time': log.login_time,
'ip_address': log.ip_address,
'user_agent': log.user_agent,
'login_status': log.login_status,
'failure_reason': log.failure_reason
}
for log in logs
],
'total_count': len(logs)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Get login logs error: {str(e)}")
raise HTTPException(
status_code=500,
detail="로그인 로그 조회 중 오류가 발생했습니다"
)
@router.get("/logs/system")
async def get_system_logs(
skip: int = 0,
limit: int = 100,
level: Optional[str] = None,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
시스템 로그 조회 (관리자 이상)
Args:
skip: 건너뛸 레코드 수
limit: 조회할 레코드 수
level: 로그 레벨 필터 (ERROR, WARNING, INFO, DEBUG)
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 시스템 로그 목록
"""
try:
# 토큰 검증 및 권한 확인
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] not in ['admin', 'system']:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="시스템 로그 조회는 관리자 이상의 권한이 필요합니다"
)
# 로그 파일에서 최근 로그 읽기 (임시 구현)
import os
from ..config import get_settings
settings = get_settings()
log_file_path = settings.logging.file_path
logs = []
if os.path.exists(log_file_path):
try:
with open(log_file_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
# 최근 로그부터 처리
recent_lines = lines[-limit-skip:] if len(lines) > skip else lines
for line in reversed(recent_lines):
if line.strip():
# 간단한 로그 파싱 (실제로는 더 정교한 파싱 필요)
parts = line.strip().split(' - ')
if len(parts) >= 4:
timestamp = parts[0]
module = parts[1]
log_level = parts[2]
message = ' - '.join(parts[3:])
if not level or log_level == level:
logs.append({
'timestamp': timestamp,
'module': module,
'level': log_level,
'message': message
})
if len(logs) >= limit:
break
except Exception as e:
logger.error(f"Failed to read log file: {str(e)}")
return {
'success': True,
'logs': logs,
'total_count': len(logs)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Get system logs error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="시스템 로그 조회 중 오류가 발생했습니다"
)
# 사용자 역할 변경 API (시스템 관리자만)
@router.put("/users/{user_id}/role")
async def change_user_role(
user_id: int,
new_role: str,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
사용자 역할 변경 (시스템 관리자만)
Args:
user_id: 변경할 사용자 ID
new_role: 새로운 역할 (system, admin, user)
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 변경 결과
"""
try:
# 토큰 검증 및 권한 확인
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
if payload['role'] != 'system':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="사용자 역할 변경은 시스템 관리자만 가능합니다"
)
# 유효한 역할인지 확인
if new_role not in ['system', 'admin', 'user']:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="유효하지 않은 역할입니다. (system, admin, user 중 선택)"
)
# 자기 자신의 역할 변경 방지
if payload['user_id'] == user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="자기 자신의 역할은 변경할 수 없습니다"
)
# 사용자 조회 및 역할 변경
from .models import UserRepository
user_repo = UserRepository(db)
user = user_repo.find_by_id(user_id)
if not user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="해당 사용자를 찾을 수 없습니다"
)
old_role = user.role
user.role = new_role
user_repo.update_user(user)
logger.info(f"User role changed: {user.username} ({old_role}{new_role}) by {payload['username']}")
return {
'success': True,
'message': f'사용자 역할이 변경되었습니다: {old_role}{new_role}',
'user_id': user_id,
'old_role': old_role,
'new_role': new_role
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Change user role error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="사용자 역할 변경 중 오류가 발생했습니다"
)
# 프론트엔드 오류 로그 수집 API
@router.post("/logs/frontend-error")
async def log_frontend_error(
error_data: dict,
request: Request,
db: Session = Depends(get_db)
):
"""
프론트엔드 오류 로그 수집
Args:
error_data: 프론트엔드에서 전송한 오류 데이터
request: FastAPI Request 객체
db: 데이터베이스 세션
Returns:
Dict: 로그 저장 결과
"""
try:
from datetime import datetime
# 클라이언트 정보 추가
client_ip = request.client.host
user_agent = request.headers.get("user-agent", "")
# 오류 데이터 보강
enhanced_error_data = {
**error_data,
'client_ip': client_ip,
'server_timestamp': datetime.utcnow().isoformat(),
'user_agent': user_agent
}
# 로그로 기록
logger.error(f"Frontend Error: {error_data.get('type', 'unknown')} - {error_data.get('message', 'No message')}",
extra={'frontend_error': enhanced_error_data})
# 데이터베이스에 저장 (선택적)
# TODO: 필요시 frontend_errors 테이블 생성하여 저장
return {
'success': True,
'message': '오류가 기록되었습니다',
'timestamp': enhanced_error_data['server_timestamp']
}
except Exception as e:
logger.error(f"Failed to log frontend error: {str(e)}")
return {
'success': False,
'message': '오류 기록에 실패했습니다'
}
# 프로필 업데이트 API
class ProfileUpdateRequest(BaseModel):
name: str
email: Optional[EmailStr] = None
department: Optional[str] = None
position: Optional[str] = None
@router.put("/profile")
async def update_profile(
profile_data: ProfileUpdateRequest,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
사용자 프로필 업데이트
Args:
profile_data: 업데이트할 프로필 정보
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 업데이트 결과
"""
try:
# 토큰 검증
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
user_id = payload['user_id']
# 사용자 조회
from .models import UserRepository
user_repo = UserRepository(db)
user = user_repo.find_by_id(user_id)
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="사용자를 찾을 수 없습니다"
)
# 이메일 중복 확인 (다른 사용자가 사용 중인지)
if profile_data.email and profile_data.email != user.email:
existing_email = user_repo.find_by_email(profile_data.email)
if existing_email and existing_email.user_id != user_id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="이미 사용 중인 이메일입니다"
)
# 프로필 업데이트
user.name = profile_data.name
user.email = profile_data.email
user.department = profile_data.department
user.position = profile_data.position
user_repo.update_user(user)
logger.info(f"Profile updated for user: {user.username}")
return {
'success': True,
'message': '프로필이 업데이트되었습니다',
'user': user.to_dict()
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Profile update error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="프로필 업데이트 중 오류가 발생했습니다"
)
# 비밀번호 변경 API
class ChangePasswordRequest(BaseModel):
current_password: str
new_password: str
@router.put("/change-password")
async def change_password(
password_data: ChangePasswordRequest,
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db)
):
"""
사용자 비밀번호 변경
Args:
password_data: 현재 비밀번호와 새 비밀번호
credentials: JWT 토큰
db: 데이터베이스 세션
Returns:
Dict: 변경 결과
"""
try:
# 토큰 검증
from .jwt_service import jwt_service
payload = jwt_service.verify_access_token(credentials.credentials)
user_id = payload['user_id']
# 사용자 조회
from .models import UserRepository
user_repo = UserRepository(db)
user = user_repo.find_by_id(user_id)
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="사용자를 찾을 수 없습니다"
)
# 현재 비밀번호 확인
if not user.check_password(password_data.current_password):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="현재 비밀번호가 올바르지 않습니다"
)
# 새 비밀번호 유효성 검사
if len(password_data.new_password) < 8:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="새 비밀번호는 8자 이상이어야 합니다"
)
# 비밀번호 변경
user.set_password(password_data.new_password)
user_repo.update_user(user)
logger.info(f"Password changed for user: {user.username}")
return {
'success': True,
'message': '비밀번호가 변경되었습니다'
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Password change error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="비밀번호 변경 중 오류가 발생했습니다"
)

View File

@@ -27,9 +27,9 @@ class User(Base):
name = Column(String(100), nullable=False)
email = Column(String(100), index=True)
# 권한 관리
role = Column(String(20), default='user', nullable=False)
access_level = Column(String(20), default='worker', nullable=False)
# 권한 관리 - 3단계 시스템: system(제작자) > admin(관리자) > user(사용자)
role = Column(String(20), default='user', nullable=False) # system, admin, user
access_level = Column(String(20), default='worker', nullable=False) # 호환성 유지
# 계정 상태 관리
is_active = Column(Boolean, default=True, nullable=False)
@@ -118,6 +118,40 @@ class User(Base):
def update_last_login(self):
"""마지막 로그인 시간 업데이트"""
self.last_login_at = datetime.utcnow()
# 권한 체크 메서드들
def is_system(self) -> bool:
"""시스템 관리자 권한 확인"""
return self.role == 'system'
def is_admin(self) -> bool:
"""관리자 권한 확인 (시스템 관리자 포함)"""
return self.role in ['system', 'admin']
def is_user(self) -> bool:
"""일반 사용자 권한 확인"""
return self.role == 'user'
def can_create_users(self) -> bool:
"""사용자 생성 권한 확인 (시스템 관리자만)"""
return self.is_system()
def can_view_logs(self) -> bool:
"""로그 조회 권한 확인 (관리자 이상)"""
return self.is_admin()
def can_manage_system(self) -> bool:
"""시스템 관리 권한 확인 (시스템 관리자만)"""
return self.is_system()
def get_role_display_name(self) -> str:
"""역할 표시명 반환"""
role_names = {
'system': '시스템 관리자',
'admin': '관리자',
'user': '사용자'
}
return role_names.get(self.role, '알 수 없음')
class LoginLog(Base):

View File

@@ -0,0 +1,198 @@
"""
초기 시스템 설정 컨트롤러
배포 후 첫 실행 시 시스템 관리자 계정 생성
"""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from typing import Optional
from ..database import get_db
from .models import User, UserRepository
from ..utils.logger import get_logger
logger = get_logger(__name__)
router = APIRouter()
class SystemSetupRequest(BaseModel):
username: str
password: str
name: str
email: Optional[EmailStr] = None
department: Optional[str] = None
position: Optional[str] = None
class SystemSetupResponse(BaseModel):
success: bool
message: str
user_id: Optional[int] = None
setup_completed: bool
@router.get("/setup/status")
async def get_setup_status(db: Session = Depends(get_db)):
"""
시스템 초기 설정 상태 확인
Returns:
Dict: 설정 완료 여부
"""
try:
user_repo = UserRepository(db)
# 시스템 관리자가 존재하는지 확인
system_admin = db.query(User).filter(User.role == 'system').first()
return {
'success': True,
'setup_completed': system_admin is not None,
'has_system_admin': system_admin is not None,
'total_users': db.query(User).count()
}
except Exception as e:
logger.error(f"Setup status check error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="설정 상태 확인 중 오류가 발생했습니다"
)
@router.post("/setup/initialize", response_model=SystemSetupResponse)
async def initialize_system(
setup_data: SystemSetupRequest,
db: Session = Depends(get_db)
):
"""
시스템 초기화 및 첫 번째 시스템 관리자 생성
Args:
setup_data: 시스템 관리자 계정 정보
db: 데이터베이스 세션
Returns:
SystemSetupResponse: 설정 결과
"""
try:
user_repo = UserRepository(db)
# 이미 시스템 관리자가 존재하는지 확인
existing_admin = db.query(User).filter(User.role == 'system').first()
if existing_admin:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="시스템이 이미 초기화되었습니다"
)
# 사용자명 중복 확인
existing_user = user_repo.find_by_username(setup_data.username)
if existing_user:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="이미 존재하는 사용자명입니다"
)
# 이메일 중복 확인 (이메일이 제공된 경우)
if setup_data.email:
existing_email = user_repo.find_by_email(setup_data.email)
if existing_email:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="이미 존재하는 이메일입니다"
)
# 시스템 관리자 계정 생성
user = User(
username=setup_data.username,
name=setup_data.name,
email=setup_data.email,
role='system',
access_level='system',
department=setup_data.department,
position=setup_data.position,
is_active=True
)
# 비밀번호 설정
user.set_password(setup_data.password)
# 데이터베이스에 저장
db.add(user)
db.commit()
db.refresh(user)
logger.info(f"System initialized with admin user: {user.username}")
return SystemSetupResponse(
success=True,
message="시스템이 성공적으로 초기화되었습니다",
user_id=user.user_id,
setup_completed=True
)
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"System initialization error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="시스템 초기화 중 오류가 발생했습니다"
)
@router.post("/setup/reset")
async def reset_system_setup(
confirm_reset: bool = False,
db: Session = Depends(get_db)
):
"""
시스템 설정 리셋 (개발/테스트 용도)
Args:
confirm_reset: 리셋 확인
db: 데이터베이스 세션
Returns:
Dict: 리셋 결과
"""
try:
if not confirm_reset:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="리셋을 확인해주세요 (confirm_reset=true)"
)
# 개발 환경에서만 허용
from ..config import get_settings
settings = get_settings()
if settings.environment != 'development':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="개발 환경에서만 시스템 리셋이 가능합니다"
)
# 모든 사용자 삭제
db.query(User).delete()
db.commit()
logger.warning("System setup has been reset (development only)")
return {
'success': True,
'message': '시스템 설정이 리셋되었습니다',
'setup_completed': False
}
except HTTPException:
raise
except Exception as e:
db.rollback()
logger.error(f"System reset error: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="시스템 리셋 중 오류가 발생했습니다"
)

View File

@@ -108,9 +108,11 @@ logger.info("파일 관리 API 라우터 비활성화됨 (files 라우터 사용
# 인증 API 라우터 등록
try:
from .auth import auth_router
from .auth import auth_router, setup_router
app.include_router(auth_router, prefix="/auth", tags=["authentication"])
app.include_router(setup_router, prefix="/setup", tags=["system-setup"])
logger.info("인증 API 라우터 등록 완료")
logger.info("시스템 설정 API 라우터 등록 완료")
except ImportError as e:
logger.warning(f"인증 라우터를 찾을 수 없습니다: {e}")

View File

@@ -0,0 +1,197 @@
#!/usr/bin/env python3
"""
시스템 관리자 계정 생성 스크립트
최초 설치 시 시스템 관리자 계정을 생성합니다.
"""
import sys
import os
import getpass
from datetime import datetime
# 프로젝트 루트를 Python 경로에 추가
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from app.config import get_settings
from app.auth.models import User, UserRepository
from app.database import Base
def create_system_admin():
"""시스템 관리자 계정 생성"""
print("=" * 60)
print("🔧 TK-MP 시스템 관리자 계정 생성")
print("=" * 60)
# 설정 로드
settings = get_settings()
# 데이터베이스 연결
try:
engine = create_engine(settings.database_url)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()
print("✅ 데이터베이스 연결 성공")
# 테이블 생성 (필요한 경우)
Base.metadata.create_all(bind=engine)
print("✅ 데이터베이스 테이블 확인/생성 완료")
except Exception as e:
print(f"❌ 데이터베이스 연결 실패: {str(e)}")
return False
# 기존 시스템 관리자 확인
try:
user_repo = UserRepository(db)
existing_admin = db.query(User).filter(User.role == 'system').first()
if existing_admin:
print(f"⚠️ 시스템 관리자가 이미 존재합니다: {existing_admin.username}")
response = input("새로운 시스템 관리자를 추가로 생성하시겠습니까? (y/N): ").lower()
if response != 'y':
print("❌ 작업이 취소되었습니다.")
return False
except Exception as e:
print(f"❌ 기존 관리자 확인 실패: {str(e)}")
return False
# 사용자 입력 받기
print("\n📝 시스템 관리자 정보를 입력해주세요:")
print("-" * 40)
try:
# 사용자명 입력
while True:
username = input("사용자명 (영문/숫자, 3-20자): ").strip()
if not username:
print("❌ 사용자명을 입력해주세요.")
continue
if len(username) < 3 or len(username) > 20:
print("❌ 사용자명은 3-20자여야 합니다.")
continue
if not username.replace('_', '').isalnum():
print("❌ 사용자명은 영문, 숫자, 언더스코어만 사용 가능합니다.")
continue
# 중복 확인
existing_user = user_repo.find_by_username(username)
if existing_user:
print("❌ 이미 존재하는 사용자명입니다.")
continue
break
# 이름 입력
while True:
name = input("이름 (한글/영문, 2-50자): ").strip()
if not name:
print("❌ 이름을 입력해주세요.")
continue
if len(name) < 2 or len(name) > 50:
print("❌ 이름은 2-50자여야 합니다.")
continue
break
# 이메일 입력 (선택사항)
email = input("이메일 (선택사항): ").strip()
if email and '@' not in email:
print("⚠️ 올바르지 않은 이메일 형식입니다. 빈 값으로 설정합니다.")
email = None
# 비밀번호 입력
while True:
password = getpass.getpass("비밀번호 (8자 이상): ")
if len(password) < 8:
print("❌ 비밀번호는 8자 이상이어야 합니다.")
continue
password_confirm = getpass.getpass("비밀번호 확인: ")
if password != password_confirm:
print("❌ 비밀번호가 일치하지 않습니다.")
continue
break
# 부서/직책 입력 (선택사항)
department = input("부서 (선택사항): ").strip() or None
position = input("직책 (선택사항): ").strip() or None
except KeyboardInterrupt:
print("\n❌ 작업이 취소되었습니다.")
return False
# 입력 정보 확인
print("\n📋 입력된 정보:")
print("-" * 40)
print(f"사용자명: {username}")
print(f"이름: {name}")
print(f"이메일: {email or '(없음)'}")
print(f"부서: {department or '(없음)'}")
print(f"직책: {position or '(없음)'}")
print(f"역할: 시스템 관리자")
response = input("\n위 정보로 시스템 관리자를 생성하시겠습니까? (y/N): ").lower()
if response != 'y':
print("❌ 작업이 취소되었습니다.")
return False
# 사용자 생성
try:
# 사용자 객체 생성
user = User(
username=username,
name=name,
email=email,
role='system',
access_level='system',
department=department,
position=position,
is_active=True
)
# 비밀번호 설정
user.set_password(password)
# 데이터베이스에 저장
db.add(user)
db.commit()
db.refresh(user)
print(f"✅ 시스템 관리자 계정이 생성되었습니다!")
print(f" - 사용자 ID: {user.user_id}")
print(f" - 사용자명: {user.username}")
print(f" - 이름: {user.name}")
print(f" - 생성일시: {user.created_at}")
return True
except Exception as e:
db.rollback()
print(f"❌ 사용자 생성 실패: {str(e)}")
return False
finally:
db.close()
def main():
"""메인 함수"""
try:
success = create_system_admin()
if success:
print("\n🎉 시스템 관리자 계정 생성이 완료되었습니다!")
print("이제 이 계정으로 로그인하여 다른 사용자를 관리할 수 있습니다.")
else:
print("\n❌ 시스템 관리자 계정 생성에 실패했습니다.")
sys.exit(1)
except Exception as e:
print(f"\n💥 예상치 못한 오류가 발생했습니다: {str(e)}")
sys.exit(1)
if __name__ == "__main__":
main()