Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
365c927
feat: db key prefix 이름을 담고있는 enum 생성
ChoiSeungWoo98 Aug 8, 2025
ca197f5
feat: db profile 저장 시 사용되는 코드 추가 및 문구가 오류인걸 에러로 수정
ChoiSeungWoo98 Aug 8, 2025
ac0445a
refactor: 컬럼 설정 변경 및 추가, 제거 시 업데이트 되도록 수정
ChoiSeungWoo98 Aug 8, 2025
b23b8c1
refactor: result를 공통으로 사용 하도록 수정
ChoiSeungWoo98 Aug 8, 2025
c6ad827
refactor: db_profile을 공통으로 사용하도록 수정
ChoiSeungWoo98 Aug 8, 2025
5b457ad
style: name을 view_name으로 변수 명칭 수정
ChoiSeungWoo98 Aug 8, 2025
be92d22
feat: db profile 저장하는 부분 추가
ChoiSeungWoo98 Aug 8, 2025
ad016e1
feat: 전체 방생하는 로그를 찍어주는 logging 추가
ChoiSeungWoo98 Aug 8, 2025
a24d3da
feat: db 전체 조회 상태 값 추가
ChoiSeungWoo98 Aug 8, 2025
2df2757
refactor: 조회 결과 부분 result 모델로 이동
ChoiSeungWoo98 Aug 8, 2025
dd07df6
feat: db 전체 조회 기능 구현
ChoiSeungWoo98 Aug 8, 2025
05b5030
feat: 반환 상태 값 추가
ChoiSeungWoo98 Aug 10, 2025
846a7e7
feat: 반환 타입 추가
ChoiSeungWoo98 Aug 10, 2025
e746eed
feat: 스키마, 테이블, 컬럼 조회 로직 구현
ChoiSeungWoo98 Aug 10, 2025
21930f9
feat: 업데이트 시 사용되는 상태 값 추가
ChoiSeungWoo98 Aug 10, 2025
27ebc1f
refactor: save 시 사용되는 모델 공통으로 사용하도록 수정
ChoiSeungWoo98 Aug 10, 2025
08ad1e5
feat: profile 정보 업데이트 하는 로직 생성
ChoiSeungWoo98 Aug 10, 2025
61f0e71
feat: 제거 시 사용되는 상태 값 추가
ChoiSeungWoo98 Aug 10, 2025
fdb0160
refactor: save 및 update 시 사용되는 모델 공통으로 사용하도록 수정
ChoiSeungWoo98 Aug 10, 2025
7c2b1e3
feat: 삭제 하는 로직 생성
ChoiSeungWoo98 Aug 10, 2025
e1188d8
style: modify 부분 네이밍 일치시키는 작업 진행
ChoiSeungWoo98 Aug 10, 2025
e8329fd
style: delete 부분 네이밍 일관성 유지하도록 수정
ChoiSeungWoo98 Aug 10, 2025
7dc1301
style: create 부분 네이밍 일관성 있게 수정
ChoiSeungWoo98 Aug 10, 2025
da28d28
refactor: 연결 테스트에서 메서드로 뺀 부분을 사용하도록 수정
ChoiSeungWoo98 Aug 10, 2025
6ad7745
refactor: 쿼리 생성 부분 service 쪽으로 빼서 작업하도록 개선
ChoiSeungWoo98 Aug 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 125 additions & 4 deletions app/api/user_db_api.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# app/api/user_db_api.py

from fastapi import APIRouter, Depends
from typing import List

from app.core.exceptions import APIException
from app.core.response import ResponseMessage
from app.schemas.user_db.db_profile_model import DBProfileCreate
from app.schemas.user_db.db_profile_model import DBProfileInfo, UpdateOrCreateDBProfile
from app.services.user_db_service import UserDbService, user_db_service
from app.schemas.user_db.result_model import DBProfile, ColumnInfo

user_db_service_dependency = Depends(lambda: user_db_service)

Expand All @@ -18,13 +20,132 @@
summary="DB 연결 테스트",
)
def connection_test(
db_info: DBProfileCreate,
db_info: DBProfileInfo,
service: UserDbService = user_db_service_dependency,
) -> ResponseMessage[bool]:
"""DB 연결 정보를 받아 연결 가능 여부를 테스트합니다."""
db_info.validate_required_fields()

db_info.validate_required_fields()
result = service.connection_test(db_info)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.is_successful, code=result.code)

@router.post(
"/create/profile",
response_model=ResponseMessage[str],
summary="DB 프로필 저장",
)
def create_profile(
create_db_info: UpdateOrCreateDBProfile,
service: UserDbService = user_db_service_dependency,
) -> ResponseMessage[str]:

create_db_info.validate_required_fields()
result = service.create_profile(create_db_info)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.view_name, code=result.code)

@router.put(
"/modify/profile",
response_model=ResponseMessage[str],
summary="DB 프로필 업데이트",
)
def update_profile(
update_db_info: UpdateOrCreateDBProfile,
service: UserDbService = user_db_service_dependency,
) -> ResponseMessage[str]:

update_db_info.validate_required_fields()
result = service.update_profile(update_db_info)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.view_name, code=result.code)

@router.delete(
"/remove/{profile_id}",
response_model=ResponseMessage[str],
summary="DB 프로필 삭제",
)
def delete_profile(
profile_id: str,
service: UserDbService = user_db_service_dependency,
) -> ResponseMessage[str]:

result = service.delete_profile(profile_id)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.view_name, code=result.code)

@router.get(
"/find/all",
response_model=ResponseMessage[List[DBProfile]],
summary="DB 프로필 전체 조회",
)
def find_all_profile(
service: UserDbService = user_db_service_dependency,
) -> ResponseMessage[List[DBProfile]]:

result = service.find_all_profile()

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.profiles, code=result.code)

@router.get(
"/find/schemas/{profile_id}",
response_model=ResponseMessage[List[str]],
summary="특정 DB의 전체 스키마 조회",
)
def find_schemas(
profile_id: str,
service: UserDbService = user_db_service_dependency
) -> ResponseMessage[List[str]]:

db_info = service.find_profile(profile_id)
result = service.find_schemas(db_info)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.schemas, code=result.code)

@router.get(
"/find/tables/{profile_id}/{schema_name}",
response_model=ResponseMessage[List[str]],
summary="특정 스키마의 전체 테이블 조회",
)
def find_tables(
profile_id: str,
schema_name: str,
service: UserDbService = user_db_service_dependency
) -> ResponseMessage[List[str]]:

db_info = service.find_profile(profile_id)
result = service.find_tables(db_info, schema_name)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.tables, code=result.code)

@router.get(
"/find/columns/{profile_id}/{schema_name}/{table_name}",
response_model=ResponseMessage[List[ColumnInfo]],
summary="특정 테이블의 전체 컬럼 조회",
)
def find_columns(
profile_id: str,
schema_name: str,
table_name: str,
service: UserDbService = user_db_service_dependency
) -> ResponseMessage[List[ColumnInfo]]:

db_info = service.find_profile(profile_id)
result = service.find_columns(db_info, schema_name, table_name)

if not result.is_successful:
raise APIException(result.code)
return ResponseMessage.success(value=result.columns, code=result.code)
33 changes: 33 additions & 0 deletions app/core/all_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# app/core/all_logging.py

import logging
from fastapi import Request

# 로깅 기본 설정 (애플리케이션 시작 시 한 번만 구성)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s", # [수정] 로그 레벨(INFO, ERROR)을 포함
datefmt="%Y-%m-%d %H:%M:%S",
)


async def log_requests_middleware(request: Request, call_next):
"""
모든 API 요청과 에러에 대한 로그를 남기는 미들웨어입니다.
"""
endpoint = f"{request.method} {request.url.path}"

# 일반 요청 로그를 남깁니다.
logging.info(f"엔드포인트: {endpoint}")

try:
# 다음 미들웨어 또는 실제 엔드포인트를 호출합니다.
response = await call_next(request)
return response
except Exception as e:
# [수정] 에러 발생 시, exc_info=True를 추가하여 전체 트레이스백을 함께 기록합니다.
# 메시지 형식도 "ERROR 엔드포인트:"로 변경합니다.
logging.error(f"ERROR 엔드포인트: {endpoint}", exc_info=True)
# 예외를 다시 발생시켜 FastAPI의 전역 예외 처리기가 최종 응답을 만들도록 합니다.
raise e

7 changes: 7 additions & 0 deletions app/core/enum/db_key_prefix_name.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# app/core/enum/db_key_prefix_name.py
from enum import Enum

class DBSaveIdEnum(Enum):
"""저장할 디비 ID 앞에 들어갈 이름"""
user_db = "USER-DB"
driver = "DRIVER"
49 changes: 32 additions & 17 deletions app/core/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ class CommonCode(Enum):
""" DRIVER, DB 성공 코드 - 21xx """
SUCCESS_DRIVER_INFO = (status.HTTP_200_OK, "2100", "드라이버 정보 조회를 성공하였습니다.")
SUCCESS_USER_DB_CONNECT_TEST = (status.HTTP_200_OK, "2101", "테스트 연결을 성공하였습니다.")
SUCCESS_FIND_PROFILE = (status.HTTP_200_OK, "2102", "디비 정보 조회를 성공하였습니다.")
SUCCESS_FIND_SCHEMAS = (status.HTTP_200_OK, "2103", "디비 스키마 정보 조회를 성공하였습니다.")
SUCCESS_FIND_TABLES = (status.HTTP_200_OK, "2104", "디비 테이블 정보 조회를 성공하였습니다.")
SUCCESS_FIND_COLUMNS = (status.HTTP_200_OK, "2105", "디비 컬럼 정보 조회를 성공하였습니다.")
SUCCESS_SAVE_PROFILE = (status.HTTP_200_OK, "2130", "디비 연결 정보를 저장하였습니다.")
SUCCESS_UPDATE_PROFILE = (status.HTTP_200_OK, "2150", "디비 연결 정보를 업데이트 하였습니다.")
SUCCESS_DELETE_PROFILE = (status.HTTP_200_OK, "2170", "디비 연결 정보를 삭제 하였습니다.")


""" KEY 성공 코드 - 22xx """

Expand All @@ -31,27 +39,27 @@ class CommonCode(Enum):
""" SQL 성공 코드 - 25xx """

# =======================================
# 클라이언트 오류 (Client Error) - 4xxx
# 클라이언트 에러 (Client Error) - 4xxx
# =======================================
""" 기본 클라이언트 오류 코드 - 40xx """
""" 기본 클라이언트 에러 코드 - 40xx """
NO_VALUE = (status.HTTP_400_BAD_REQUEST, "4000", "필수 값이 존재하지 않습니다.")
DUPLICATION = (status.HTTP_409_CONFLICT, "4001", "이미 존재하는 데이터입니다.")
NO_SEARCH_DATA = (status.HTTP_404_NOT_FOUND, "4002", "요청한 데이터를 찾을 수 없습니다.")
INVALID_PARAMETER = (status.HTTP_422_UNPROCESSABLE_ENTITY, "4003", "필수 값이 누락되었습니다.")

""" DRIVER, DB 클라이언트 오류 코드 - 41xx """
""" DRIVER, DB 클라이언트 에러 코드 - 41xx """
INVALID_DB_DRIVER = (status.HTTP_409_CONFLICT, "4100", "지원하지 않는 데이터베이스입니다.")
NO_DB_DRIVER = (status.HTTP_400_BAD_REQUEST, "4101", "데이터베이스는 필수 값입니다.")

""" KEY 클라이언트 오류 코드 - 42xx """
""" KEY 클라이언트 에러 코드 - 42xx """
INVALID_API_KEY_FORMAT = (status.HTTP_400_BAD_REQUEST, "4200", "API 키의 형식이 올바르지 않습니다.")
INVALID_API_KEY_PREFIX = (
status.HTTP_400_BAD_REQUEST,
"4201",
"API 키가 선택한 서비스의 올바른 형식이 아닙니다. (예: OpenAI는 sk-로 시작)",
)

""" AI CHAT TAB 클라이언트 오류 코드 - 43xx """
""" AI CHAT, DB 클라이언트 에러 코드 - 43xx """
INVALID_CHAT_TAB_NAME_FORMAT = (status.HTTP_400_BAD_REQUEST, "4300", "채팅 탭 이름의 형식이 올바르지 않습니다.")
INVALID_CHAT_TAB_NAME_LENGTH = (
status.HTTP_400_BAD_REQUEST,
Expand All @@ -65,15 +73,15 @@ class CommonCode(Enum):
"허용되지 않는 특수 문자: 큰따옴표(\"), 작은따옴표('), 세미콜론(;), 꺾쇠괄호(<, >)",
)

""" ANNOTATION 클라이언트 오류 코드 - 44xx """
""" ANNOTATION 클라이언트 에러 코드 - 44xx """

""" SQL 클라이언트 오류 코드 - 45xx """
""" SQL 클라이언트 에러 코드 - 45xx """

# ==================================
# 서버 오류 (Server Error) - 5xx
# 서버 에러 (Server Error) - 5xx
# ==================================
""" 기본 서버 오류 코드 - 50xx """
FAIL = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5000", "서버 처리 중 오류가 발생했습니다.")
""" 기본 서버 에러 코드 - 50xx """
FAIL = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5000", "서버 처리 중 에러가 발생했습니다.")
DB_BUSY = (
status.HTTP_503_SERVICE_UNAVAILABLE,
"5001",
Expand All @@ -82,19 +90,26 @@ class CommonCode(Enum):
FAIL_TO_VERIFY_CREATION = (
status.HTTP_500_INTERNAL_SERVER_ERROR,
"5002",
"데이터 생성 후 검증 과정에서 오류가 발생했습니다.",
"데이터 생성 후 검증 과정에서 에러가 발생했습니다.",
)

""" DRIVER, DB 서버 오류 코드 - 51xx """
FAIL_CONNECT_DB = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5100", "디비 연결 중 오류가 발생했습니다.")
""" DRIVER, DB 서버 에러 코드 - 51xx """
FAIL_CONNECT_DB = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5100", "디비 연결 중 에러가 발생했습니다.")
FAIL_FIND_PROFILE = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5101", "디비 정보 조회 중 에러가 발생했습니다.")
FAIL_FIND_SCHEMAS = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5102", "디비 스키마 정보 조회 중 에러가 발생했습니다.")
FAIL_FIND_TABLES = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5103", "디비 테이블 정보 조회 중 에러가 발생했습니다.")
FAIL_FIND_COLUMNS = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5104", "디비 컬럼 정보 조회 중 에러가 발생했습니다.")
FAIL_SAVE_PROFILE = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5130", "디비 정보 저장 중 에러가 발생했습니다.")
FAIL_UPDATE_PROFILE = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5150", "디비 정보 업데이트 중 에러가 발생했습니다.")
FAIL_DELETE_PROFILE = (status.HTTP_500_INTERNAL_SERVER_ERROR, "5170", "디비 정보 삭제 중 에러가 발생했습니다.")

""" KEY 서버 오류 코드 - 52xx """
""" KEY 서버 에러 코드 - 52xx """

""" AI CHAT, DB 서버 오류 코드 - 53xx """
""" AI CHAT, DB 서버 에러 코드 - 53xx """

""" ANNOTATION 서버 오류 코드 - 54xx """
""" ANNOTATION 서버 에러 코드 - 54xx """

""" SQL 서버 오류 코드 - 55xx """
""" SQL 서버 에러 코드 - 55xx """

def __init__(self, http_status: int, code: str, message: str):
"""Enum 멤버가 생성될 때 각 값을 속성으로 할당합니다."""
Expand Down
Loading