diff --git a/account.py b/account.py index f210cdb..313d4ed 100644 --- a/account.py +++ b/account.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/10/16 - 업데이트 : 2025/02/24 + 업데이트 : 2025/03/08 설명 : 계정 생성, 로그인, 세션 관리를 위한 API 엔드포인트 정의 """ @@ -58,8 +58,9 @@ class PwReset_Payload(BaseModel): class FineName_Payload(BaseModel): univ_id: int -class FindProf_Payload(BaseModel): - department: int +class LoadProfPayload(BaseModel): + subj_no: int + def generate_token(): """랜덤한 15자리 토큰 생성""" @@ -73,12 +74,15 @@ async def check_session(payload: Checksession_Payload): try: is_valid = account_DB.validate_user_token(payload.user_id, payload.token) if isinstance(is_valid, Exception): + logger.error(f"Invalid session for user {payload.user_id}: {str(is_valid)}", exc_info=True) raise HTTPException(status_code=401, detail=f"Invalid session: {str(is_valid)}") if is_valid: + logger.info(f"Session valid for user {payload.user_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Session valid"} + logger.warning(f"Invalid session token for user {payload.user_id}") raise HTTPException(status_code=401, detail="Invalid session token") except Exception as e: - logger.debug(f"Error validating session: {str(e)}") + logger.error(f"Unexpected error validating session for user {payload.user_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unexpected error validating session: {str(e)}") @@ -89,31 +93,35 @@ async def api_acc_signup_post(payload: SignUp_Payload): token = generate_token() result = account_DB.insert_user(payload, token) if result is True: + logger.info(f"User {payload.univ_id} signed up successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Signup successful", "PAYLOADS": {"Token": token}} - elif isinstance(result, tuple) and result[0] == 1062: + if isinstance(result, tuple) and result[0] == 1062: + logger.warning(f"Duplicate signup attempt: {payload.univ_id} is already registered") return {"RESULT_CODE": 409, "RESULT_MSG": "Duplicate entry: This univ_id is already registered"} - else: - # print(result) - raise HTTPException(status_code=500, detail=f"Error during signup: {str(result)}") + logger.error(f"Unexpected signup error: {str(result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during signup: {str(result)}") except Exception as e: - if "1062" in str(e): - return {"RESULT_CODE": 409, "RESULT_MSG": "Duplicate entry: This univ_id is already registered"} - else: - raise HTTPException(status_code=500, detail=f"Unhandled exception during signup: {str(e)}") + logger.error(f"Unhandled exception during signup: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unhandled exception during signup: {str(e)}") + @router.post("/acc/signin") async def api_acc_signin_post(payload: Signin_Payload): """사용자 로그인""" try: s_no = account_DB.validate_user(payload.id, payload.pw) - if s_no is None: # 로그인 실패 처리 + if s_no is None: + logger.warning(f"Invalid login attempt: {payload.id}") raise HTTPException(status_code=401, detail="Invalid credentials") - if isinstance(s_no, Exception): # 예외 발생 처리 + if isinstance(s_no, Exception): + logger.error(f"Internal error during validation: {str(s_no)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Internal error during validation: {str(s_no)}") token = generate_token() save_result = account_DB.save_signin_user_token(payload.id, token) - if isinstance(save_result, Exception): # 토큰 저장 중 오류 처리 + if isinstance(save_result, Exception): + logger.error(f"Error saving session token for user {payload.id}: {str(save_result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error saving session token: {str(save_result)}") + logger.info(f"User {payload.id} signed in successfully") return { "RESULT_CODE": 200, "RESULT_MSG": "Login successful", @@ -125,6 +133,7 @@ async def api_acc_signin_post(payload: Signin_Payload): except HTTPException as http_err: raise http_err except Exception as e: + logger.error(f"Unhandled exception during login for user {payload.id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unhandled exception during login: {str(e)}") @router.post("/acc/signout") @@ -133,12 +142,15 @@ async def api_acc_signout_post(payload: SignOut_Payload): try: result = account_DB.signout_user(payload.token) if isinstance(result, Exception): + logger.error(f"Error during logout: {str(result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during logout: {str(result)}") if result is True: + logger.info("User logged out successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Logout successful"} - else: - raise HTTPException(status_code=500, detail="Logout failed") + logger.warning("Logout failed") + raise HTTPException(status_code=500, detail="Logout failed") except Exception as e: + logger.error(f"Unhandled exception during logout: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unhandled exception during logout: {str(e)}") @router.post("/acc/delacc") @@ -147,12 +159,15 @@ async def api_acc_delacc_post(payload: DelAcc_Payload): try: result = account_DB.delete_user(payload.id) if isinstance(result, Exception): + logger.error(f"Error during account deletion: {str(result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during account deletion: {str(result)}") if result is True: + logger.info(f"Account {payload.id} deleted successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Account deleted successfully"} - else: - raise HTTPException(status_code=500, detail="Account deletion failed") + logger.warning(f"Account deletion failed for {payload.id}") + raise HTTPException(status_code=500, detail="Account deletion failed") except Exception as e: + logger.error(f"Unhandled exception during account deletion: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unhandled exception during account deletion: {str(e)}") @router.post("/acc/checkacc") @@ -164,33 +179,32 @@ async def api_acc_check(payload: AccCheck_Payload): """ try: result = account_DB.find_user_pw( - univ_id = payload.univ_id, - name = payload.name, - email = payload.email, - id = payload.user_id + univ_id=payload.univ_id, + name=payload.name, + email=payload.email, + id=payload.user_id ) if result is True: + logger.info(f"Account validation successful for user {payload.user_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "OK"} - else: - # raise HTTPException(status_code=500, detail="Account validation failed") - return {"RESULT_CODE": 400, "RESULT_MSG": "Account validation failed"} + logger.warning(f"Account validation failed for user {payload.user_id}") + return {"RESULT_CODE": 400, "RESULT_MSG": "Account validation failed"} except Exception as e: + logger.error(f"Unhandled exception during account validation for user {payload.user_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unhandled exception during account validation: {str(e)}") @router.post("/acc/resetpw") async def api_acc_pwreset(payload: PwReset_Payload): """비밀번호 리셋(변경) 함수""" try: - result = account_DB.edit_user_pw( - payload.univ_id, - payload.pw - ) + result = account_DB.edit_user_pw(payload.univ_id, payload.pw) if result is True: + logger.info(f"Password reset successful for user {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "OK"} - else: - # raise HTTPException(status_code=500, detail="Password update failed") - return {"RESULT_CODE": 400, "RESULT_MSG": "Password update failed"} + logger.warning(f"Password update failed for user {payload.univ_id}") + return {"RESULT_CODE": 400, "RESULT_MSG": "Password update failed"} except Exception as e: + logger.error(f"Unhandled exception while updating password for user {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unhandled exception while updating password: {str(e)}") @router.post("/acc/find_sname") @@ -198,14 +212,39 @@ async def api_acc_find_student_name(payload: FineName_Payload): """학번으로 학생 이름을 찾는 기능""" try: result = account_DB.fetch_student_name(payload.univ_id) - if isinstance(result, Exception) or result == None: + if isinstance(result, Exception) or result is None: + logger.error(f"Error in find student name operation for univ_id {payload.univ_id}: {str(result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error in find student name Operation: {str(result)}") + logger.info(f"Student name found for univ_id {payload.univ_id}: {result}") return {"RESULT_CODE": 200, "RESULT_MSG": "Find Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in find student name Operation: {str(e)}") + logger.error(f"Unexpected error in find student name operation for univ_id {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unexpected error in find student name Operation: {str(e)}") -# @router.post("/acc/find_prof") # 미사용 주석처리 (25.02.15) -# async def api_acc_find_professor(payload: FindProf_Payload): -# """자신의 학과에 속한 교수 리스트를 불러오는 기능""" -# return \ No newline at end of file +@router.post("/acc/load_dept") +async def api_acc_load_department(): + """모든 학과를 조회하는 기능""" + try: + result = account_DB.fetch_dept_list() + if isinstance(result, Exception): + logger.error(f"Error in load dept operation: {str(result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error in load dept Operation: {str(result)}") + logger.info("Department list retrieved successfully") + return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} + except Exception as e: + logger.error(f"Unexpected error in load dept operation: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load dept Operation: {str(e)}") + +@router.post("/acc/load_prof") +async def api_acc_load_professor_by_subject(payload: LoadProfPayload): + """특정 교과목이 속한 학과의 교수 리스트를 불러오는 기능""" + try: + result = account_DB.fetch_professor_list_by_subject(payload.subj_no) + if isinstance(result, Exception): + logger.error(f"Error in load professor operation for subject {payload.subj_no}: {str(result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error in load professor operation: {str(result)}") + logger.info(f"Professor list retrieved successfully for subject {payload.subj_no}") + return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} + except Exception as e: + logger.error(f"Unexpected error in load professor operation for subject {payload.subj_no}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load professor operation: {str(e)}") diff --git a/ccp.py b/ccp.py index eb2ede1..9cbb423 100644 --- a/ccp.py +++ b/ccp.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2025/01/10 - 업데이트 : 2025/02/05 + 업데이트 : 2025/03/08 설명 : 프로젝트 Import/Export API 엔드포인트 정의 """ @@ -32,8 +32,9 @@ class ccp_payload(BaseModel): ver: int = None def handle_db_result(result): + """데이터베이스 결과 처리 함수""" if isinstance(result, Exception): - logging.error(f"Database error: {result}") + logging.error(f"Database error: {result}", exc_info=True) return False return result @@ -41,6 +42,7 @@ def create_project_info(): return async def pull_storage_server(pid: int, output_path: str): + """Storage 서버에서 특정 프로젝트의 데이터를 다운로드 및 추출하는 함수""" b_server_url = f"http://192.168.50.84:10080/api/ccp/push" async with httpx.AsyncClient() as client: try: @@ -51,15 +53,17 @@ async def pull_storage_server(pid: int, output_path: str): archive_path = os.path.join(output_path, f"{pid}_output.tar.gz") with open(archive_path, 'wb') as f: f.write(response.content) + logging.info(f"Downloaded archive for project {pid}: {archive_path}") with tarfile.open(archive_path, 'r:gz') as tar: tar.extractall(path=output_path) os.remove(archive_path) + logging.info(f"Extraction completed and archive removed for project {pid}") return {"RESULT_CODE": 200, "RESULT_MSG": f"Files for project {pid} downloaded successfully."} else: - logging.error(f"Failed to download from storage server. Status code: {response.status_code}") + logging.error(f"Failed to download from storage server for project {pid}. Status code: {response.status_code}") return {"RESULT_CODE": 500, "RESULT_MSG": f"Failed to download from storage server. Status code: {response.status_code}"} except Exception as e: - logging.error(f"Error while pulling from storage server for project {pid}: {str(e)}") + logging.error(f"Error while pulling from storage server for project {pid}: {str(e)}", exc_info=True) return {"RESULT_CODE": 500, "RESULT_MSG": f"Error while pulling from storage server: {str(e)}"} load_dotenv() @@ -68,13 +72,13 @@ async def pull_storage_server(pid: int, output_path: str): cipher = Fernet(key) def encrypt_ccp_file(pid): + """CCP 파일을 tar 압축 후 암호화하는 함수""" try: + logging.info(f"------ Start encryption process for PID {pid} ------") input_dir = f'/data/ccp/{pid}/' output_dir = f'/data/ccp/' - # 파일을 tar로 압축할 메모리 버퍼 생성 compressed_file = io.BytesIO() - # tar 파일로 압축 with tarfile.open(fileobj=compressed_file, mode='w|') as tar: for root, dirs, files in os.walk(input_dir): @@ -82,18 +86,16 @@ def encrypt_ccp_file(pid): file_path = os.path.join(root, file) arcname = os.path.relpath(file_path, input_dir) tar.add(file_path, arcname=arcname) - + logging.info(f"Files in {input_dir} compressed successfully.") # 압축된 데이터 가져오기 compressed_file.seek(0) compressed_data = compressed_file.read() - # 파일 데이터 암호화 encrypted_data = cipher.encrypt(compressed_data) - + logging.info(f"Data encryption completed for PID {pid}.") # 헤더 작성 num_files = len(os.listdir(input_dir)) # 디렉터리 내 파일 개수 header = struct.pack('!I', num_files) # 파일 개수 (4바이트) - # 각 파일의 메타데이터 기록 for file in os.listdir(input_dir): file_path = os.path.join(input_dir, file) @@ -102,41 +104,40 @@ def encrypt_ccp_file(pid): header += struct.pack('!I', file_name_length) # 파일 이름 길이 (4바이트) header += file.encode('utf-8') # 파일 이름 (UTF-8) header += struct.pack('!I', file_size) # 파일 크기 (4바이트) - + logging.info(f"Header creation completed for PID {pid}. Number of files: {num_files}") # 암호화된 ccp 파일 저장 encrypted_file_path = os.path.join(output_dir, f'{pid}.ccp') with open(encrypted_file_path, 'wb') as encrypted_file: encrypted_file.write(header) # 헤더 기록 encrypted_file.write(encrypted_data) # 암호화된 데이터 기록 - + logging.info(f"Encrypted CCP file saved successfully: {encrypted_file_path}") + logging.info(f"------ End of encryption process for PID {pid} ------") return True - except Exception as e: - logging.error(f"Error occurred during encryption process for pid {pid}: {e}") + logging.error(f"Error occurred during encryption process for PID {pid}: {str(e)}", exc_info=True) return False def decrypt_ccp_file(pid): + """CCP 파일을 복호화하여 원본 데이터를 복원하는 함수""" try: + logging.info(f"------ Start decryption process for PID {pid} ------") input_file_path = f'/data/ccp/{pid}.ccp' output_dir = f'/data/ccp/{pid}/' - if not os.path.exists(input_file_path): - raise Exception(f"ccp file {input_file_path} does not exist") - + raise Exception(f"CCP file {input_file_path} does not exist") + logging.info(f"CCP file found: {input_file_path}") # 파일 열기 with open(input_file_path, 'rb') as encrypted_file: # 헤더 읽기 (파일 개수 + 각 파일의 메타데이터) header = encrypted_file.read(4) if len(header) < 4: raise Exception(f"Failed to read header, insufficient data. Read {len(header)} bytes") - num_files = struct.unpack('!I', header)[0] # 파일 개수 - + logging.info(f"Number of files in CCP: {num_files}") # 디렉터리 생성 os.makedirs(output_dir, exist_ok=True) - os.makedirs(os.path.join(output_dir, 'DATABASE'), exist_ok=True) # DATABASE 폴더 생성 - os.makedirs(os.path.join(output_dir, 'OUTPUT'), exist_ok=True) # OUTPUT 폴더 생성 - + os.makedirs(os.path.join(output_dir, 'DATABASE'), exist_ok=True) + os.makedirs(os.path.join(output_dir, 'OUTPUT'), exist_ok=True) # 각 파일의 메타데이터 읽기 및 복원 files_metadata = [] for _ in range(num_files): @@ -145,58 +146,51 @@ def decrypt_ccp_file(pid): if len(file_name_length_data) < 4: raise Exception(f"Failed to read file name length, insufficient data. Read {len(file_name_length_data)} bytes") file_name_length = struct.unpack('!I', file_name_length_data)[0] - # 파일 이름 읽기 file_name = encrypted_file.read(file_name_length).decode('utf-8') - # 파일 크기 읽기 file_size_data = encrypted_file.read(4) if len(file_size_data) < 4: raise Exception(f"Failed to read file size, insufficient data. Read {len(file_size_data)} bytes") file_size = struct.unpack('!I', file_size_data)[0] - files_metadata.append((file_name, file_size)) - + logging.info(f"Metadata extraction completed for {num_files} files.") # 남은 암호화된 데이터 읽기 encrypted_data = encrypted_file.read() - - # 복호화 - decrypted_data = cipher.decrypt(encrypted_data) - - # 복호화된 데이터 저장 - decrypted_tar_path = os.path.join(output_dir, 'ccp_decrypted.tar') - with open(decrypted_tar_path, 'wb') as decrypted_file: - decrypted_file.write(decrypted_data) - - # 각 파일의 데이터를 복원 - with open(decrypted_tar_path, 'rb') as decrypted_tar: - with tarfile.open(fileobj=decrypted_tar) as tar: - # 타르 파일 내부의 폴더 구조를 제대로 복원하도록 설정 - for member in tar.getmembers(): - member_path = os.path.join(output_dir, member.name) # 최종 경로 - - # 'OUTPUT' 폴더 내부만 경로 복원 - if member.name.startswith('OUTPUT/'): - member_path = os.path.join(output_dir, 'OUTPUT', os.path.relpath(member.name, 'OUTPUT')) - elif member.name.startswith('DATABASE/'): - member_path = os.path.join(output_dir, 'DATABASE', os.path.relpath(member.name, 'DATABASE')) - - # 디렉터리 생성 및 파일 추출 - if member.isdir(): - os.makedirs(member_path, exist_ok=True) - else: - # 파일 추출 전 존재하는지 체크하고, 필요한 디렉터리 생성 - os.makedirs(os.path.dirname(member_path), exist_ok=True) - with open(member_path, 'wb') as f: - f.write(tar.extractfile(member).read()) - + # 복호화 + decrypted_data = cipher.decrypt(encrypted_data) + logging.info(f"Data decryption completed for PID {pid}") + # 복호화된 데이터 저장 + decrypted_tar_path = os.path.join(output_dir, 'ccp_decrypted.tar') + with open(decrypted_tar_path, 'wb') as decrypted_file: + decrypted_file.write(decrypted_data) + logging.info(f"Decrypted tar file saved: {decrypted_tar_path}") + # 각 파일의 데이터를 복원 + with open(decrypted_tar_path, 'rb') as decrypted_tar: + with tarfile.open(fileobj=decrypted_tar) as tar: + for member in tar.getmembers(): + member_path = os.path.join(output_dir, member.name) + # 'OUTPUT' 폴더 내부만 경로 복원 + if member.name.startswith('OUTPUT/'): + member_path = os.path.join(output_dir, 'OUTPUT', os.path.relpath(member.name, 'OUTPUT')) + elif member.name.startswith('DATABASE/'): + member_path = os.path.join(output_dir, 'DATABASE', os.path.relpath(member.name, 'DATABASE')) + # 디렉터리 생성 및 파일 추출 + if member.isdir(): + os.makedirs(member_path, exist_ok=True) + else: + os.makedirs(os.path.dirname(member_path), exist_ok=True) + with open(member_path, 'wb') as f: + f.write(tar.extractfile(member).read()) + logging.info(f"Decryption and extraction completed for PID {pid}") + logging.info(f"------ End of decryption process for PID {pid} ------") return {"RESULT_CODE": 200, "RESULT_MSG": f"Decryption successful for project {pid}"} - except Exception as e: - logging.error(f"Error during decryption process for pid {pid}: {str(e)}") + logging.error(f"Error during decryption process for PID {pid}: {str(e)}", exc_info=True) return {"RESULT_CODE": 500, "RESULT_MSG": f"Decryption failed: {str(e)}"} def build_csv_dict(pid): + """CCP 데이터베이스 폴더에서 CSV 파일을 분석하여 매핑하는 함수""" source_dir = f"/data/ccp/{pid}/DATABASE" target_prefix = "/var/lib/mysql/csv/" prefix_mapping = { @@ -218,284 +212,217 @@ def build_csv_dict(pid): } csv_dict = {} try: + logging.info(f"------ Start building CSV dictionary for PID {pid} ------") + if not os.path.exists(source_dir): + raise FileNotFoundError(f"Directory {source_dir} does not exist.") files = os.listdir(source_dir) - except FileNotFoundError: - raise Exception(f"디렉터리 {source_dir} 가 존재하지 않습니다.") - sorted_prefixes = sorted(prefix_mapping.keys(), key=lambda x: len(x), reverse=True) - for filename in files: - if filename.endswith(".csv"): - for prefix in sorted_prefixes: - if filename.startswith(prefix): - key = prefix_mapping[prefix] - csv_dict[key] = os.path.join(target_prefix, filename) - break - return csv_dict + logging.info(f"Found {len(files)} files in {source_dir}.") + sorted_prefixes = sorted(prefix_mapping.keys(), key=lambda x: len(x), reverse=True) + for filename in files: + if filename.endswith(".csv"): + for prefix in sorted_prefixes: + if filename.startswith(prefix): + key = prefix_mapping[prefix] + csv_dict[key] = os.path.join(target_prefix, filename) + logging.info(f"Mapped file {filename} to key {key}.") + break + logging.info(f"CSV dictionary built successfully for PID {pid}. Total mappings: {len(csv_dict)}") + logging.info(f"------ End of CSV dictionary build for PID {pid} ------") + return csv_dict + except Exception as e: + logging.error(f"Error during CSV dictionary build for PID {pid}: {str(e)}", exc_info=True) + raise @router.post("/ccp/import") async def api_project_import(payload: ccp_payload): """프로젝트 복원 기능""" - - logging.info(f"Step 1: Retrieving version history for project {payload.pid}") + logging.info(f"------ Start project import process for PID {payload.pid} ------") try: + # Step 1: Retrieve version history + logging.info(f"Step 1: Retrieving version history for project {payload.pid}") history = csv_DB.fetch_csv_history(payload.pid) - if not history or len(history) == 0: + if not history: raise Exception(f"No history records found for project {payload.pid}") highest_ver = str(int(max(record['ver'] for record in history)) + 1) - logging.info(f"Highest version for project {payload.pid} is {highest_ver}") - selected_version = None - for record in history: - if record['ver'] == payload.ver: - selected_version = record['ver'] - break + logging.info(f"Highest version: {highest_ver}") + selected_version = next((record['ver'] for record in history if record['ver'] == payload.ver), None) if selected_version is None: raise Exception(f"Version {payload.ver} not found in project history") - logging.info(f"Selected version {payload.ver} found in project history") - except Exception as e: - logging.error(f"Failed to retrieve version history for project {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to retrieve version history: {str(e)}") - - #return {"Highest version": highest_ver, "selected version": selected_version} # 디버깅용 - - logging.info(f"Step 2: Backing up current project state for project {payload.pid}") - try: - logging.info(f"Initializing folder /data/ccp/{payload.pid}") - os.makedirs(f'/data/ccp/{payload.pid}', exist_ok=True) + logging.info(f"Selected version {payload.ver} found in history") + # Step 2: Backup current project + logging.info(f"Step 2: Backing up current project {payload.pid}") os.makedirs(f'/data/ccp/{payload.pid}/DATABASE', exist_ok=True) os.makedirs(f'/data/ccp/{payload.pid}/OUTPUT', exist_ok=True) - except Exception as e: - logging.error(f"Failed to initialize folder for project {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to initialize folder: {str(e)}") - - logging.info(f"Exporting the database to CSV files for project ID: {payload.pid}") - try: result = csv_DB.export_csv(payload.pid) - except Exception as e: - logging.error(f"Failed to export DB during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to export DB during backup: {str(e)}") - if not handle_db_result(result): - raise HTTPException(status_code=500, detail="Failed to export DB during backup") - - logging.info(f"Copying the OUTPUT files from Storage Server to /data/ccp/{payload.pid}/OUTPUT for backup") - try: + if not handle_db_result(result): + raise Exception("Failed to export DB during backup") result = await pull_storage_server(payload.pid, f'/data/ccp/{payload.pid}/OUTPUT') if result['RESULT_CODE'] != 200: - raise HTTPException(status_code=500, detail=result['RESULT_MSG']) - except Exception as e: - logging.error(f"Failed to download OUTPUT files during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to download OUTPUT files during backup: {str(e)}") - - logging.info(f"Encrypting /data/ccp/{payload.pid} folder to create backup CCP file") - try: - encryption_result = encrypt_ccp_file(payload.pid) - if not encryption_result: - raise HTTPException(status_code=500, detail=f"Failed to encrypt project folder for backup, pid {payload.pid}") - except Exception as e: - logging.error(f"Error during encryption for backup, pid {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during encryption for backup: {str(e)}") - - logging.info("Saving backup record to DB history") - try: - backup_message = f"Revert {highest_ver} to {payload.ver}" - payload.msg = backup_message + raise Exception(result['RESULT_MSG']) + if not encrypt_ccp_file(payload.pid): + raise Exception(f"Failed to encrypt project folder for backup") + # Step 3: Save backup history + logging.info("Saving backup record to DB history") + payload.msg = f"Revert {highest_ver} to {payload.ver}" backup_ver = csv_DB.insert_csv_history(payload.pid, payload.univ_id, payload.msg) if backup_ver is None: raise Exception("Failed to insert backup history record") - logging.info(f"Backup history record inserted with version {backup_ver}") - except Exception as e: - logging.error(f"Failed to save backup record: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to save backup record: {str(e)}") - logging.info("Uploading backup CCP file to Storage Server") - try: + logging.info(f"Backup history recorded as version {backup_ver}") + # Step 4: Upload backup to Storage Server history = csv_DB.fetch_csv_history(payload.pid) version = str(max(record['ver'] for record in history)) ccp_file_path = f"/data/ccp/{payload.pid}.ccp" ccp_file_name = f"{payload.pid}_{version}.ccp" storage_url = "http://192.168.50.84:10080/api/ccp/pull" - logging.info(f"Reading backup CCP file: {ccp_file_path}") + logging.info(f"Uploading backup CCP file to Storage Server: {ccp_file_name}") with open(ccp_file_path, "rb") as file: files = {"file": (ccp_file_name, file, "application/octet-stream")} form_data = {"pid": str(payload.pid), "name": ccp_file_name} - logging.info(f"Sending backup CCP file to Storage Server: {storage_url}") response = requests.post(storage_url, files=files, data=form_data) if response.status_code != 200: - logging.error(f"Failed to upload backup CCP file: {response.text}") - raise HTTPException(status_code=500, detail="Failed to upload backup CCP file to Storage Server") - logging.info(f"Backup CCP file uploaded successfully: {ccp_file_name}") - except FileNotFoundError: - logging.error(f"Backup CCP file not found: {ccp_file_path}") - raise HTTPException(status_code=404, detail="Backup CCP file not found") - except requests.exceptions.RequestException as e: - logging.error(f"Request error during backup CCP file upload: {str(e)}") - raise HTTPException(status_code=500, detail=f"Request error during backup CCP file upload: {str(e)}") - except Exception as e: - logging.error(f"Unexpected error during backup CCP file upload: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during backup CCP file upload: {str(e)}") - logging.info(f"Deleting /data/ccp/{payload.pid} folder and backup CCP file") - try: - shutil.rmtree(f'/data/ccp/{payload.pid}') + raise Exception("Failed to upload backup CCP file") + # Step 5: Remove temporary backup files + shutil.rmtree(f'/data/ccp/{payload.pid}', ignore_errors=True) os.remove(f'/data/ccp/{payload.pid}.ccp') - except Exception as e: - logging.error(f"Failed to delete folder or backup CCP file for project {payload.pid}: {str(e)}") - - logging.info(f"Step 2: Backup completed with version {backup_ver}") - # return {"RESULT_CODE": 200, "RESULT_MSG": f"Project {payload.pid} imported successfully with backup version {backup_ver}."} - - logging.info(f"Step 3: Downloading CCP file for selected version {payload.ver} from Storage Server") - try: + logging.info("Backup completed and temporary files removed") + # Step 6: Download selected CCP version + logging.info(f"Step 6: Downloading CCP file for version {payload.ver} from Storage Server") selected_ccp_url = "http://192.168.50.84:10080/api/ccp/push_ccp" - params = {"pid": payload.pid, "ver": payload.ver} async with httpx.AsyncClient() as client: - response = await client.post(selected_ccp_url, params=params) + response = await client.post(selected_ccp_url, params={"pid": payload.pid, "ver": payload.ver}) if response.status_code != 200: raise Exception(f"Storage server returned status {response.status_code}") selected_ccp_file_path = f"/data/ccp/{payload.pid}_{payload.ver}.ccp" with open(selected_ccp_file_path, "wb") as f: f.write(response.content) - logging.info(f"CCP file downloaded successfully: {selected_ccp_file_path}") - except Exception as e: - logging.error(f"Step 3 failed: {str(e)}") - raise HTTPException(status_code=500, detail=f"Step 3 failed: {str(e)}") - - logging.info(f"Step 4: Decrypting and extracting the downloaded CCP file for project {payload.pid}") - try: - target_ccp_file_path = f"/data/ccp/{payload.pid}.ccp" - os.rename(selected_ccp_file_path, target_ccp_file_path) + # Step 7: Decrypt and extract the CCP file + logging.info("Step 7: Decrypting and extracting the downloaded CCP file") + os.rename(selected_ccp_file_path, f"/data/ccp/{payload.pid}.ccp") result = decrypt_ccp_file(payload.pid) if result.get("RESULT_CODE", 500) != 200: raise Exception(result.get("RESULT_MSG", "Unknown error during decryption")) - logging.info("Decryption and extraction completed successfully") - except Exception as e: - logging.error(f"Step 4 failed: {str(e)}") - raise HTTPException(status_code=500, detail=f"Step 4 failed: {str(e)}") - - logging.info(f"Step 5: Pushing DATABASE CSV files to DB server for project {payload.pid}") - db_push_url = "http://192.168.50.84:70/api/ccp/push_db" - database_dir = f"/data/ccp/{payload.pid}/DATABASE" - if not os.path.exists(database_dir): - raise Exception("DATABASE folder not found in extracted files") - try: - files_transferred = [] - for filename in os.listdir(database_dir): - if filename.endswith(".csv"): - file_path = os.path.join(database_dir, filename) - with open(file_path, "rb") as f: - files_payload = {"file": (filename, f, "application/octet-stream")} - data_payload = {"pid": str(payload.pid)} - response = requests.post(db_push_url, files=files_payload, data=data_payload) - if response.status_code != 200: - raise Exception(f"Failed to push file {filename}: {response.text}") - else: - files_transferred.append(filename) - logging.info(f"Successfully pushed files to DB server: {files_transferred}") - except Exception as e: - logging.error(f"Failed to push DATABASE CSV files to DB server for project {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to push DATABASE CSV files to DB server: {str(e)}") - - logging.info(f"Step 5.1: Restoring DATABASE CSV files for project {payload.pid}") - try: - csv_files = build_csv_dict(payload.pid) - logging.info(f"CSV files to import: {csv_files}") - import_result = csv_DB.import_csv(csv_files, payload.pid) - if import_result is not True: - raise Exception("DB import_csv function returned failure") - logging.info("DATABASE CSV files restored successfully") - except Exception as e: - logging.error(f"Failed to restore DATABASE CSV files for project {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to restore DATABASE CSV files: {str(e)}") - - logging.info(f"Step 6: Restoring OUTPUT files for project {payload.pid}") - try: + # Step 8: Restore DATABASE CSV files + logging.info(f"Step 8: Pushing DATABASE CSV files to DB server for project {payload.pid}") + db_push_url = "http://192.168.50.84:70/api/ccp/push_db" + database_dir = f"/data/ccp/{payload.pid}/DATABASE" + if not os.path.exists(database_dir): + raise Exception("DATABASE folder not found in extracted files") + try: + files_transferred = [] + for filename in os.listdir(database_dir): + if filename.endswith(".csv"): + file_path = os.path.join(database_dir, filename) + with open(file_path, "rb") as f: + files_payload = {"file": (filename, f, "application/octet-stream")} + data_payload = {"pid": str(payload.pid)} + response = requests.post(db_push_url, files=files_payload, data=data_payload) + if response.status_code != 200: + raise Exception(f"Failed to push file {filename}: {response.text}") + else: + files_transferred.append(filename) + logging.info(f"Successfully pushed files to DB server: {files_transferred}") + except Exception as e: + logging.error(f"Failed to push DATABASE CSV files to DB server for project {payload.pid}: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to push DATABASE CSV files to DB server: {str(e)}") + logging.info(f"Step 8.5: Restoring DATABASE CSV files for project {payload.pid}") + try: + csv_files = build_csv_dict(payload.pid) + logging.info(f"CSV files to import: {csv_files}") + import_result = csv_DB.import_csv(csv_files, payload.pid) + if import_result is not True: + raise Exception("DB import_csv function returned failure") + logging.info("DATABASE CSV files restored successfully") + except Exception as e: + logging.error(f"Failed to restore DATABASE CSV files for project {payload.pid}: {str(e)}") + raise HTTPException(status_code=500, detail=f"Failed to restore DATABASE CSV files: {str(e)}") + # Step 9: Restore OUTPUT files + logging.info(f"Step 9: Restoring OUTPUT files for project {payload.pid}") output_folder = f"/data/ccp/{payload.pid}/OUTPUT" target_folder = os.path.join(output_folder, str(payload.pid)) - if not os.path.exists(target_folder): - raise Exception("Expected subfolder (named as pid) not found in OUTPUT folder") - archive_path = f"/data/ccp/{payload.pid}_output.tar.gz" - with tarfile.open(archive_path, "w:gz") as tar: - for root, dirs, files in os.walk(target_folder): - for file in files: - full_path = os.path.join(root, file) - rel_path = os.path.relpath(full_path, target_folder) - tar.add(full_path, arcname=rel_path) - logging.info(f"Archived OUTPUT folder to {archive_path}") - pull_output_url = "http://192.168.50.84:10080/api/ccp/pull_output" - archive_filename = f"{payload.pid}_output.tar.gz" - with open(archive_path, "rb") as file: - files = {"file": (archive_filename, file, "application/gzip")} - form_data = {"pid": str(payload.pid), "name": archive_filename} - logging.info(f"Uploading OUTPUT archive to Storage Server: {pull_output_url}") - response = requests.post(pull_output_url, files=files, data=form_data) - if response.status_code != 200: - logging.error(f"Failed to upload OUTPUT archive: {response.text}") - raise Exception("Failed to upload OUTPUT archive to Storage Server") - logging.info("OUTPUT files restored successfully on Storage Server") - os.remove(archive_path) - except Exception as e: - logging.info(f"Any files not found.. skipped.") - # logging.error(f"Failed to restore OUTPUT files for project {payload.pid}: {str(e)}") - # raise HTTPException(status_code=500, detail=f"Failed to restore OUTPUT files: {str(e)}") - - logging.info("Project import process completed successfully") - return {"RESULT_CODE": 200, "RESULT_MSG": f"Project {payload.pid} imported successfully."} + if os.path.exists(target_folder): + archive_path = f"/data/ccp/{payload.pid}_output.tar.gz" + with tarfile.open(archive_path, "w:gz") as tar: + for root, _, files in os.walk(target_folder): + for file in files: + full_path = os.path.join(root, file) + rel_path = os.path.relpath(full_path, target_folder) + tar.add(full_path, arcname=rel_path) + pull_output_url = "http://192.168.50.84:10080/api/ccp/pull_output" + with open(archive_path, "rb") as file: + response = requests.post(pull_output_url, files={"file": (f"{payload.pid}_output.tar.gz", file, "application/gzip")}) + if response.status_code != 200: + raise Exception("Failed to upload OUTPUT archive to Storage Server") + os.remove(archive_path) + else: + logging.info("No OUTPUT files found, skipping restore process.") + logging.info(f"------ Project import process completed successfully for PID {payload.pid} ------") + return {"RESULT_CODE": 200, "RESULT_MSG": f"Project {payload.pid} imported successfully."} + except Exception as e: + logging.error(f"Error during project import process for PID {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during project import: {str(e)}") # 복원 실패 시 revert 기능 추가해야 함 def initialize_folder(pid: int): """백업/추출을 위한 폴더를 초기화한다.""" - logging.info(f"Initializing folder /data/ccp/{pid}") try: - os.makedirs(f'/data/ccp/{pid}', exist_ok=True) os.makedirs(f'/data/ccp/{pid}/DATABASE', exist_ok=True) os.makedirs(f'/data/ccp/{pid}/OUTPUT', exist_ok=True) + logging.info(f"Folder structure initialized successfully for project {pid}") except Exception as e: - logging.error(f"Failed to initialize folder for project {pid}: {str(e)}") + logging.error(f"Failed to initialize folder for project {pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to initialize folder: {str(e)}") def export_database_csv(payload: ccp_payload): """DB 서버의 데이터를 CSV 파일로 내보낸다.""" - logging.info(f"Exporting the database to CSV files for project ID: {payload.pid}") try: result = csv_DB.export_csv(payload.pid) + if not handle_db_result(result): + raise Exception("Failed to export DB") + logging.info(f"Database export successful for project {payload.pid}") + return result except Exception as e: - logging.error(f"Failed to export DB: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to export DB: {e}") - if not handle_db_result(result): - raise HTTPException(status_code=500, detail="Failed to export DB") - return result + logging.error(f"Failed to export database for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to export database: {str(e)}") async def download_output_files(pid: int): """Storage 서버에서 OUTPUT 파일들을 다운로드 받아 지정 폴더에 저장한다.""" - logging.info(f"Copying the OUTPUT files from Storage Server to /data/ccp/{pid}/OUTPUT") try: result = await pull_storage_server(pid, f'/data/ccp/{pid}/OUTPUT') if result['RESULT_CODE'] != 200: - raise HTTPException(status_code=500, detail=result['RESULT_MSG']) + raise Exception(result['RESULT_MSG']) + logging.info(f"OUTPUT files downloaded successfully for project {pid}") + return result except Exception as e: - logging.error(f"Failed to download OUTPUT files from Storage server: {str(e)}") + logging.error(f"Failed to download OUTPUT files from Storage server for project {pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to download OUTPUT files: {str(e)}") - return result def encrypt_project_folder(pid: int): """지정 폴더를 암호화하여 CCP 파일로 생성한다.""" - logging.info(f"Encrypting /data/ccp/{pid} folder to /data/ccp/{pid}.ccp") try: encryption_result = encrypt_ccp_file(pid) if not encryption_result: - raise HTTPException(status_code=500, detail=f"Failed to encrypt project folder for pid {pid}") + raise Exception(f"Failed to encrypt project folder for pid {pid}") + logging.info(f"Encryption successful for project {pid}") + return encryption_result except Exception as e: - logging.error(f"Error during encryption process for pid {pid}: {str(e)}") + logging.error(f"Error during encryption process for pid {pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during encryption: {str(e)}") - return encryption_result def save_history_record(payload: ccp_payload) -> int: """DB 서버에 히스토리 레코드를 저장한다.""" - logging.info("Saving data to MySQL database (history record)") - ver = csv_DB.insert_csv_history(payload.pid, payload.univ_id, payload.msg) - if ver is None: - raise HTTPException(status_code=500, detail="Failed to insert history record") - return ver + try: + ver = csv_DB.insert_csv_history(payload.pid, payload.univ_id, payload.msg) + if ver is None: + raise Exception("Failed to insert history record") + logging.info(f"History record saved successfully with version {ver} for project {payload.pid}") + return ver + except Exception as e: + logging.error(f"Failed to save history record for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to insert history record: {str(e)}") def upload_ccp_file(payload: ccp_payload, ver: int): """생성된 CCP 파일을 Storage 서버에 업로드한다.""" - logging.info("Uploading CCP file to Storage Server") + logging.info(f"------ Starting CCP file upload for project {payload.pid} (version {ver}) ------") ccp_file_path = f"/data/ccp/{payload.pid}.ccp" ccp_file_name = f"{payload.pid}_{ver}.ccp" storage_url = "http://192.168.50.84:10080/api/ccp/pull" @@ -507,157 +434,162 @@ def upload_ccp_file(payload: ccp_payload, ver: int): logging.info(f"Sending CCP file to Storage Server: {storage_url}") response = requests.post(storage_url, files=files, data=form_data) if response.status_code != 200: - logging.error(f"Failed to upload CCP file: {response.text}") + logging.error(f"Failed to upload CCP file for project {payload.pid}: {response.text}", exc_info=True) raise HTTPException(status_code=500, detail="Failed to upload CCP file to Storage Server") logging.info(f"CCP file uploaded successfully: {ccp_file_name}") + logging.info(f"------ CCP file upload completed for project {payload.pid} ------") except FileNotFoundError: - logging.error(f"CCP file not found: {ccp_file_path}") + logging.error(f"CCP file not found: {ccp_file_path}", exc_info=True) raise HTTPException(status_code=404, detail="CCP file not found") except requests.exceptions.RequestException as e: - logging.error(f"Request error during CCP file upload: {str(e)}") + logging.error(f"Request error during CCP file upload for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Request to storage server failed: {str(e)}") except Exception as e: - logging.error(f"Unexpected error during CCP file upload: {str(e)}") + logging.error(f"Unexpected error during CCP file upload for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during CCP file upload: {str(e)}") def cleanup_project_folder(pid: int): """작업 후 생성된 폴더와 파일들을 정리한다.""" - logging.info(f"Deleting /data/ccp/{pid} folder and CCP file") try: - shutil.rmtree(f'/data/ccp/{pid}') + shutil.rmtree(f'/data/ccp/{pid}', ignore_errors=True) os.remove(f'/data/ccp/{pid}.ccp') + logging.info(f"Cleanup completed successfully for project {pid}") + except FileNotFoundError: + logging.warning(f"Some files for project {pid} were not found during cleanup.") except Exception as e: - logging.error(f"Failed to delete folder or CCP file for project {pid}: {str(e)}") + logging.error(f"Failed to delete folder or CCP file for project {pid}: {str(e)}", exc_info=True) @router.post("/ccp/export") async def api_project_export(payload: ccp_payload): """프로젝트 추출 기능""" - + logging.info(f"------ Start project export process for PID {payload.pid} ------") + # Step 1: 폴더 초기화 try: - logging.info(f"Initializing folder /data/ccp/{payload.pid}") - os.makedirs(f'/data/ccp/{payload.pid}', exist_ok=True) + logging.info(f"Initializing folder structure for project {payload.pid}") os.makedirs(f'/data/ccp/{payload.pid}/DATABASE', exist_ok=True) os.makedirs(f'/data/ccp/{payload.pid}/OUTPUT', exist_ok=True) except Exception as e: - logging.error(f"Failed to initialize folder for project {payload.pid}: {str(e)}") + logging.error(f"Failed to initialize folder for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to initialize folder: {str(e)}") - - logging.info(f"Exporting the database to CSV files for project ID: {payload.pid}") + # Step 2: 데이터베이스 내보내기 try: + logging.info(f"Exporting database to CSV for project {payload.pid}") result = csv_DB.export_csv(payload.pid) + if not handle_db_result(result): + raise Exception("Failed to export database") + logging.info("Database exported successfully") except Exception as e: - logging.error(f"Failed to export DB during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to export DB during backup: {str(e)}") - if not handle_db_result(result): - raise HTTPException(status_code=500, detail="Failed to export DB during backup") - - logging.info(f"Copying the CSV files from CD to /data/ccp/{payload.pid}/DATABASE for backup") + logging.error(f"Failed to export database for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to export database: {str(e)}") + # Step 3: CSV 파일 다운로드 try: - API_SERVER_URL = "http://192.168.50.84:70" - url = f"{API_SERVER_URL}/api/ccp/pull_db" - payload_dict = {"pid": payload.pid} - result = requests.post(url, json=payload_dict) - response_data = result.json() - if result.status_code != 200: - raise HTTPException(status_code=500, detail=response_data.get("message", "Unknown error")) + logging.info("Downloading CSV files from API Server") + api_url = "http://192.168.50.84:70/api/ccp/pull_db" + response = requests.post(api_url, json={"pid": payload.pid}) + if response.status_code != 200: + raise Exception(response.json().get("message", "Unknown error")) + logging.info("CSV files downloaded successfully") except Exception as e: - logging.error(f"Failed to download CSV files during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to download CSV files during backup: {str(e)}") - - logging.info(f"Clean up the CSV folder from CD") + logging.error(f"Failed to download CSV files: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to download CSV files: {str(e)}") + # Step 4: CSV 폴더 정리 try: - API_SERVER_URL = "http://192.168.50.84:70" - url = f"{API_SERVER_URL}/api/ccp/clean_db" - payload_dict = {"pid": payload.pid} - result = requests.post(url, json=payload_dict) - response_data = result.json() - if result.status_code != 200: - raise HTTPException(status_code=500, detail=response_data.get("message", "Unknown error")) + logging.info("Cleaning up the CSV folder from API Server") + cleanup_url = "http://192.168.50.84:70/api/ccp/clean_db" + response = requests.post(cleanup_url, json={"pid": payload.pid}) + if response.status_code != 200: + raise Exception(response.json().get("message", "Unknown error")) + logging.info("CSV folder cleaned up successfully") except Exception as e: - logging.error(f"Failed to clean up CSV folder during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to clean up CSV folder during backup: {str(e)}") - - logging.info(f"Copying the OUTPUT files from Storage Server to /data/ccp/{payload.pid}/OUTPUT for backup") + logging.error(f"Failed to clean up CSV folder: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to clean up CSV folder: {str(e)}") + # Step 5: OUTPUT 파일 다운로드 try: + logging.info("Downloading OUTPUT files from Storage Server") result = await pull_storage_server(payload.pid, f'/data/ccp/{payload.pid}/OUTPUT') if result['RESULT_CODE'] != 200: - raise HTTPException(status_code=500, detail=result['RESULT_MSG']) + raise Exception(result['RESULT_MSG']) + logging.info("OUTPUT files downloaded successfully") except Exception as e: - logging.error(f"Failed to download OUTPUT files during backup: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to download OUTPUT files during backup: {str(e)}") - - logging.info(f"Encrypting /data/ccp/{payload.pid} folder to create backup CCP file") + logging.error(f"Failed to download OUTPUT files: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to download OUTPUT files: {str(e)}") + # Step 6: 프로젝트 폴더 암호화 try: - encryption_result = encrypt_ccp_file(payload.pid) - if not encryption_result: - raise HTTPException(status_code=500, detail=f"Failed to encrypt project folder for backup, pid {payload.pid}") + logging.info("Encrypting project folder") + if not encrypt_ccp_file(payload.pid): + raise Exception("Failed to encrypt project folder") + logging.info("Project folder encrypted successfully") except Exception as e: - logging.error(f"Error during encryption for backup, pid {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during encryption for backup: {str(e)}") - - logging.info("Saving backup record to DB history") + logging.error(f"Error during encryption: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during encryption: {str(e)}") + # Step 7: 히스토리 저장 try: - backup_message = payload.msg - payload.msg = backup_message + logging.info("Saving backup history to DB") backup_ver = csv_DB.insert_csv_history(payload.pid, payload.univ_id, payload.msg) if backup_ver is None: raise Exception("Failed to insert backup history record") - logging.info(f"Backup history record inserted with version {backup_ver}") + logging.info(f"Backup history recorded successfully as version {backup_ver}") except Exception as e: - logging.error(f"Failed to save backup record: {str(e)}") - raise HTTPException(status_code=500, detail=f"Failed to save backup record: {str(e)}") - logging.info("Uploading backup CCP file to Storage Server") + logging.error(f"Failed to save backup history: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Failed to save backup history: {str(e)}") + # Step 8: CCP 파일 업로드 try: + logging.info("Uploading backup CCP file to Storage Server") history = csv_DB.fetch_csv_history(payload.pid) version = str(max(record['ver'] for record in history)) ccp_file_path = f"/data/ccp/{payload.pid}.ccp" ccp_file_name = f"{payload.pid}_{version}.ccp" storage_url = "http://192.168.50.84:10080/api/ccp/pull" - logging.info(f"Reading backup CCP file: {ccp_file_path}") with open(ccp_file_path, "rb") as file: files = {"file": (ccp_file_name, file, "application/octet-stream")} - form_data = {"pid": str(payload.pid), "name": ccp_file_name} - logging.info(f"Sending backup CCP file to Storage Server: {storage_url}") - response = requests.post(storage_url, files=files, data=form_data) + response = requests.post(storage_url, files=files, data={"pid": str(payload.pid), "name": ccp_file_name}) if response.status_code != 200: - logging.error(f"Failed to upload backup CCP file: {response.text}") - raise HTTPException(status_code=500, detail="Failed to upload backup CCP file to Storage Server") + raise Exception("Failed to upload backup CCP file") logging.info(f"Backup CCP file uploaded successfully: {ccp_file_name}") except FileNotFoundError: - logging.error(f"Backup CCP file not found: {ccp_file_path}") + logging.error(f"Backup CCP file not found: {ccp_file_path}", exc_info=True) raise HTTPException(status_code=404, detail="Backup CCP file not found") except requests.exceptions.RequestException as e: - logging.error(f"Request error during backup CCP file upload: {str(e)}") - raise HTTPException(status_code=500, detail=f"Request error during backup CCP file upload: {str(e)}") + logging.error(f"Request error during CCP file upload: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Request error during CCP file upload: {str(e)}") except Exception as e: - logging.error(f"Unexpected error during backup CCP file upload: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during backup CCP file upload: {str(e)}") - logging.info(f"Deleting /data/ccp/{payload.pid} folder and backup CCP file") + logging.error(f"Unexpected error during CCP file upload: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during CCP file upload: {str(e)}") + # Step 9: 임시 폴더 정리 try: - shutil.rmtree(f'/data/ccp/{payload.pid}') + logging.info("Cleaning up temporary project folder and CCP file") + shutil.rmtree(f'/data/ccp/{payload.pid}', ignore_errors=True) os.remove(f'/data/ccp/{payload.pid}.ccp') + logging.info("Temporary files deleted successfully") + except FileNotFoundError: + logging.warning(f"Some files were already deleted, skipping cleanup.") except Exception as e: - logging.error(f"Failed to delete folder or backup CCP file for project {payload.pid}: {str(e)}") + logging.error(f"Failed to clean up project folder: {str(e)}", exc_info=True) + logging.info(f"------ Project export process completed successfully for PID {payload.pid} ------") return {"RESULT_CODE": 200, "RESULT_MSG": f"Project {payload.pid} exported successfully."} @router.post("/ccp/del_history") async def api_delete_history(payload: ccp_payload): + """프로젝트 히스토리 삭제""" try: result = csv_DB.delete_csv_history(payload.pid) if not result: - raise HTTPException(status_code=500, detail=f"Failed to delete history for pid {payload.pid}") + raise Exception(f"Failed to delete history for project {payload.pid}") + logging.info(f"History successfully deleted for project {payload.pid}") except Exception as e: - logging.error(f"Error occurred during delete history for pid {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during process: {str(e)}") - return {"RESULT_CODE": 200, "RESULT_MSG": result} + logging.error(f"Error occurred while deleting history for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during deletion process: {str(e)}") + return {"RESULT_CODE": 200, "RESULT_MSG": "History deleted successfully"} @router.post("/ccp/load_history") async def api_load_history(payload: ccp_payload): + """프로젝트 히스토리 로드""" try: result = csv_DB.fetch_csv_history(payload.pid) if not result: - raise HTTPException(status_code=500, detail=f"Failed to load history for pid {payload.pid}") + raise Exception(f"Failed to load history for project {payload.pid}") + logging.info(f"History successfully loaded for project {payload.pid}, total records: {len(result)}") except Exception as e: - logging.error(f"Error occurred during load history for pid {payload.pid}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error during load: {str(e)}") - return {"RESULT_CODE": 200, "RESULT_MSG": result} \ No newline at end of file + logging.error(f"Error occurred while loading history for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during history load: {str(e)}") + return {"RESULT_CODE": 200, "RESULT_MSG": "History loaded successfully", "PAYLOAD": result} diff --git a/docs_converter.py b/docs_converter.py index f2dcf38..85e6ede 100644 --- a/docs_converter.py +++ b/docs_converter.py @@ -315,15 +315,15 @@ def process_report(doc_rep_no): @router.post("/docs/convert") async def docs_convert(payload: ConverterPayload): try: - if payload.doc_type == 0: # 프로젝트 개요서 / 완료 + if payload.doc_type == 0: # 프로젝트 개요서 return process_summary(payload.doc_s_no) - elif payload.doc_type == 1: # 회의록 / 완료 + elif payload.doc_type == 1: # 회의록 return process_meeting_minutes(payload.doc_s_no) - elif payload.doc_type == 2: # 테스트 케이스 / 완료, 나중에 실제 데이터로 검증 필요 + elif payload.doc_type == 2: # 테스트 케이스 return process_testcase(payload.doc_s_no) - elif payload.doc_type == 3: # 요구사항 명세서 / 정렬 이슈가 존재함 + elif payload.doc_type == 3: # 요구사항 명세서 return process_reqspec(payload.doc_s_no) - elif payload.doc_type == 4: # 보고서 / 완료 + elif payload.doc_type == 4: # 보고서 return process_report(payload.doc_s_no) else: raise HTTPException(status_code=400, detail="Unsupported document type") diff --git a/grade.py b/grade.py index fc2eb02..3645430 100644 --- a/grade.py +++ b/grade.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/11/24 - 업데이트 : 2025/02/14 + 업데이트 : 2025/03/08 설명 : 프로젝트를 평가와 관련 된 엔드포인트 정의 """ @@ -46,11 +46,12 @@ async def api_grade_add_comment(payload: GradePayload): try: result = grade_DB.add_comment(payload.pid, payload.univ_id, payload.comment) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in add comment Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Comment successfully added/updated for student {payload.univ_id} in project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Add Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in add comment Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in add comment Operation: {str(e)}") + logger.error(f"Error while adding/updating comment: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in add comment operation: {str(e)}") @router.post("/grade/comment_del") async def api_grade_del_comment(payload: GradePayload): @@ -58,11 +59,12 @@ async def api_grade_del_comment(payload: GradePayload): try: result = grade_DB.delete_comment(payload.pid, payload.univ_id) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in delete comment Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Comment successfully deleted for student {payload.univ_id} in project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Delete Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in delete comment Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in delete comment Operation: {str(e)}") + logger.error(f"Error while deleting comment: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in delete comment operation: {str(e)}") @router.post("/grade/comment_load_student") async def api_grade_load_comment_one(payload: GradePayload): @@ -70,11 +72,12 @@ async def api_grade_load_comment_one(payload: GradePayload): try: result = grade_DB.fetch_comment_by_student(payload.univ_id) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in load one comment Operation: {str(result)}") - return {"RESULT_CODE": 200, "RESULT_MSG": "Load one Successful.", "PAYLOAD": {"Result": result}} + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded {len(result)} comments for student {payload.univ_id}") + return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in load one comment Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in load one comment Operation: {str(e)}") + logger.error(f"Error while loading comments: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load comment operation: {str(e)}") @router.post("/grade/comment_load_project_student") async def api_grade_load_comment_one_project(payload: GradePayload): @@ -82,11 +85,12 @@ async def api_grade_load_comment_one_project(payload: GradePayload): try: result = grade_DB.fetch_one_comment(payload.pid, payload.univ_id) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in load one comment project Operation: {str(result)}") - return {"RESULT_CODE": 200, "RESULT_MSG": "Load one Successful.", "PAYLOAD": {"Result": result}} + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded comment for student {payload.univ_id} in project {payload.pid}") + return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in load one comment Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in load one comment Operation: {str(e)}") + logger.error(f"Error while loading comment: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load comment operation: {str(e)}") @router.post("/grade/comment_load_project") async def api_grade_load_comment_project(payload: GradePayload): @@ -94,11 +98,12 @@ async def api_grade_load_comment_project(payload: GradePayload): try: result = grade_DB.fetch_comment_by_project(payload.pid) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in load comment Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded {len(result)} comments for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in load comment Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in load comment Operation: {str(e)}") + logger.error(f"Error while loading comments for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load comment operation: {str(e)}") @router.post("/grade/assign") async def api_grade_assign(payload: AssignPayload): @@ -110,11 +115,12 @@ async def api_grade_assign(payload: AssignPayload): payload.tech, payload.presentation, payload.completion ) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in assign grade Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Grades successfully assigned for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Assign Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in assign grade Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in assign grade Operation: {str(e)}") + logger.error(f"Error while assigning grades for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in assign grade operation: {str(e)}") @router.post("/grade/edit") async def api_grade_edit(payload: AssignPayload): @@ -126,24 +132,25 @@ async def api_grade_edit(payload: AssignPayload): payload.tech, payload.presentation, payload.completion ) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in edit grade Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Grades successfully updated for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Edit Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in edit grade Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in edit grade Operation: {str(e)}") + logger.error(f"Error while editing grades for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in edit grade operation: {str(e)}") @router.post("/grade/delete") async def api_grade_delete(payload: GradePayload): """교수가 프로젝트의 평가를 삭제""" try: result = grade_DB.delete_grade(payload.pid) - # DB 함수에서 False를 반환할 경우, 해당 프로젝트의 평가가 존재하지 않음을 의미 - if isinstance(result, Exception) or result is False: - raise HTTPException(status_code=500, detail=f"Error in delete grade Operation: {str(result)}") + if isinstance(result, Exception) or result is False: # result가 False라면 해당 프로젝트에 평가가 존재하지 않다는 것을 의미 + raise Exception(f"Database error: {str(result)} or no grade found to delete") + logger.info(f"Grades successfully deleted for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Delete Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in delete grade Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in delete grade Operation: {str(e)}") + logger.error(f"Error while deleting grades for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in delete grade operation: {str(e)}") @router.post("/grade/load") async def api_grade_load(payload: GradePayload): @@ -151,8 +158,9 @@ async def api_grade_load(payload: GradePayload): try: result = grade_DB.fetch_grade(payload.pid) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in load grade Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded grades for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in load grade Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in load grade Operation: {str(e)}") \ No newline at end of file + logger.error(f"Error while loading grades for project {payload.pid}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load grade operation: {str(e)}") diff --git a/llm.py b/llm.py index fa53ad0..c229e8c 100644 --- a/llm.py +++ b/llm.py @@ -4,8 +4,8 @@ 파일명 : llm.py 생성자 : 김창환 - 생성일 : 2024/11/26 - 업데이트 : 2025/02/24 + 생성일 : 2024/11/26 + 업데이트 : 2025/03/17 설명 : llm 통신 관련 엔드포인트 정의 """ @@ -24,56 +24,15 @@ router = APIRouter() -# """ -# LLM 통신 절차 (구버전) - -# 1. DB로부터 프로젝트의 기본 정보 및 온라인 산출물 정보를 받아온다. -# 2. Storage Server로부터 MS Word (docx, doc, ...) 파일을 받아와 내용을 파싱한다. -# 3. 위 두 정보를 가공한 뒤 ChatGPT에 정보를 전달한다. -# 4. 필요에 따라 추가적으로 프롬프트를 전달한다. -# 5. ChatGPT에게 받은 응답을 프론트엔드에 전달한다. -# """ -# prompt_init_old = """ -# CodeCraft PMS (이하 PMS)는 Project Management System으로서, 기존의 서비스로는 대학생이 제대로 사용하기 힘들었다는 것을 개선하기 위해 만든 서비스이다. - -# 너는 이 PMS를 사용하는 대학생들에게 프로젝트를 진행하는 데 도움을 주기 위해 사용될 것이다. -# 모든 응답은 무조건 한국어로 답해주어야 한다. - -# 이 PMS는 코드보단 산출물 관리를 중점으로 진행하며, 다루게 될 산출물은 다음과 같다. -# WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서, SOW, System Architecture, Application Architecture, 메뉴 구성도, 벤치마킹, 역량점검, DB 설계서, DB 명세서, DB 정의서, DFD, 단위테스트, 발표자료, 통합테스트, 프로젝트 계획서, 프로젝트 명세서 - -# 이 중에서 PMS에서 자체적으로 작성 및 수정할 수 있는 산출물 (이하 온라인 산출물)은 다음과 같다: WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서. -# 온라인 산출물은 {db_data}에 포함되어 있으며, 이곳에 포함되지 않은 산출물은 {output_data}에 포함되어 있을 수도 있다. - -# {output_data}에는 PMS에서 자체적으로 제공해주지 않는 산출물에 대한 정보가 들어있으며, docx로 저장된 파일을 python을 통해 데이터를 가공해 데이터를 넘겨준 것이다. -# 그렇기 때문에 ms word로 작성되지 않은 문서는 데이터로 가공이 불가능해 데이터에서 누락됐을 수 있다. -# 데이터로 가공이 불가능한 문서의 경우 파일의 제목만 {output_data}에 첨부해 전달한다. -# 다만 현재 이 기능은 구현되지 않았으므로 {output_data}에 대한 내용은 무시하고, 온라인 산출물에 대한 내용만 생각한다. - -# 참고로, 답변에 pid와 같은 unique number는 backend에서 효율적으로 관리하기 위해 작성한 임의의 숫자이므로 실제 사용자는 알 필요가 없다. -# 그렇기 때문에 내용에 이와 관련된 내용은 포함하지 않도록 한다. -# 또한 {db_data}와 {output_data}도 backend에서 임의로 붙인 이름이므로 실제로 답변에 작성할 때는 {db_data}는 프로젝트/온라인 산출물 데이터로, {output_data}는 기타 산출물 데이터로 출력한다. -# 답변에는 이 서비스를 개발한 우리가 아니라 PMS를 이용하는 사람을 위해 사용될 것이므로 우리가 개발한 'PMS' 자체에 대한 수정이나 개선 사항을 내용에 포함하지는 않도록 한다. -# """ - -""" - LLM 통신 절차 - - 1. -""" - """ LLM 메뉴 구상도 ├── 메인 메뉴 │ ├── 프로젝트 - │ │ ├── 현재 프로젝트 분석 - │ │ ├── 프로젝트 진행에 대한 조언 - │ │ ├── (유저가 아이디어를 던지면 LLM이 정보를 가공 후 PMS에 적용 가능한 데이터로 만들어 안내하는 기능? <- 실현 가능한가..) + │ │ ├── 프로젝트 분석 및 조언 # prompt_project_0 + │ │ ├── 프로젝트 리스크 분석 # prompt_project_1 │ ├── 산출물 - │ │ ├── 현재 산출물 분석 - │ │ ├── (미정) - │ ├── (미정) - │ │ ├── (미정) + │ │ ├── 작성된 산출물 분석 # prompt_output_0 + │ │ ├── 산출물 품질 평가 # prompt_output_1 │ ├── PMS 서비스 안내 # 이 메뉴는 LLM 연계가 아닌 기존에 준비된 문장을 출력 │ │ ├── 대학생을 위한 PMS 서비스란? │ │ ├── 각 메뉴별 안내 @@ -87,31 +46,59 @@ """ prompt_init = """ - CodeCraft PMS (이하 PMS)는 Project Management System으로서, 기존의 서비스로는 대학생이 제대로 사용하기 힘들었다는 문제를 해결하기 위해 개발된 프로젝트 관리 서비스이다. - PMS는 학생들이 프로젝트를 체계적으로 진행할 수 있도록 WBS 기반의 산출물 관리 시스템을 제공하며, 프로젝트의 진행을 한눈에 파악할 수 있도록 설계되었다. - 너는 프로젝트를 지도하는 교수이며, 학생들이 원활하게 프로젝트를 진행하도록 가이드하는 역할을 한다. 학생들이 올바른 방향으로 프로젝트를 수행할 수 있도록 질문에 답변하고, 프로젝트의 산출물 작성 및 관리에 도움을 주는 것이 주요 역할이다. - - 이 PMS는 소스코드가 아닌 산출물 관리를 중점으로 진행하며, 다루게 될 산출물은 다음과 같다. - [WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서, SOW, System Architecture, Application Architecture, 메뉴 구성도, 벤치마킹, 역량점검, DB 설계서, DB 명세서, DB 정의서, DFD, 단위테스트, 발표자료, 통합테스트, 프로젝트 계획서, 프로젝트 명세서] - - 이 중에서 PMS에서 자체적으로 작성 및 수정할 수 있는 산출물 (이하 온라인 산출물)은 다음과 같다: WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서. - 온라인 산출물은 {db_data}에 포함되어 있으며, {output_data}에는 PMS에서 자체적으로 제공해주지 않는 산출물에 대한 정보가 들어있다. (이하 기타 산출물) - - 본 PMS는 RAG 기능 즉, '파일' 형태의 문서를 분석하는 기능을 지원하지 않으므로 {output_data}은 제목만으로 그 문서의 내용을 유추한다. - - 반드시 지켜야 할 사항은 다음과 같다. - 하나, 모든 응답은 무조건 한국어로 답해주어야 한다. - 둘, 답변에 pid와 같은 unique number는 backend에서 효율적으로 관리하기 위해 작성한 임의의 숫자이므로 사용자에게는 알리지 않는다. - 셋, {db_data}과 {output_data}도 backend에서 임의로 붙인 이름이므로 실제로 답변에 작성할 때는 {db_data}은 '프로젝트의 온라인 산출물'로, {output_data}은 '기타 산출물'로 출력한다. - 넷, 답변에는 이 서비스를 개발한 우리가 아니라 PMS를 이용하는 사람을 위해 사용될 것이므로 우리가 개발한 'PMS' 자체에 대한 수정이나 개선 사항을 내용에 포함하지 않는다. - 다섯, {output_data}의 문서 내용에 대해 추가로 알려주면 더 자세한 분석을 할 수 있다는 등의 응답은 하지 않는다. 무조건 제목으로 그 문서의 내용을 유추하며, 이와 유사한 응답과 질문은 금한다. - 여섯, 불필요한 말은 하지 않고 사용자가 요청한 내용에 대해서만 응답하고 멘트를 끊는다. - 일곱, 사용자가 이전에 제공한 규칙이나 내용을 다시 요청할 경우, 어떤 서론이나 추가적인 설명 없이 해당 내용만 출력한다. - 여덟, 모든 답변에서 서론 없이, 질문에 대한 핵심 내용만 간결하게 답변한다. - 위 내용은 무슨 일이 있어도 반드시 지킨다. +CodeCraft PMS(이하 PMS)는 대학생들이 보다 쉽게 프로젝트를 관리할 수 있도록 설계된 WBS 기반의 프로젝트 관리 시스템이다. +PMS는 **산출물 관리 및 프로젝트 진행 상태 시각화** 기능을 제공하여, 체계적인 프로젝트 운영을 지원한다. + +**PMS Assistant** +PMS에서 사용자와 LLM이 소통하는 기능을 "PMS Assistant"라고 하며, 교수 역할을 수행하여 프로젝트 진행을 돕는다. + +**메뉴 구성:** +- **프로젝트 관련** + - 현재 프로젝트 분석 + - 프로젝트 진행 조언 +- **산출물 관련** + - 현재 산출물 분석 + - 특정 산출물 작성 가이드 + +PMS는 **소스코드가 아닌 산출물 관리**를 중심으로 설계되었으며, 주요 산출물은 다음과 같다: +[WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서, SOW, System Architecture, Application Architecture, 메뉴 구성도, 벤치마킹, 역량점검, DB 설계서, DB 명세서, DB 정의서, DFD, 단위테스트, 발표자료, 통합테스트, 프로젝트 계획서, 프로젝트 명세서] + +이 중에서 **PMS에서 직접 관리할 수 있는 온라인 산출물**은 다음과 같다: +**[WBS, 개요서, 회의록, 테스트케이스, 요구사항 명세서, 보고서]** +이 데이터는 `{db_data}`에 포함되어 있으며, +그 외 PMS에서 관리하지 않는 **기타 산출물**은 `{output_data}`에 포함되어 있다. + +**반드시 지켜야 할 규칙** +1. 모든 응답은 **한국어**로 제공해야 한다. +2. `pid`와 같은 **unique number**는 사용자에게 노출하지 않는다. +3. `{db_data}`은 **'프로젝트의 온라인 산출물'**, `{output_data}`은 **'기타 산출물'**로 출력한다. +4. PMS 자체의 수정이나 개선 사항을 답변에 포함하지 않는다. +5. `{output_data}`의 문서는 제목을 기준으로 분석하며, 추가 설명 요청이 와도 고려하지 않는다. +6. 불필요한 서론 없이, 핵심 내용만 간결하게 답변한다. +7. 사용자가 이전에 요청한 규칙을 다시 요청하면 추가 설명 없이 해당 내용만 출력한다. """ -# 프로젝트 종료 일까지 100일 이하로 남았다면 수능처럼 디데이 알려주는 기능 만들기? +PROMPTS = [ + """ + 현재 이 프로젝트의 진행 상태를 전반적으로 분석해줘. + 프로젝트의 강점과 주의해야 할 점을 중심으로, 앞으로 나아가야 할 방향에 대해 간략한 조언을 제공해줘. + 단, 구체적인 해결 방안이나 내부 수정 사항은 포함하지 말아줘. + """, # prompt_project_0 + """ + 현재 이 프로젝트의 진행 상황을 바탕으로, 잠재적인 리스크 요소들을 분석해줘. + 프로젝트 일정, 팀 구성, 자원 배분, 기술적 이슈 등 여러 측면에서 발생할 수 있는 위험 요소들을 식별하고, 각 요소가 프로젝트에 미칠 영향을 간략하게 설명해줘. + 단, 구체적인 해결 방안이나 내부 수정 사항은 포함하지 말아줘. + """, # prompt_project_1 + """ + 현재 이 프로젝트에서 작성된 산출물(온라인 산출물과 기타 산출물)의 내용을 바탕으로, 각 산출물의 주요 구성 요소와 특징을 분석해줘. + 각 산출물의 제목과 문서 구성을 기준으로, 전달하려는 핵심 메시지와 강점을 간결하게 요약하고 설명해줘. + """, # prompt_output_0 + """ + 현재 이 프로젝트에서 작성된 산출물(온라인 산출물과 기타 산출물)의 품질을 평가해줘. + 문서의 내용, 구성, 가독성, 그리고 전달하려는 메시지의 명확성을 고려하여 각 산출물의 품질 수준을 간략하게 평가하고, 강점과 개선이 필요한 요소들을 요약해서 설명해줘. + 단, 구체적인 해결 방안이나 내부 수정 사항은 포함하지 말아줘. + """ # prompt_output_1 +] class keypayload(BaseModel): pid: int @@ -120,20 +107,17 @@ class keypayload(BaseModel): class llm_payload(BaseModel): pid: int prompt: str = None + menu: int = None def db_data_collect(pid): - return project_DB.fetch_project_for_LLM(pid) + data = project_DB.fetch_project_for_LLM(pid) + logger.info(f"DB data: " + data) + return data def output_data_collect(pid): - data = output_DB.fetch_all_other_documents(pid) - # result = analysis_output(data) - return result - -# def analysis_output(data): RAG 기능 폐기 결정으로 인한 기능 삭제 (25.02.22) -# # print(data) -# # ~개쩌는 문서 파싱 기능 구현~ # -# result = "output data는 아직 미구현 기능입니다." -# return result + data = str(output_DB.fetch_all_other_documents(pid)) + logger.info(f"Output data: " + data) + return data def load_key(pid): try: @@ -196,71 +180,26 @@ def llm_init(pid): data = f"[db_data: {db_data}], [output_data: {output_data}]" return data -def save_llm_data(pid, contents): - return - # path = "gpt/" + str(pid) + ".txt" - # with open(path, 'w') as f: - # f.write(contents) - -# @router.post("/llm/reconnect") # LLM 사용 컨셉이 변경됨에 따라 함수 비활성화 (25.02.19) -# async def api_reconnect_gpt(payload: llm_payload): -# # PMS의 세션을 복원 시 GPT 통신 기록을 프론트에 전달 # -# try: -# logging.info(f"Sending gpt chat file to Next.js using Raw Binary") -# file_name = str(payload.pid) + ".txt" -# llm_file_path = "gpt/" + file_name -# with open(llm_file_path, "rb") as file: -# response = requests.post( -# "http://192.168.50.84:90/api/file_receive", -# data=file, -# headers={ -# "Content-Type": "application/octet-stream", -# "file-name": quote(file_name) -# } -# ) -# if response.status_code != 200: -# logging.error(f"Frontend server response error: {response.status_code} - {response.text}") -# raise HTTPException(status_code=500, detail="Failed to send file to frontend") - -# logging.info(f"File {file_name} successfully transferred to frontend") -# return {"RESULT_CODE": 200, "RESULT_MSG": "File transferred successfully"} - -# except requests.exceptions.RequestException as e: -# logging.error(f"Failed to send file to frontend: {str(e)}") -# raise HTTPException(status_code=500, detail=f"Request to frontend failed: {str(e)}") - -def create_gpt_txt(pid): - contents = prompt_init + "\n\n" + llm_init(pid) - save_llm_data(pid, contents) - @router.post("/llm/interact") async def api_interact_gpt(payload: llm_payload): - # ChatGPT와 세션을 맺는 기능 구현 # try: - # gpt_chat_path = f"gpt/{payload.pid}.txt" - # if not os.path.isfile(gpt_chat_path): # 이전 프롬프트 기록이 없다면 - # create_gpt_txt(payload.pid) # 프롬프트 기록 생성 - try: - key = load_key(payload.pid) # Gemini key 로드 - except: + key = load_key(payload.pid) # Gemini key 로드 + except Exception as e: logger.debug(f"LLM process error while loading key for PID {payload.pid}: {str(e)}") raise HTTPException(status_code=500, detail="Key exception occurred.") - genai.configure(api_key=key) - model = genai.GenerativeModel("gemini-2.0-flash") # Gemini 모델 선언 - - # try: # 세션 복원 기능 삭제로 인한 비활성화 - # with open(gpt_chat_path, "r", encoding="utf-8") as file: - # previous_prompts = file.read() # 이전 프롬프트 기록 불러오기 - # except Exception as e: - # raise HTTPException(status_code=500, detail=f"Failed to read {gpt_file_path}: {e}") - - new_prompt = f"{prompt_init}\n\n{payload.prompt}" # 이전 프롬프트 + 신규 프롬프트 - response = model.generate_content(new_prompt) # 프롬프트 전송 - - # save_llm_data(payload.pid, response.text) 미사용 비활성화 + model = genai.GenerativeModel("gemini-2.0-flash") # Gemini 모델 선언 + # menu 값이 올바른 범위 내에 있는지 확인 + if 0 <= payload.menu < len(PROMPTS): + selected_prompt = PROMPTS[payload.menu] + else: + logger.debug(f"Invalid menu value received: {payload.menu}") + raise HTTPException(status_code=400, detail="Invalid menu value.") + # 최종 프롬프트 구성 + prompt = prompt_init + "\n\n" + llm_init(payload.pid) + selected_prompt + response = model.generate_content(prompt) return response.text except Exception as e: - logger.debug(f"Unhandled Error occured: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unhandled Error occured while LLM process: {str(e)}") \ No newline at end of file + logger.debug(f"Unhandled Error occurred: {str(e)}") + raise HTTPException(status_code=500, detail=f"Unhandled Error occurred while LLM process: {str(e)}") \ No newline at end of file diff --git a/logger.py b/logger.py index 203bbea..10842bc 100644 --- a/logger.py +++ b/logger.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2025/02/15 - 업데이트 : 2025/02/15 + 업데이트 : 2025/02/28 설명 : 디버깅 관련 로깅 함수 정의 """ @@ -21,4 +21,8 @@ ], ) -logger = logging.getLogger("project_logger") \ No newline at end of file +logger = logging.getLogger("project_logger") + +uvicorn_access_logger = logging.getLogger("uvicorn.access") # 액세스 로그도 남게 추가 (25.02.28) +uvicorn_access_logger.handlers.clear() +uvicorn_access_logger.propagate = True diff --git a/main.py b/main.py index 4f89e72..58b4538 100644 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/10/14 - 업데이트 : 2025/02/14 + 업데이트 : 2025/03/08 설명 : FastAPI 서버 설정 및 계정, 프로젝트, 업무, 산출물 관리 라우터 포함 """ @@ -23,6 +23,7 @@ from dotenv import load_dotenv from pathlib import Path from logger import logger +from datetime import datetime import os # 라우터 추가 파트 @@ -37,6 +38,7 @@ from permission import router as permission_router from docs_converter import router as docs_router from subject import router as subject_router +from professor import router as professor_router #from test import router as test_router # Frontend Axios에서 API 통신 테스트를 위한 라우터 # Database Project와의 연동을 위해 각 Router에 sys.path 경로 정의 필요 @@ -116,6 +118,13 @@ async def validation_exception_handler(request, exc): content={"detail": exc.errors()}, ) +server_start_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + +@app.on_event("startup") +async def startup_event(): + logger.info("------------------------------------------------------------") + logger.info(f"CodeCraft PMS Backend Server started at {server_start_time}") + logger.info("------------------------------------------------------------") @app.get("/") async def root(): @@ -134,4 +143,5 @@ async def root(): app.include_router(docs_router, prefix="/api") app.include_router(ccp_router, prefix="/api") app.include_router(subject_router, prefix="/api") +app.include_router(professor_router, prefix="/api") #app.include_router(test_router, prefix="/api") # 정식 Release 전 Delete 필요 diff --git a/output.py b/output.py index e946d17..23539d1 100644 --- a/output.py +++ b/output.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/10/20 - 업데이트 : 2025/01/21 + 업데이트 : 2025/02/26 설명 : 산출물의 생성, 수정, 조회, 삭제, 업로드를 위한 API 엔드포인트 정의 """ @@ -624,89 +624,118 @@ def gen_file_uid(): if not output_DB.is_uid_exists(tmp_uid): return tmp_uid @router.post("/output/otherdoc_add") -async def add_other_document( - file: UploadFile = File(...), +async def add_other_documents( + files: List[UploadFile] = File(...), pid: int = Form(...), univ_id: int = Form(...) ): """ 기타 산출물 추가 API """ + logger.info("------------------------------------------------------------") + logger.info("Starting process to add other documents") try: load_dotenv() headers = {"Authorization": os.getenv('ST_KEY')} - file_unique_id = gen_file_uid() - data = { - "fuid": file_unique_id, - "pid": pid, - "univ_id": univ_id - } + attachments = [] - # 파일 읽기 - file_content = await file.read() - files = {"file": (file.filename, file_content, file.content_type)} - - # 외부 요청 - response = requests.post( - "http://192.168.50.84:10080/api/output/otherdoc_add", - files=files, - data=data, - headers=headers - ) + for file in files: + logger.info(f"Processing file: {file.filename}") + file_unique_id = gen_file_uid() + logger.info(f"Generated file unique id: {file_unique_id}") - # 응답 상태 코드 확인 - if response.status_code != 200: - raise HTTPException( - status_code=response.status_code, - detail=f"Error: {response.status_code} - {response.text}" - ) - - # 응답 데이터 처리 - response_data = response.json() - file_path = response_data.get("FILE_PATH") # 외부 서버에서 반환된 파일 경로 - - # uploaded_date 변환 - uploaded_date = response_data.get("uploaded_date") - if uploaded_date: - # Convert '241124-153059' to '2024-11-24 15:30:59' - uploaded_date = datetime.strptime(uploaded_date, "%y%m%d-%H%M%S").strftime("%Y-%m-%d %H:%M:%S") - else: - raise HTTPException(status_code=500, detail="uploaded_date is missing in the response") - - # 메타데이터 저장 - db_result = output_DB.add_other_document( - file_unique_id=file_unique_id, - file_name=file.filename, - file_path=file_path, - file_date=uploaded_date, # DATETIME 형식으로 변환된 값 - univ_id = univ_id, - pid=pid - ) # 이후 univ_id 정의 필요 - - # DB 저장 실패 시 파일 삭제 처리 - if not db_result: - os.remove(file_path) - raise HTTPException( - status_code=500, - detail="File uploaded but failed to save metadata to the database." + data = { + "fuid": file_unique_id, + "pid": pid, + "userid": univ_id + } + # 파일 읽기 + file_content = await file.read() + files_payload = {"file": (file.filename, file_content, file.content_type)} + # 최대 3회 재시도 + max_attempts = 3 + for attempt in range(1, max_attempts + 1): + try: + logger.info(f"Uploading file {file.filename} to storage server (Attempt {attempt})") + response = requests.post( + "http://192.168.50.84:10080/api/output/otherdoc_add", + files=files_payload, + data=data, + headers=headers + ) + if response.status_code == 200: + # 업로드 성공 시 루프 종료 + logger.info(f"File upload succeeded on attempt {attempt} for {file.filename}") + break + else: + error_msg = (f"File upload failed for {file.filename} on attempt {attempt} " + f"with status code {response.status_code}: {response.text}") + logger.error(error_msg) + if attempt == max_attempts: + raise HTTPException( + status_code=response.status_code, + detail=error_msg + ) + except requests.exceptions.RequestException as req_exc: + logger.error(f"RequestException on attempt {attempt} for file {file.filename}: {str(req_exc)}", exc_info=True) + if attempt == max_attempts: + raise HTTPException( + status_code=500, + detail=f"Request failed for {file.filename} after {attempt} attempts: {str(req_exc)}" + ) + # 업로드 응답 처리 + response_data = response.json() + file_path = response_data.get("FILE_PATH") + uploaded_date = response_data.get("uploaded_date") + if uploaded_date: + try: + uploaded_date = datetime.strptime(uploaded_date, "%y%m%d-%H%M%S").strftime("%Y-%m-%d %H:%M:%S") + except Exception as parse_error: + error_msg = f"Error parsing uploaded_date for {file.filename}: {str(parse_error)}" + logger.error(error_msg, exc_info=True) + raise HTTPException(status_code=500, detail=error_msg) + else: + error_msg = f"uploaded_date is missing in the response for {file.filename}" + logger.error(error_msg) + raise HTTPException(status_code=500, detail=error_msg) + logger.info(f"Saving metadata to database for file: {file.filename}") + db_result = output_DB.add_other_document( + file_unique_id=file_unique_id, + file_name=file.filename, + file_path=file_path, + file_date=uploaded_date, + univ_id=univ_id, + pid=pid ) - - # 성공 응답 반환 - return { - "RESULT_CODE": 200, - "RESULT_MSG": "File uploaded and metadata saved successfully.", - "PAYLOADS": { + if not db_result: + logger.error(f"Database failed to save metadata for {file.filename}. Removing file from storage.") + os.remove(file_path) + raise HTTPException( + status_code=500, + detail="File uploaded but failed to save metadata to the database." + ) + logger.info(f"File processed successfully: {file.filename}") + attachments.append({ "file_unique_id": file_unique_id, "file_name": file.filename, "file_path": file_path, - } + "file_date": uploaded_date + }) + logger.info("All files processed successfully") + return { + "RESULT_CODE": 200, + "RESULT_MSG": "Files uploaded and metadata saved successfully.", + "PAYLOADS": attachments } - - except requests.exceptions.RequestException as e: - raise HTTPException(status_code=500, detail=f"Request failed: {e}") + except HTTPException as http_exc: + logger.error(f"HTTPException occurred: {http_exc.detail}") + raise http_exc except Exception as e: - raise HTTPException(status_code=500, detail=f"Error during file upload and metadata saving: {str(e)}") - + logger.error("Unexpected error occurred during file upload and metadata saving", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") + finally: + logger.info("------------------------------------------------------------") + @router.post("/output/otherdoc_edit_path") async def edit_otherdoc_path(file_unique_id: int = Form(...), new_file_path: str = Form(...)): diff --git a/professor.py b/professor.py new file mode 100644 index 0000000..2770914 --- /dev/null +++ b/professor.py @@ -0,0 +1,136 @@ +""" + CodeCraft PMS Backend Project + + 파일명 : professor.py + 생성자 : 김창환 + + 생성일 : 2025/03/17 + 업데이트 : 2025/03/17 + + 설명 : 교수 계정 관련 엔드포인트 +""" + +from fastapi import APIRouter, HTTPException +from pydantic import BaseModel +from logger import logger +import sys, os, random, string, logging + +sys.path.append(os.path.abspath('/data/Database Project')) # Database Project와 연동하기 위해 사용 +import project_DB +import account_DB + +router = APIRouter() + +class Signin_Payload(BaseModel): + id: str + pw: str + +class Token_Payload(BaseModel): + token: str + +class Checksession_Payload(BaseModel): + user_id: str + token: str + +class Profnum_Payload(BaseModel): + f_no: int + + +def generate_token(): + """랜덤한 15자리 토큰 생성""" + characters = string.ascii_letters + string.digits + string.punctuation + return ''.join(random.choices(characters, k=15)) + +@router.post("/prof/checksession") +async def api_prof_check_session(payload: Checksession_Payload): + """세션 유효성 확인""" + try: + is_valid = account_DB.validate_professor_token(payload.user_id, payload.token) + if isinstance(is_valid, Exception): + logger.error(f"Invalid session for professor {payload.user_id}: {str(is_valid)}", exc_info=True) + raise HTTPException(status_code=401, detail=f"Invalid session: {str(is_valid)}") + if is_valid: + logger.info(f"Session valid for professor {payload.user_id}") + return {"RESULT_CODE": 200, "RESULT_MSG": "Session valid"} + logger.warning(f"Invalid session token for professor {payload.user_id}") + raise HTTPException(status_code=401, detail="Invalid session token") + except Exception as e: + logger.error(f"Unexpected error validating session for professor {payload.user_id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error validating session: {str(e)}") + +@router.post("/prof/signin") +async def api_prof_signin_post(payload: Signin_Payload): + """사용자 로그인""" + try: + f_no = account_DB.validate_professor(payload.id, payload.pw) + if f_no is None: + logger.warning(f"Invalid login attempt: {payload.id}") + raise HTTPException(status_code=401, detail="Invalid credentials") + if isinstance(f_no, Exception): + logger.error(f"Internal error during validation: {str(f_no)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Internal error during validation: {str(f_no)}") + token = generate_token() + save_result = account_DB.save_signin_professor_token(payload.id, token) + if isinstance(save_result, Exception): + logger.error(f"Error saving session token for professor {payload.id}: {str(save_result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error saving session token: {str(save_result)}") + logger.info(f"User {payload.id} signed in successfully") + return { + "RESULT_CODE": 200, + "RESULT_MSG": "Login successful", + "PAYLOADS": { + "Token": token, + "Univ_ID": f_no + } + } + except HTTPException as http_err: + raise http_err + except Exception as e: + logger.error(f"Unhandled exception during login for user {payload.id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unhandled exception during login: {str(e)}") + +@router.post("/prof/signout") +async def api_prof_signout_post(payload: Token_Payload): + """사용자 로그아웃""" + try: + result = account_DB.signout_professor(payload.token) + if isinstance(result, Exception): + logger.error(f"Error during logout: {str(result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error during logout: {str(result)}") + if result is True: + logger.info("Professor logged out successfully") + return {"RESULT_CODE": 200, "RESULT_MSG": "Logout successful"} + logger.warning("Logout failed") + raise HTTPException(status_code=500, detail="Logout failed") + except Exception as e: + logger.error(f"Unhandled exception during logout: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unhandled exception during logout: {str(e)}") + +@router.post("/prof/check_acc") +async def api_prof_check_account_type(payload: Token_Payload): + """이 계정이 학생인지 교수인지 확인하는 함수""" + try: + result = account_DB.check_user_type(payload.token) + if isinstance(result, Exception): + logger.info(f"function api_prof_check_account_type failed: {str(result)}") + raise HTTPException(status_code=500, detail=f"check_acc failed") + elif result == 0: + logger.warning(f"function waring api_prof_check_account_type: Token {payload.token} isn't found in the database.") + raise HTTPException(status_code=404, detail=f"Token {payload.token} isn't found in the database.") + return {"RESULT_CODE": 200, "RESULT_MSG": "Check Successful.", "PAYLOAD": {"Result": result}} + except Exception as e: + logger.error(f"Error while check account token {payload.token}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in check token operation: {str(e)}") + +@router.post("/prof/load_project") +async def api_prof_load_project_info(payload: Profnum_Payload): + """교수용 프로젝트 로드 함수""" + try: + result = project_DB.fetch_project_info_for_professor(payload.f_no) + if isinstance(result, Exception): + logger.info(f"function api_prof_load_project_info failed: {str(result)}") + raise HTTPException(status_code=500, detail=f"load_project failed") + return {"RESULT_CODE": 200, "RESULT_MSG": "Check Successful.", "PAYLOAD": {"Result": result}} + except Exception as e: + logger.error(f"Error while load project {payload.f_no}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in load project operation: {str(e)}") \ No newline at end of file diff --git a/project.py b/project.py index 03a3015..f7086b2 100644 --- a/project.py +++ b/project.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/10/16 - 업데이트 : 2025/02/24 + 업데이트 : 2025/03/08 설명 : 프로젝트의 생성, 수정, 조회를 위한 API 엔드포인트 정의 """ @@ -20,6 +20,7 @@ sys.path.append(os.path.abspath('/data/Database Project')) # Database Project와 연동하기 위해 사용 import project_DB +import account_DB import permission import wbs @@ -112,48 +113,59 @@ class ProjectCheckUser(BaseModel): class Wizard(BaseModel): pid: int +class FindProf_Payload(BaseModel): + univ_id: int + # 유틸리티 함수 def gen_project_uid(): """프로젝트 고유 ID 생성""" while True: tmp_uid = random.randint(10000, 99999) - if not project_DB.is_uid_exists(tmp_uid): return tmp_uid + if not project_DB.is_uid_exists(tmp_uid): + return tmp_uid def init_file_system(PUID): + """파일 시스템 초기화""" load_dotenv() headers = {"Authorization": os.getenv('ST_KEY')} data = {"PUID": PUID} try: response = requests.post("http://192.168.50.84:10080/api/project/init", json=data, headers=headers) - if response.status_code == 200: return True - else: print(f"Error: {response.status_code} - {response.text}"); return False - except requests.exceptions.RequestException as e: - print(f"Request failed: {e}"); return False + if response.status_code == 200: + return True + else: + logger.error(f"File system initialization failed for PUID {PUID}: {response.status_code} - {response.text}") + return False + except requests.exceptions.RequestException as e: + logger.error(f"Request to init file system failed for PUID {PUID}: {str(e)}", exc_info=True) + return False + # API 엔드포인트 @router.post("/project/init") async def api_project_init(payload: ProjectInit): """프로젝트 생성 및 초기화""" try: - logger.debug("Step 1: Generating Project UID") + logger.info("------------------------------------------------------------") + logger.info("Starting project creation process") + logger.info("Step 1: Generating Project UID") PUID = gen_project_uid() - logger.debug(f"Generated PUID: {PUID}") - - logger.debug("Step 2: Initializing project in the database") + logger.info(f"Generated PUID: {PUID}") + logger.info("Step 2: Initializing project in the database") db_result = project_DB.init_project(payload, PUID) if not db_result: logger.error(f"Database initialization failed for PUID: {PUID}") raise HTTPException( status_code=500, - detail=f"Database initialization returned False for PUID: {PUID}", + detail=f"Database initialization failed for PUID: {PUID}", ) - - logger.debug("Step 3: Initializing file system") + logger.info("Step 3: Initializing file system") file_result = init_file_system(PUID) if not file_result: logger.error(f"File system initialization failed for PUID: {PUID}") delete_result = project_DB.delete_project(PUID) if not delete_result: + logger.error(f"Cleanup failed after file system initialization failure for PUID: {PUID}") raise HTTPException( status_code=500, detail=f"File system initialization failed for PUID: {PUID}, and cleanup also failed.", @@ -162,35 +174,31 @@ async def api_project_init(payload: ProjectInit): status_code=500, detail=f"File system initialization failed for PUID: {PUID}. Project deleted successfully.", ) - - logger.debug("Step 4: Adding project leader to database") + logger.info("Step 4: Adding project leader to database") adduser_result = project_DB.add_project_user(PUID, payload.univ_id, 1, "Project Leader") if not adduser_result: - logger.error(f"Add User to Project failed for PUID: {PUID}") + logger.error(f"Adding project leader failed for PUID: {PUID}") raise HTTPException( status_code=500, - detail=f"Add User to Project failed for PUID: {PUID}", + detail=f"Adding project leader failed for PUID: {PUID}", ) - - logger.debug("Step 5: Adding leader permissions") + logger.info("Step 5: Adding leader permissions") addleader_result = permission.add_leader_permission(PUID, payload.univ_id) if not addleader_result: - logger.error(f"Add leader permission failed for PUID: {PUID}") + logger.error(f"Adding leader permission failed for PUID: {PUID}") raise HTTPException( status_code=500, - detail=f"Add leader permission to user failed for PUID: {PUID}", + detail=f"Adding leader permission to user failed for PUID: {PUID}", ) - - logger.debug("Step 6: Init WBS data") + logger.info("Step 6: Initializing WBS data") wbs_data = [["", "", "", "", "", "", "INITWBS", "", 0, "2025-01-01", "2025-01-10", 1, 0, 0, 0]] initwbs_result = wbs.init_wbs(wbs_data, PUID) - if not addleader_result: - logger.error(f"Add leader permission failed for PUID: {PUID}") + if not initwbs_result: + logger.error(f"Initializing WBS data failed for PUID: {PUID}") raise HTTPException( status_code=500, - detail=f"Add leader permission to user failed for PUID: {PUID}", + detail=f"Initializing WBS data failed for PUID: {PUID}", ) - logger.info(f"Project {PUID} created successfully") return { "RESULT_CODE": 200, @@ -201,11 +209,13 @@ async def api_project_init(payload: ProjectInit): logger.error(f"HTTPException occurred: {http_exc.detail}") raise http_exc except Exception as e: - logger.error(f"Unexpected error occurred: {str(e)}", exc_info=True) + logger.error(f"Unexpected error occurred during project creation: {str(e)}", exc_info=True) raise HTTPException( status_code=500, detail=f"Unexpected error during project creation: {str(e)}", ) + finally: + logger.info("------------------------------------------------------------") @router.post("/project/edit") @@ -214,9 +224,11 @@ async def api_project_edit(payload: ProjectEdit): try: result = project_DB.edit_project(payload) if result is True: + logger.info(f"Project {payload.pid} has been updated successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Project updated successfully"} raise HTTPException(status_code=500, detail="Project update failed") except Exception as e: + logger.error(f"Project {payload.pid} update failed: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during project update: {str(e)}") @@ -226,8 +238,8 @@ async def api_project_load(payload: ProjectLoad): try: project_info = project_DB.fetch_project_info(payload.univ_id) if not project_info: + logger.warning(f"No projects found for university ID {payload.univ_id}") raise HTTPException(status_code=404, detail="No projects found") - payloads = [ { "pid": project["p_no"], @@ -237,12 +249,16 @@ async def api_project_load(payload: ProjectLoad): "pperiod": f"{project['p_start']}-{project['p_end']}", "pmm": project["p_method"], "wizard": project["p_wizard"], + "profno": project["f_no"], + "profname": project["f_name"], + "subno": project["subj_no"], } for project in project_info ] - + logger.info(f"Projects loaded successfully for university ID {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Projects loaded successfully", "PAYLOADS": payloads} except Exception as e: + logger.error(f"Project load failed for university ID {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during project load: {str(e)}") @@ -253,9 +269,11 @@ async def api_project_delete(payload: ProjectDelete): try: result = project_DB.delete_project(payload.pid) if result is True: + logger.info(f"Project {payload.pid} has been deleted successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Project deleted successfully"} raise HTTPException(status_code=500, detail="Project deletion failed") except Exception as e: + logger.error(f"Project {payload.pid} deletion failed: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during project deletion: {str(e)}") @@ -265,9 +283,11 @@ async def api_project_add_user(payload: ProjectAddUser): try: result = project_DB.add_project_user(payload.pid, payload.univ_id, 0, payload.role) if result is True: + logger.info(f"User added to project {payload.pid}, Univ ID {payload.univ_id}, Role {payload.role}") return {"RESULT_CODE": 200, "RESULT_MSG": "User added to project successfully"} raise HTTPException(status_code=500, detail="Failed to add user to project") except Exception as e: + logger.error(f"Error adding user to project {payload.pid}, Univ ID {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during user addition: {str(e)}") @@ -277,9 +297,11 @@ async def api_project_delete_user(payload: ProjectDeleteUser): try: result = project_DB.delete_project_user(payload.pid, payload.univ_id) if result is True: + logger.info(f"User removed from project {payload.pid}, Univ ID {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "User removed from project successfully"} raise HTTPException(status_code=500, detail="Failed to remove user from project") except Exception as e: + logger.error(f"Error removing user from project {payload.pid}, Univ ID {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during user removal: {str(e)}") @@ -289,9 +311,11 @@ async def api_project_edit_user(payload: ProjectEditUser): try: result = project_DB.edit_project_user(payload.univ_id, payload.pid, payload.role) if result is True: + logger.info(f"User role updated in project {payload.pid}, Univ ID {payload.univ_id}, New Role {payload.role}") return {"RESULT_CODE": 200, "RESULT_MSG": "User updated successfully"} raise HTTPException(status_code=500, detail="Failed to update user") except Exception as e: + logger.error(f"Error updating user in project {payload.pid}, Univ ID {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during user update: {str(e)}") @@ -301,11 +325,15 @@ async def api_project_check_pm(payload: ProjectCheckPM): try: has_permission = project_DB.validate_pm_permission(payload.pid, payload.univ_id) if has_permission: + logger.info(f"PM permission granted for project {payload.pid}, Univ ID {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Permission granted"} + logger.warning(f"PM permission denied for project {payload.pid}, Univ ID {payload.univ_id}") raise HTTPException(status_code=403, detail="Permission denied") except Exception as e: + logger.error(f"Error checking PM permission for project {payload.pid}, Univ ID {payload.univ_id}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error during permission check: {str(e)}") + @router.post("/project/checkuser") async def api_project_check_user(payload: ProjectCheckUser): """ @@ -316,9 +344,11 @@ async def api_project_check_user(payload: ProjectCheckUser): users = project_DB.fetch_project_user(payload.pid) # 데이터베이스 호출 실패 처리 if isinstance(users, Exception): + logger.error(f"Database error while fetching users for project {payload.pid}: {str(users)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error fetching project users: {str(users)}") # 프로젝트에 참여자가 없는 경우 처리 if not users: + logger.warning(f"Project {payload.pid} not found or has no users") raise HTTPException(status_code=404, detail="Project not found or has no users") # 참여자 목록 반환 payloads = [ @@ -330,12 +360,14 @@ async def api_project_check_user(payload: ProjectCheckUser): } for user in users ] + logger.info(f"Successfully retrieved users for project {payload.pid}") return { "RESULT_CODE": 200, "RESULT_MSG": "Project users retrieved successfully", "PAYLOADS": payloads } except Exception as e: + logger.error(f"Error checking project users for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error checking project users: {str(e)}") @@ -343,8 +375,9 @@ async def api_project_check_user(payload: ProjectCheckUser): async def api_complete_wizard(payload: Wizard): try: result = project_DB.complete_setup_wizard(payload.pid) + logger.info(f"Wizard completed successfully for project {payload.pid}") except Exception as e: - print(traceback.format_exc()) + logger.error(f"Failed to complete wizard for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Failed to complete wizard: {e}") return {"RESULT_CODE": 200, "RESULT_MSG": "Wizard complete successfully"} @@ -353,7 +386,8 @@ def init_draft_project(univ_id): """프로젝트 임시 저장 초기화 함수""" try: os.makedirs(f"draft/{univ_id}", exist_ok=True) - with open(f"draft/{univ_id}/draft_num", "w") as f: f.write("0") + with open(f"draft/{univ_id}/draft_num", "w") as f: + f.write("0") project_data = { "draft_id": {} } @@ -371,9 +405,10 @@ def init_draft_project(univ_id): } with open(f"draft/{univ_id}/draft.json", "w", encoding="utf-8") as f: json.dump(project_data, f, indent=4) + logger.info(f"Draft project initialized for university ID {univ_id}") return 0 except Exception as e: - logger.debug(f"Error occured during init draft project: {e}") + logger.error(f"Error occurred during draft project initialization for university ID {univ_id}: {e}", exc_info=True) return False @@ -386,6 +421,7 @@ def save_draft_json(univ_id, draft_id, payload: DraftPayload): with open(project_file, "r", encoding="utf-8") as f: project_data = json.load(f) except json.JSONDecodeError: + logger.error(f"Invalid JSON format in {project_file}, resetting draft data", exc_info=True) project_data = {"draft_id": {}} else: project_data = {"draft_id": {}} @@ -412,7 +448,7 @@ def save_draft_json(univ_id, draft_id, payload: DraftPayload): project_data["draft_id"][str(draft_id)] = draft_entry with open(project_file, "w", encoding="utf-8") as f: json.dump(project_data, f, indent=4, ensure_ascii=False) - print(f"Draft {draft_id} saved for project {univ_id}.") + logger.info(f"Draft {draft_id} saved for university ID {univ_id}") @router.post("/project/save_draft") @@ -423,43 +459,47 @@ async def api_save_draft_project(payload: DraftPayload): if not os.path.isdir(f"draft/{payload.leader_univ_id}"): id = init_draft_project(payload.leader_univ_id) if id is False: + logger.error(f"Failed to initialize draft project for university ID {payload.leader_univ_id}", exc_info=True) raise HTTPException(status_code=500, detail="Failed to init draft project") else: - with open(draft_path, "r") as f: - id = int(f.read().strip()) + with open(draft_path, "r") as f: id = int(f.read().strip()) if payload.new: - # print("id value is: " + str(id)) save_draft_json(payload.leader_univ_id, id, payload) - with open(draft_path, "w") as f: - f.write(str(id+1)) + with open(draft_path, "w") as f: f.write(str(id + 1)) else: if payload.draft_id is None: + logger.warning(f"Draft ID is required for updating for university ID {payload.leader_univ_id}") raise HTTPException(status_code=400, detail="Draft ID is required for updating") save_draft_json(payload.leader_univ_id, payload.draft_id, payload) + logger.info(f"Draft project saved successfully for university ID {payload.leader_univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Success"} @router.post("/project/load_draft") async def api_load_draft_project(payload: DraftPayload): """프로젝트 임시 저장 로드 함수""" - draft_folder = f"draft/{payload.leader_univ_id}" draft_num_path = f"{draft_folder}/draft_num" draft_json_path = f"{draft_folder}/draft.json" if not os.path.isdir(draft_folder): + logger.warning(f"Draft folder not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="Draft folder not found") try: with open(draft_num_path, "r") as f: draft_num = f.read().strip() except FileNotFoundError: + logger.warning(f"draft_num file not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="draft_num file not found") try: with open(draft_json_path, "r", encoding="utf-8") as f: draft_data = json.load(f) except FileNotFoundError: + logger.warning(f"draft.json file not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="draft.json file not found") except json.JSONDecodeError: + logger.error(f"Invalid JSON format in draft.json for university ID {payload.leader_univ_id}", exc_info=True) raise HTTPException(status_code=500, detail="Invalid JSON format in draft.json") + logger.info(f"Draft project loaded successfully for university ID {payload.leader_univ_id}") return { "RESULT_CODE": 200, "RESULT_MSG": "Success", @@ -471,41 +511,39 @@ async def api_load_draft_project(payload: DraftPayload): @router.post("/project/del_draft") async def api_delete_draft_project(payload: DraftPayload): """프로젝트 임시 저장 삭제 함수""" - draft_folder = f"draft/{payload.leader_univ_id}" draft_num_path = f"{draft_folder}/draft_num" draft_json_path = f"{draft_folder}/draft.json" - if not os.path.isdir(draft_folder): # payload 값 검증 - logger.debug(f"Draft folder not found") + if not os.path.isdir(draft_folder): + logger.warning(f"Draft folder not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="Draft folder not found") + if payload.draft_id is None: - logger.debug(f"Draft ID is required for deletion") + logger.warning(f"Draft ID is required for deletion for university ID {payload.leader_univ_id}") raise HTTPException(status_code=400, detail="Draft ID is required for deletion") try: with open(draft_json_path, "r", encoding="utf-8") as f: draft_data = json.load(f) except FileNotFoundError: - logger.debug(f"draft.json file not found") + logger.warning(f"draft.json file not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="draft.json file not found") except json.JSONDecodeError: - logger.debug(f"Invalid JSON format in draft.json") + logger.error(f"Invalid JSON format in draft.json for university ID {payload.leader_univ_id}", exc_info=True) raise HTTPException(status_code=500, detail="Invalid JSON format in draft.json") draft_id_str = str(payload.draft_id) if draft_id_str not in draft_data.get("draft_id", {}): - logger.debug(f"Draft ID not found") + logger.warning(f"Draft ID {draft_id_str} not found for university ID {payload.leader_univ_id}") raise HTTPException(status_code=404, detail="Draft ID not found") - # draft_id 오름차순 정렬 del draft_data["draft_id"][draft_id_str] sorted_drafts = {str(idx): value for idx, value in enumerate(draft_data["draft_id"].values())} draft_data["draft_id"] = sorted_drafts - new_draft_num = len(sorted_drafts) # 최대 ID + 1 - + new_draft_num = len(sorted_drafts) with open(draft_json_path, "w", encoding="utf-8") as f: json.dump(draft_data, f, indent=4, ensure_ascii=False) with open(draft_num_path, "w") as f: f.write(str(new_draft_num)) - + logger.info(f"Draft {payload.draft_id} deleted for university ID {payload.leader_univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Draft deleted successfully"} @@ -515,10 +553,12 @@ async def api_project_load_prof(payload: ProjectLoadUser): try: result = project_DB.fetch_project_professor_name(payload.pid) if isinstance(result, Exception): + logger.error(f"Error in Load professor Operation for project {payload.pid}: {str(result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error in Load professor Operation: {str(result)}") + logger.info(f"Professor loaded successfully for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in Load professor Operation: {str(e)}") + logger.error(f"Unexpected error in Load professor Operation for project {payload.pid}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unexpected error in Load professor Operation: {str(e)}") @router.post("/project/count_user") @@ -527,8 +567,24 @@ async def api_project_count_student(payload: ProjectLoad): try: result = project_DB.fetch_project_user_count(payload.univ_id) if isinstance(result, Exception): + logger.error(f"Error in count user Operation for university {payload.univ_id}: {str(result)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Error in count user Operation: {str(result)}") + logger.info(f"User count retrieved successfully for university {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Count Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in count user Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in count user Operation: {str(e)}") \ No newline at end of file + logger.error(f"Unexpected error in count user operation for university {payload.univ_id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in count user operation: {str(e)}") + +@router.post("/project/find_prof") +async def api_project_find_professor(payload: FindProf_Payload): + """자신의 학과에 속한 교수 리스트를 불러오는 기능""" + try: + result = account_DB.fetch_professor_list(payload.univ_id) + if isinstance(result, Exception): + logger.error(f"Error in find professor operation for university {payload.univ_id}: {str(result)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error in find professor operation: {str(result)}") + logger.info(f"Professor list retrieved successfully for university {payload.univ_id}") + return {"RESULT_CODE": 200, "RESULT_MSG": "Find Successful.", "PAYLOAD": {"Result": result}} + except Exception as e: + logger.error(f"Unexpected error in find professor operation for university {payload.univ_id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in find professor operation: {str(e)}") \ No newline at end of file diff --git a/push.py b/push.py index 3ac5942..deaf1cd 100644 --- a/push.py +++ b/push.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2025/01/25 - 업데이트 : 2025/01/25 + 업데이트 : 2025/03/08 설명 : Next.JS에 파일을 전송하는 함수 정의 """ @@ -17,30 +17,32 @@ from urllib.parse import quote def push_to_nextjs(file_path, file_name): + """파일을 Next.js 서버로 전송""" + logging.info(f"------ Starting file transfer to Next.js for {file_name} ------") try: - try: + with open(file_path, "rb") as file: logging.info(f"Sending file {file_name} to Next.js using Raw Binary") - - with open(file_path, "rb") as file: - response = requests.post( - "http://192.168.50.84:90/api/file_receive", - data=file, - headers={ - "Content-Type": "application/octet-stream", - "file-name": quote(file_name) - } - ) - if response.status_code != 200: - logging.error(f"Frontend server response error: {response.status_code} - {response.text}") - raise HTTPException(status_code=500, detail="Failed to send file to frontend") - - logging.info(f"File {file_name} successfully transferred to frontend") - return {"RESULT_CODE": 200, "RESULT_MSG": "File transferred successfully"} - - except requests.exceptions.RequestException as e: - logging.error(f"Failed to send file to frontend: {str(e)}") - raise HTTPException(status_code=500, detail=f"Request to frontend failed: {str(e)}") - + + response = requests.post( + "http://192.168.50.84:90/api/file_receive", + data=file, + headers={ + "Content-Type": "application/octet-stream", + "file-name": quote(file_name) + } + ) + if response.status_code != 200: + logging.error(f"Frontend server response error: {response.status_code} - {response.text}", exc_info=True) + raise HTTPException(status_code=500, detail="Failed to send file to frontend") + logging.info(f"File {file_name} successfully transferred to frontend") + logging.info(f"------ File transfer completed for {file_name} ------") + return {"RESULT_CODE": 200, "RESULT_MSG": "File transferred successfully"} + except FileNotFoundError: + logging.error(f"File not found: {file_path}", exc_info=True) + raise HTTPException(status_code=404, detail="File not found") + except requests.exceptions.RequestException as e: + logging.error(f"Request error during file transfer for {file_name}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Request to frontend failed: {str(e)}") except Exception as e: - logging.error(f"Unexpected error during file transfer: {str(e)}") + logging.error(f"Unexpected error during file transfer for {file_name}: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") \ No newline at end of file diff --git a/subject.py b/subject.py index d2b9588..a4df108 100644 --- a/subject.py +++ b/subject.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2025/02/14 - 업데이트 : 2025/02/14 + 업데이트 : 2025/03/08 설명 : 교과목 관련 엔드포인트 정의 """ @@ -30,11 +30,12 @@ async def api_subject_load_all(): try: result = subject_DB.fetch_subject_list() if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in Load all Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded {len(result)} subjects.") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in Load all Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in Load all Operation: {str(e)}") + logger.error(f"Error while loading all subjects: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in Load all subjects: {str(e)}") @router.post("/subject/load_dept") async def api_subject_load_by_dept(payload: SubjectPayload): @@ -42,11 +43,12 @@ async def api_subject_load_by_dept(payload: SubjectPayload): try: result = subject_DB.fetch_subject_list_of_dept(payload.dno) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in Load dept Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded {len(result)} subjects for department {payload.dno}.") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in Load dept Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in Load dept Operation: {str(e)}") + logger.error(f"Error while loading subjects for department {payload.dno}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in Load subjects for department: {str(e)}") @router.post("/subject/load_student") async def api_subject_load_by_student(payload: SubjectPayload): @@ -54,8 +56,9 @@ async def api_subject_load_by_student(payload: SubjectPayload): try: result = subject_DB.fetch_subject_list_of_student(payload.univ_id) if isinstance(result, Exception): - raise HTTPException(status_code=500, detail=f"Error in Load student Operation: {str(result)}") + raise Exception(f"Database error: {str(result)}") + logger.info(f"Successfully loaded {len(result)} subjects for student {payload.univ_id}.") return {"RESULT_CODE": 200, "RESULT_MSG": "Load Successful.", "PAYLOAD": {"Result": result}} except Exception as e: - logger.debug(f"Error in Load student Operation: {str(e)}") - raise HTTPException(status_code=500, detail=f"Unexpected error in Load student Operation: {str(e)}") \ No newline at end of file + logger.error(f"Error while loading subjects for student {payload.univ_id}: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Unexpected error in Load subjects for student: {str(e)}") diff --git a/task.py b/task.py index faf5bec..7b97478 100644 --- a/task.py +++ b/task.py @@ -5,7 +5,7 @@ 생성자 : 김창환 생성일 : 2024/10/20 - 업데이트 : 2024/12/15 + 업데이트 : 2025/03/08 설명 : 작업의 생성, 수정, 조회, 삭제를 위한 API 엔드포인트 정의 및 확장 """ @@ -45,14 +45,14 @@ class TaskEditPayload(BaseModel): class TaskDeletePayload(BaseModel): tid: int -# 업무 조회 @router.post("/task/load") async def load_tasks(payload: TaskLoadPayload): + """특정 프로젝트 및 사용자의 업무 조회""" try: task_info_list = task_DB.fetch_task_info(payload.pid, payload.univ_id) if not task_info_list: + logger.info(f"No tasks found for project {payload.pid}, user {payload.univ_id}") return {"RESULT_CODE": 404, "RESULT_MSG": "No tasks found for the given project and user."} - tasks = [ { "tid": task["w_no"], @@ -65,26 +65,26 @@ async def load_tasks(payload: TaskLoadPayload): } for task in task_info_list ] - + logger.info(f"Successfully loaded {len(tasks)} tasks for project {payload.pid}, user {payload.univ_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Success", "PAYLOADS": tasks} - except Exception as e: - return {"RESULT_CODE": 500, "RESULT_MSG": str(e)} + logger.error(f"Error while loading tasks: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error while loading tasks: {str(e)}") -# 업무 모두 조회 @router.post("/task/load_all") async def load_tasks_all(payload: TaskLoadPayload): + """프로젝트 내 모든 업무 조회""" try: tasks = task_DB.fetch_all_task_info(payload.pid) + logger.info(f"Successfully loaded {len(tasks)} tasks for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Tasks fetched successfully", "PAYLOADS": tasks} except Exception as e: - print(f"Error [fetch_all_tasks_info]: {e}") - raise HTTPException(status_code=500, detail=f"Error fetching tasks info: {e}") - + logger.error(f"Error while fetching all tasks: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error fetching all tasks: {str(e)}") -# 업무 추가 @router.post("/task/add") async def add_task(payload: TaskAddPayload): + """업무 추가""" try: task_id = task_DB.add_task_info( tname=payload.tname, @@ -96,15 +96,15 @@ async def add_task(payload: TaskAddPayload): ) if isinstance(task_id, Exception): raise task_id - + logger.info(f"Task '{payload.tname}' added successfully with ID {task_id}") return {"RESULT_CODE": 200, "RESULT_MSG": "Task added successfully.", "PAYLOADS": {"task_id": task_id}} - except Exception as e: - return {"RESULT_CODE": 500, "RESULT_MSG": str(e)} + logger.error(f"Error while adding task: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error while adding task: {str(e)}") -# 업무 수정 @router.post("/task/edit") async def edit_task(payload: TaskEditPayload): + """업무 수정""" try: success = task_DB.update_task_info( tname=payload.tname, @@ -116,22 +116,22 @@ async def edit_task(payload: TaskEditPayload): w_no=payload.tid, ) if not success: - raise HTTPException(status_code=500, detail="Task update failed.") - + raise Exception("Task update failed.") + logger.info(f"Task {payload.tid} updated successfully for project {payload.pid}") return {"RESULT_CODE": 200, "RESULT_MSG": "Task updated successfully."} - except Exception as e: - return {"RESULT_CODE": 500, "RESULT_MSG": str(e)} + logger.error(f"Error while editing task: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error while editing task: {str(e)}") -# 업무 삭제 @router.post("/task/delete") async def delete_task(payload: TaskDeletePayload): + """업무 삭제""" try: success = task_DB.delete_task_info(payload.tid) if not success: - raise HTTPException(status_code=500, detail="Task deletion failed.") - + raise Exception("Task deletion failed.") + logger.info(f"Task {payload.tid} deleted successfully") return {"RESULT_CODE": 200, "RESULT_MSG": "Task deleted successfully."} - except Exception as e: - return {"RESULT_CODE": 500, "RESULT_MSG": str(e)} \ No newline at end of file + logger.error(f"Error while deleting task: {str(e)}", exc_info=True) + raise HTTPException(status_code=500, detail=f"Error while deleting task: {str(e)}") diff --git a/wbs.py b/wbs.py index e6ff897..b1be17e 100644 --- a/wbs.py +++ b/wbs.py @@ -39,9 +39,7 @@ def init_wbs(data, pid): init_result = wbs_DB.add_multiple_wbs(data, pid) if init_result != True: raise HTTPException(status_code=500, detail=f"Failed to add init WBS data. Error: {init_result}") - return {"RESULT_CODE": 200, "RESULT_MSG": "WBS init successful"} - except Exception as e: raise HTTPException(status_code=500, detail=f"Error during WBS batch update: {str(e)}")