From 418eebaeb8eaeabfb9380237c9c9a9a47b87d948 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:21:13 +0900 Subject: [PATCH 01/12] =?UTF-8?q?refactor:=20enum=20=ED=95=98=EC=9D=B4?= =?UTF-8?q?=ED=94=88(-)=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/enum/db_key_prefix_name.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/core/enum/db_key_prefix_name.py b/app/core/enum/db_key_prefix_name.py index b10b72c..83aed3b 100644 --- a/app/core/enum/db_key_prefix_name.py +++ b/app/core/enum/db_key_prefix_name.py @@ -8,4 +8,4 @@ class DBSaveIdEnum(Enum): user_db = "USER-DB" driver = "DRIVER" api_key = "API-KEY" - chat_tab = "CHAT_TAB" \ No newline at end of file + chat_tab = "CHAT-TAB" From 132d7f52ea13184de9582632dcfeb08ad0eb4f5e Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:21:47 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20chat=5Ftab,=20message=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/core/status.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/core/status.py b/app/core/status.py index 7d68f56..953b8b4 100644 --- a/app/core/status.py +++ b/app/core/status.py @@ -28,7 +28,6 @@ class CommonCode(Enum): SUCCESS_UPDATE_PROFILE = (status.HTTP_200_OK, "2150", "디비 연결 정보를 업데이트 하였습니다.") SUCCESS_DELETE_PROFILE = (status.HTTP_200_OK, "2170", "디비 연결 정보를 삭제 하였습니다.") - """ KEY 성공 코드 - 22xx """ SUCCESS_DELETE_API_KEY = (status.HTTP_204_NO_CONTENT, "2200", "API KEY가 성공적으로 삭제되었습니다.") SUCCESS_UPDATE_API_KEY = (status.HTTP_200_OK, "2201", "API KEY가 성공적으로 수정되었습니다.") @@ -39,6 +38,7 @@ class CommonCode(Enum): SUCCESS_CHAT_TAB_UPDATE = (status.HTTP_200_OK, "2301", "채팅 탭 이름이 성공적으로 수정되었습니다.") SUCCESS_CHAT_TAB_DELETE = (status.HTTP_200_OK, "2302", "채팅 탭을 성공적으로 삭제되었습니다.") SUCCESS_GET_CHAT_TAB = (status.HTTP_200_OK, "2303", "모든 채팅 탭을 성공적으로 조회하였습니다.") + SUCCESS_GET_CHAT_MESSAGES = (status.HTTP_200_OK, "2304", "채팅 탭의 모든 메시지를 성공적으로 불러왔습니다.") """ ANNOTATION 성공 코드 - 24xx """ @@ -78,6 +78,8 @@ class CommonCode(Enum): "채팅 탭 이름에 SQL 예약어나 허용되지 않는 특수문자가 포함되어 있습니다. " "허용되지 않는 특수 문자: 큰따옴표(\"), 작은따옴표('), 세미콜론(;), 꺾쇠괄호(<, >)", ) + INVALID_CHAT_TAB_ID_FORMAT = (status.HTTP_400_BAD_REQUEST, "4303", "채팅 탭 ID의 형식이 올바르지 않습니다.") + NO_CHAT_TAB_DATA = (status.HTTP_404_NOT_FOUND, "4304", "해당 ID를 가진 채팅 탭을 찾을 수 없습니다.") """ ANNOTATION 클라이언트 에러 코드 - 44xx """ From a1b5d8df2ca7e5abf95307e2ed810166df4425f4 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:22:53 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20chat=5Ftab=5Fmessage=20api=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/chat_tab_api.py | 81 +++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 27 deletions(-) diff --git a/app/api/chat_tab_api.py b/app/api/chat_tab_api.py index e5cb938..7d57c6e 100644 --- a/app/api/chat_tab_api.py +++ b/app/api/chat_tab_api.py @@ -3,7 +3,7 @@ from app.core.response import ResponseMessage from app.core.status import CommonCode from app.schemas.chat_tab.create_model import ChatTabCreate -from app.schemas.chat_tab.response_model import ChatTabResponse +from app.schemas.chat_tab.response_model import ChatMessagesResponse, ChatTabResponse from app.schemas.chat_tab.update_model import ChatTabUpdate from app.services.chat_tab_service import ChatTabService, chat_tab_service @@ -13,9 +13,9 @@ @router.post( - "/actions", + "/create", response_model=ResponseMessage[ChatTabResponse], - summary="Chat Tab 생성", + summary="새로운 Chat Tab 생성", description="새로운 Chat Tab을 생성하여 로컬 데이터베이스에 저장합니다.", ) def store_chat_tab( @@ -34,6 +34,33 @@ def store_chat_tab( ) return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_CHAT_TAB_CREATE) + +@router.get( + "/find", + response_model=ResponseMessage[list[ChatTabResponse]], + summary="저장된 모든 Chat_tab 정보 조회", + description=""" + chat_tab 테이블에 저장된 모든 chat tab들을 확인합니다. + """, +) +def get_all_chat_tab( + service: ChatTabService = chat_tab_service_dependency, +) -> ResponseMessage[list[ChatTabResponse]]: + """저장된 모든 chat_tab의 메타데이터를 조회하여 등록 여부를 확인합니다.""" + chat_tabs_in_db = service.get_all_chat_tab() + + response_data = [ + ChatTabResponse( + id=chat_tab.id, + name=chat_tab.name, + created_at=chat_tab.created_at, + updated_at=chat_tab.updated_at, + ) + for chat_tab in chat_tabs_in_db + ] + return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_TAB) + + @router.put( "/modify/{tabId}", response_model=ResponseMessage[ChatTabResponse], @@ -42,7 +69,7 @@ def store_chat_tab( def updated_chat_tab( chatName: ChatTabUpdate, tabId: str = Path(..., description="수정할 채팅 탭의 고유 ID"), - service: ChatTabService = chat_tab_service_dependency + service: ChatTabService = chat_tab_service_dependency, ) -> ResponseMessage[ChatTabResponse]: """ 채팅 탭 ID를 기준으로 채팅 탭의 이름을 새로운 값으로 수정합니다. @@ -60,14 +87,15 @@ def updated_chat_tab( return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_CHAT_TAB_UPDATE) + @router.delete( "/remove/{tabId}", response_model=ResponseMessage, summary="특정 Chat Tab 삭제", ) def delete_chat_tab( - tabId: str = Path(..., description="수정할 채팅 탭의 고유 ID"), - service: ChatTabService = chat_tab_service_dependency + tabId: str = Path(..., description="수정할 채팅 탭의 고유 ID"), + service: ChatTabService = chat_tab_service_dependency, ) -> ResponseMessage: """ 채팅 탭 ID를 기준으로 채팅 탭을 삭제합니다. @@ -76,27 +104,26 @@ def delete_chat_tab( service.delete_chat_tab(tabId) return ResponseMessage.success(code=CommonCode.SUCCESS_CHAT_TAB_DELETE) + @router.get( - "/result", - response_model=ResponseMessage[list[ChatTabResponse]], - summary="저장된 모든 Chat_tab 정보 조회", - description=""" - chat_tab 테이블에 저장된 모든 chat tab들을 확인합니다. - """, + "/find/{tabId}/messages", + response_model=ResponseMessage[ChatMessagesResponse], + summary="특정 탭의 메시지 전체 조회", ) -def get_all_chat_tab( - service: ChatTabService = chat_tab_service_dependency, -) -> ResponseMessage[list[ChatTabResponse]]: - """저장된 모든 chat_tab의 메타데이터를 조회하여 등록 여부를 확인합니다.""" - chat_tabs_in_db = service.get_all_chat_tab() +def get_chat_messages_by_tabId( + tabId: str = Path(..., description="채팅 탭 고유 ID"), service: ChatTabService = chat_tab_service_dependency +) -> ResponseMessage[list[ChatMessagesResponse]]: + """tabId를 기준으로 해당 chat_tab의 전체 메시지를 가져옵니다.""" + chat_tab = service.get_chat_tab_by_tabId(tabId) - response_data = [ - ChatTabResponse( - id=chat_tab.id, - name=chat_tab.name, - created_at=chat_tab.created_at, - updated_at=chat_tab.updated_at, - ) - for chat_tab in chat_tabs_in_db - ] - return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_TAB) + chat_messages = service.get_chat_messages_by_tabId(tabId) + + response_data = ChatMessagesResponse( + id=chat_tab.id, + name=chat_tab.name, + created_at=chat_tab.created_at, + updated_at=chat_tab.updated_at, + messages=chat_messages, + ) + + return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_MESSAGES) From bf4755d314de6cd78bb8e87f2bd9b09a07e424ab Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:23:20 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20chat=5Fmessage=20repository=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/repository/chat_message_repository.py | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 app/repository/chat_message_repository.py diff --git a/app/repository/chat_message_repository.py b/app/repository/chat_message_repository.py new file mode 100644 index 0000000..352a0bd --- /dev/null +++ b/app/repository/chat_message_repository.py @@ -0,0 +1,34 @@ +import sqlite3 + +from app.core.utils import get_db_path +from app.schemas.chat_tab.db_model import ChatMessageInDB + + +class ChatMessageRepository: + + def get_chat_messages_by_tabId(self, id: str) -> list[ChatMessageInDB]: + """주어진 chat_tab_id에 해당하는 모든 메시지를 가져옵니다.""" + db_path = get_db_path() + conn = None + try: + conn = sqlite3.connect(str(db_path), timeout=10) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + # chat_message 테이블에서 chat_tab_id에 해당하는 모든 메시지를 조회합니다.' + # 메시지가 없을 경우, 빈 리스트를 반환합니다. + cursor.execute( + "SELECT * FROM chat_message WHERE chat_tab_id = ? ORDER BY created_at ASC", + (id,), + ) + rows = cursor.fetchall() + + # 조회된 모든 행을 ChatMessageInDB 객체 리스트로 변환 + return [ChatMessageInDB.model_validate(dict(row)) for row in rows] + + finally: + if conn: + conn.close() + + +chat_message_repository = ChatMessageRepository() From 1661b033fd1cffb4f86cb481dde39e4a13cbb42d Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:23:47 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20chat=5Ftab=20=EB=8B=A8=EC=9D=BC?= =?UTF-8?q?=20=EC=A1=B0=ED=9A=8C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/repository/chat_tab_repository.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/app/repository/chat_tab_repository.py b/app/repository/chat_tab_repository.py index 18dea38..2123b29 100644 --- a/app/repository/chat_tab_repository.py +++ b/app/repository/chat_tab_repository.py @@ -33,13 +33,14 @@ def create_chat_tab(self, new_id: str, name: str) -> ChatTabInDB: created_row = cursor.fetchone() if not created_row: - raise None + return None return ChatTabInDB.model_validate(dict(created_row)) finally: if conn: conn.close() + def updated_chat_tab(self, id: str, new_name: str | None) -> ChatTabInDB | None: """채팅 탭ID에 해당하는 ChatName를 수정하고, 수정된 객체를 반환합니다.""" db_path = get_db_path() @@ -98,6 +99,7 @@ def delete_chat_tab(self, id: str) -> bool: finally: if conn: conn.close() + def get_all_chat_tab(self) -> list[ChatTabInDB]: """데이터베이스에 저장된 모든 API Key를 조회합니다.""" db_path = get_db_path() @@ -115,4 +117,26 @@ def get_all_chat_tab(self) -> list[ChatTabInDB]: if conn: conn.close() + def get_chat_tab_by_id(self, id: str | None) -> ChatTabInDB | None: + """ID에 해당하는 채팅 탭 정보를 가져옵니다.""" + db_path = get_db_path() + conn = None + try: + conn = sqlite3.connect(str(db_path), timeout=10) + conn.row_factory = sqlite3.Row + cursor = conn.cursor() + + cursor.execute("SELECT * FROM chat_tab WHERE id = ?", (id,)) + row = cursor.fetchone() + + if not row: + return None + + return ChatTabInDB.model_validate(dict(row)) + + finally: + if conn: + conn.close() + + chat_tab_repository = ChatTabRepository() From 8ea9608df27c1a02385edf13919e5cfa36547e6b Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:24:12 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat:=20chat=5Ftab=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/services/chat_tab_service.py | 60 +++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/app/services/chat_tab_service.py b/app/services/chat_tab_service.py index 685bc93..0950cdd 100644 --- a/app/services/chat_tab_service.py +++ b/app/services/chat_tab_service.py @@ -2,22 +2,29 @@ from fastapi import Depends +from app.core.enum.db_key_prefix_name import DBSaveIdEnum from app.core.exceptions import APIException from app.core.status import CommonCode from app.core.utils import generate_prefixed_uuid +from app.repository.chat_message_repository import ChatMessageRepository, chat_message_repository from app.repository.chat_tab_repository import ChatTabRepository, chat_tab_repository from app.schemas.chat_tab.create_model import ChatTabCreate -from app.schemas.chat_tab.db_model import ChatTabInDB +from app.schemas.chat_tab.db_model import ChatMessageInDB, ChatTabInDB from app.schemas.chat_tab.update_model import ChatTabUpdate -from app.schemas.chat_tab.validation_utils import validate_chat_tab_name -from app.core.enum.db_key_prefix_name import DBSaveIdEnum +from app.schemas.chat_tab.validation_utils import validate_chat_tab_id, validate_chat_tab_name chat_tab_repository_dependency = Depends(lambda: chat_tab_repository) +chat_tab_repository_dependency = Depends(lambda: chat_tab_repository) class ChatTabService: - def __init__(self, repository: ChatTabRepository = chat_tab_repository): - self.repository = repository + def __init__( + self, + tab_repository: ChatTabRepository = chat_tab_repository, + message_repository: ChatMessageRepository = chat_message_repository, + ): + self.tab_repository = tab_repository + self.message_repository = message_repository def store_chat_tab(self, chatName: ChatTabCreate) -> ChatTabInDB: """새로운 AI 채팅을 데이터베이스에 저장합니다.""" @@ -26,7 +33,7 @@ def store_chat_tab(self, chatName: ChatTabCreate) -> ChatTabInDB: new_id = generate_prefixed_uuid(DBSaveIdEnum.chat_tab.value) try: - created_row = self.repository.create_chat_tab( + created_row = self.tab_repository.create_chat_tab( new_id=new_id, name=chatName.name, ) @@ -41,14 +48,15 @@ def store_chat_tab(self, chatName: ChatTabCreate) -> ChatTabInDB: raise APIException(CommonCode.DB_BUSY) from e # 기타 모든 sqlite3 오류 raise APIException(CommonCode.FAIL) from e + def updated_chat_tab(self, chatID: str, chatName: ChatTabUpdate) -> ChatTabInDB: """TabID에 해당하는 AIChatTab name을 수정합니다.""" validate_chat_tab_name(chatName.name) try: - updated_chat_tab = self.repository.updated_chat_tab(chatID, chatName.name) + updated_chat_tab = self.tab_repository.updated_chat_tab(chatID, chatName.name) if not updated_chat_tab: - raise APIException(CommonCode.NO_SEARCH_DATA) + raise APIException(CommonCode.NO_CHAT_TAB_DATA) return updated_chat_tab except sqlite3.Error as e: @@ -59,19 +67,45 @@ def updated_chat_tab(self, chatID: str, chatName: ChatTabUpdate) -> ChatTabInDB: def delete_chat_tab(self, tabId: str) -> None: """TabID에 해당하는 AIChatTab을 삭제합니다.""" try: - is_deleted = self.repository.delete_chat_tab(tabId) + is_deleted = self.tab_repository.delete_chat_tab(tabId) if not is_deleted: - raise APIException(CommonCode.NO_SEARCH_DATA) + raise APIException(CommonCode.NO_CHAT_TAB_DATA) except sqlite3.Error as e: if "database is locked" in str(e): raise APIException(CommonCode.DB_BUSY) from e raise APIException(CommonCode.FAIL) from e - def get_all_chat_tab(self) -> list[ChatTabInDB]: + def get_all_chat_tab(self) -> ChatTabInDB: """데이터베이스에 저장된 모든 Chat_tab을 조회합니다.""" try: - return self.repository.get_all_chat_tab() + return self.tab_repository.get_all_chat_tab() + except sqlite3.Error as e: + raise APIException(CommonCode.FAIL) from e + + def get_chat_tab_by_tabId(self, tabId: str) -> ChatTabInDB: + """데이터베이스에 저장된 특정 Chat_tab을 조회합니다.""" + validate_chat_tab_id(tabId) + + try: + chat_tab = self.tab_repository.get_chat_tab_by_id(tabId) + + if not chat_tab: + raise APIException(CommonCode.NO_CHAT_TAB_DATA) + return chat_tab + except sqlite3.Error as e: raise APIException(CommonCode.FAIL) from e - + + def get_chat_messages_by_tabId(self, tabId: str) -> ChatMessageInDB: + """ + 채팅 탭 메타데이터와 메시지 목록을 모두 가져와서 조합합니다. + 탭이 존재하지 않으면 예외를 발생시킵니다. + """ + try: + return self.message_repository.get_chat_messages_by_tabId(tabId) + + except sqlite3.Error as e: + raise APIException(CommonCode.FAIL) from e + + chat_tab_service = ChatTabService() From 7ee0f4b629304a6ed1b35657e48e522b10d635de Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:24:38 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat:=20chat=5Ftab=20=EC=8A=A4=ED=82=A4?= =?UTF-8?q?=EB=A7=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/chat_tab/db_model.py | 16 ++++++++++++++++ app/schemas/chat_tab/response_model.py | 12 ++++++++++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/app/schemas/chat_tab/db_model.py b/app/schemas/chat_tab/db_model.py index 7f3ab1d..ddd37fd 100644 --- a/app/schemas/chat_tab/db_model.py +++ b/app/schemas/chat_tab/db_model.py @@ -1,5 +1,7 @@ from datetime import datetime +from pydantic import Field + from app.schemas.chat_tab.base_model import ChatTabBase @@ -13,3 +15,17 @@ class ChatTabInDB(ChatTabBase): class Config: from_attributes = True + + +class ChatMessageInDB(ChatTabBase): + """데이터베이스에 저장된 형태의 메시지 스키마 (내부용)""" + + id: str = Field(..., description="메시지의 고유 ID (서버에서 생성)") + chat_tab_id: str = Field(..., description="해당 메시지가 속한 채팅 탭의 ID") + sender: str = Field(..., description="메시지 발신자 ('AI' 또는 'User')") + message: str = Field(..., description="메시지 내용") + created_at: datetime + updated_at: datetime + + class Config: + from_attributes = True diff --git a/app/schemas/chat_tab/response_model.py b/app/schemas/chat_tab/response_model.py index 0d2cf15..0f017d5 100644 --- a/app/schemas/chat_tab/response_model.py +++ b/app/schemas/chat_tab/response_model.py @@ -3,12 +3,20 @@ from pydantic import Field from app.schemas.chat_tab.base_model import ChatTabBase +from app.schemas.chat_tab.db_model import ChatMessageInDB class ChatTabResponse(ChatTabBase): """AI 채팅 탭 정보 API 응답용 스키마""" - id: str = Field(..., description="채팅 세션의 고유 ID (서버에서 생성)") - name: str = Field(..., description="채팅 세션의 이름") + id: str = Field(..., description="채팅 탭의 고유 ID (서버에서 생성)") + name: str = Field(..., description="채팅 탭의 이름") created_at: datetime updated_at: datetime + + +class ChatMessagesResponse(ChatTabResponse): + """AI 채팅 탭의 메타데이터와 전체 메시지 목록을 담는 API 응답 스키마""" + + # 해당 탭의 모든 메시지를 리스트로 담습니다. + messages: list[ChatMessageInDB] = Field(..., description="해당 채팅 탭에 속한 모든 메시지 목록") From 9038c99580a86339ac211d4f8153ed88d9331435 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:29:06 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20chat=5Ftab=20ID=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/chat_tab/validation_utils.py | 47 +++++++++++++++--------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/app/schemas/chat_tab/validation_utils.py b/app/schemas/chat_tab/validation_utils.py index 6d453ce..378075b 100644 --- a/app/schemas/chat_tab/validation_utils.py +++ b/app/schemas/chat_tab/validation_utils.py @@ -1,26 +1,37 @@ import re +from app.core.enum.db_key_prefix_name import DBSaveIdEnum from app.core.exceptions import APIException from app.core.status import CommonCode +# Util 폴더안 or base_model.py 안으로 이동 리팩토링 진행 예정 def validate_chat_tab_name(name: str | None) -> None: - """채팅 탭 이름에 대한 유효성 검증 로직을 수행합니다.""" - # 1. 문자열이 None, 문자열 전체가 공백 문자인지 확인 - if not name or name.isspace(): - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_FORMAT) - - # 2. 길이 제한 - if len(name) > 128: - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_LENGTH) - - # 3. 특수문자 및 SQL 예약어 확인 - # SQL 예약어와 위험한 특수문자를 검사합니다. - sql_keywords = ["SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "OR", "AND"] - for keyword in sql_keywords: - if keyword in name.upper(): - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) - - # 특정 특수문자를 검사하는 예시 - if re.search(r"[;\"'`<>]", name): + """채팅 탭 이름에 대한 유효성 검증 로직을 수행합니다.""" + # 1. 문자열이 None, 문자열 전체가 공백 문자인지 확인 + if not name or name.isspace(): + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_FORMAT) + + # 2. 길이 제한 + if len(name) > 128: + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_LENGTH) + + # 3. 특수문자 및 SQL 예약어 확인 + # SQL 예약어와 위험한 특수문자를 검사합니다. + sql_keywords = ["SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "OR", "AND"] + for keyword in sql_keywords: + if keyword in name.upper(): raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) + + # 특정 특수문자를 검사하는 예시 + if re.search(r"[;\"'`<>]", name): + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) + + +def validate_chat_tab_id(id: str | None) -> None: + """채팅 탭 ID에 대한 유효성 검증 로직을 수행합니다.""" + + # 1. 'CHAT-TAB-' 접두사 검증 + required_prefix = DBSaveIdEnum.chat_tab.value + "-" + if not id.startswith(required_prefix): + raise APIException(CommonCode.INVALID_CHAT_TAB_ID_FORMAT) From 19ed9c678b9e859b18e1490c1bb8208ad4ee46bf Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 01:31:46 +0900 Subject: [PATCH 09/12] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=20=EC=9E=90=EB=8F=99=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/user_db_api.py | 47 +++++++++-------------- app/core/all_logging.py | 4 +- app/core/utils.py | 4 +- app/repository/chat_message_repository.py | 1 - app/repository/chat_tab_repository.py | 1 - app/schemas/chat_tab/create_model.py | 1 - app/schemas/chat_tab/update_model.py | 1 + app/schemas/user_db/db_profile_model.py | 3 ++ app/schemas/user_db/result_model.py | 32 +++++++++++---- 9 files changed, 52 insertions(+), 42 deletions(-) diff --git a/app/api/user_db_api.py b/app/api/user_db_api.py index 81f6dbd..957d431 100644 --- a/app/api/user_db_api.py +++ b/app/api/user_db_api.py @@ -1,13 +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 DBProfileInfo, UpdateOrCreateDBProfile +from app.schemas.user_db.result_model import ColumnInfo, DBProfile 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) @@ -23,7 +23,6 @@ def connection_test( db_info: DBProfileInfo, service: UserDbService = user_db_service_dependency, ) -> ResponseMessage[bool]: - db_info.validate_required_fields() result = service.connection_test(db_info) @@ -31,6 +30,7 @@ def connection_test( raise APIException(result.code) return ResponseMessage.success(value=result.is_successful, code=result.code) + @router.post( "/create/profile", response_model=ResponseMessage[str], @@ -40,7 +40,6 @@ 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) @@ -48,6 +47,7 @@ def create_profile( raise APIException(result.code) return ResponseMessage.success(value=result.view_name, code=result.code) + @router.put( "/modify/profile", response_model=ResponseMessage[str], @@ -57,7 +57,6 @@ 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) @@ -65,6 +64,7 @@ def update_profile( raise APIException(result.code) return ResponseMessage.success(value=result.view_name, code=result.code) + @router.delete( "/remove/{profile_id}", response_model=ResponseMessage[str], @@ -74,38 +74,34 @@ 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]], + response_model=ResponseMessage[list[DBProfile]], summary="DB 프로필 전체 조회", ) def find_all_profile( service: UserDbService = user_db_service_dependency, -) -> ResponseMessage[List[DBProfile]]: - +) -> 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]], + response_model=ResponseMessage[list[str]], summary="특정 DB의 전체 스키마 조회", ) -def find_schemas( - profile_id: str, - service: UserDbService = user_db_service_dependency -) -> ResponseMessage[List[str]]: - +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) @@ -113,17 +109,15 @@ def find_schemas( 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]], + response_model=ResponseMessage[list[str]], summary="특정 스키마의 전체 테이블 조회", ) def find_tables( - profile_id: str, - schema_name: str, - service: UserDbService = user_db_service_dependency -) -> ResponseMessage[List[str]]: - + 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) @@ -131,18 +125,15 @@ def find_tables( 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]], + 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]]: - + 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) diff --git a/app/core/all_logging.py b/app/core/all_logging.py index c31d4eb..796a01f 100644 --- a/app/core/all_logging.py +++ b/app/core/all_logging.py @@ -1,12 +1,13 @@ # 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)을 포함 + format="%(asctime)s - %(levelname)s - %(message)s", # [수정] 로그 레벨(INFO, ERROR)을 포함 datefmt="%Y-%m-%d %H:%M:%S", ) @@ -30,4 +31,3 @@ async def log_requests_middleware(request: Request, call_next): logging.error(f"ERROR 엔드포인트: {endpoint}", exc_info=True) # 예외를 다시 발생시켜 FastAPI의 전역 예외 처리기가 최종 응답을 만들도록 합니다. raise e - diff --git a/app/core/utils.py b/app/core/utils.py index a4f7278..571817a 100644 --- a/app/core/utils.py +++ b/app/core/utils.py @@ -4,6 +4,7 @@ # 앱 데이터를 저장할 폴더 이름 APP_DATA_DIR_NAME = ".qgenie" + def get_db_path() -> Path: """ 사용자 홈 디렉터리 내에 앱 데이터 폴더를 만들고, @@ -15,9 +16,10 @@ def get_db_path() -> Path: db_path = app_data_dir / "local_storage.sqlite" return db_path + def generate_uuid() -> str: return uuid.uuid4().hex.upper() + def generate_prefixed_uuid(prefix: str) -> str: return f"{prefix.upper()}-{uuid.uuid4().hex.upper()}" - diff --git a/app/repository/chat_message_repository.py b/app/repository/chat_message_repository.py index 352a0bd..6e1c67f 100644 --- a/app/repository/chat_message_repository.py +++ b/app/repository/chat_message_repository.py @@ -5,7 +5,6 @@ class ChatMessageRepository: - def get_chat_messages_by_tabId(self, id: str) -> list[ChatMessageInDB]: """주어진 chat_tab_id에 해당하는 모든 메시지를 가져옵니다.""" db_path = get_db_path() diff --git a/app/repository/chat_tab_repository.py b/app/repository/chat_tab_repository.py index 2123b29..eb732d6 100644 --- a/app/repository/chat_tab_repository.py +++ b/app/repository/chat_tab_repository.py @@ -5,7 +5,6 @@ class ChatTabRepository: - def create_chat_tab(self, new_id: str, name: str) -> ChatTabInDB: """ 새로운 채팅 탭 이름을 데이터베이스에 저장하고, 저장된 객체를 반환합니다. diff --git a/app/schemas/chat_tab/create_model.py b/app/schemas/chat_tab/create_model.py index f5be4d8..31104cd 100644 --- a/app/schemas/chat_tab/create_model.py +++ b/app/schemas/chat_tab/create_model.py @@ -1,4 +1,3 @@ - from app.schemas.chat_tab.base_model import ChatTabBase from app.schemas.chat_tab.validation_utils import validate_chat_tab_name diff --git a/app/schemas/chat_tab/update_model.py b/app/schemas/chat_tab/update_model.py index 4227463..3236c71 100644 --- a/app/schemas/chat_tab/update_model.py +++ b/app/schemas/chat_tab/update_model.py @@ -6,6 +6,7 @@ class ChatTabUpdate(ChatTabBase): """채팅 탭 이름 수정을 위한 스키마""" + name: str | None = Field(None, description="수정할 채팅 탭 이름") def validate_with_name(self) -> None: diff --git a/app/schemas/user_db/db_profile_model.py b/app/schemas/user_db/db_profile_model.py index f6d601f..49598d8 100644 --- a/app/schemas/user_db/db_profile_model.py +++ b/app/schemas/user_db/db_profile_model.py @@ -17,6 +17,7 @@ class DBProfileInfo(BaseModel): name: str | None = Field(None, description="연결할 데이터베이스명") username: str | None = Field(None, description="사용자 이름") password: str | None = Field(None, description="비밀번호") + def validate_required_fields(self) -> None: """DB 종류별 필수 필드 유효성 검사""" required_fields_by_type = { @@ -52,10 +53,12 @@ def _is_empty(value: Any | None) -> bool: return True return False + class UpdateOrCreateDBProfile(DBProfileInfo): id: str | None = Field(None, description="DB Key 값") view_name: str | None = Field(None, description="DB 노출명") + class AllDBProfileInfo(DBProfileInfo): id: str | None = Field(..., description="DB Key 값") view_name: str | None = Field(None, description="DB 노출명") diff --git a/app/schemas/user_db/result_model.py b/app/schemas/user_db/result_model.py index 28c375e..ea8ad73 100644 --- a/app/schemas/user_db/result_model.py +++ b/app/schemas/user_db/result_model.py @@ -1,21 +1,26 @@ # app/schemas/user_db/result_model.py -from pydantic import BaseModel, Field from datetime import datetime -from typing import List, Any +from typing import Any + +from pydantic import BaseModel, Field from app.core.status import CommonCode + # 기본 반환 모델 class BasicResult(BaseModel): is_successful: bool = Field(..., description="성공 여부") code: CommonCode = Field(None, description="결과 코드") + # 디비 정보 후 반환되는 저장 모델 class ChangeProfileResult(BasicResult): """DB 조회 결과를 위한 확장 모델""" + view_name: str = Field(..., description="저장된 디비명") + # DB Profile 조회되는 정보를 담는 모델입니다. class DBProfile(BaseModel): id: str @@ -31,13 +36,17 @@ class DBProfile(BaseModel): class Config: from_attributes = True + # DB Profile 전체 조회 결과를 담는 새로운 모델 class AllDBProfileResult(BasicResult): """DB 프로필 전체 조회 결과를 위한 확장 모델""" - profiles: List[DBProfile] = Field([], description="DB 프로필 목록") + + profiles: list[DBProfile] = Field([], description="DB 프로필 목록") + class ColumnInfo(BaseModel): """단일 컬럼의 상세 정보를 담는 모델""" + name: str = Field(..., description="컬럼 이름") type: str = Field(..., description="데이터 타입") nullable: bool = Field(..., description="NULL 허용 여부") @@ -45,21 +54,28 @@ class ColumnInfo(BaseModel): comment: str | None = Field(None, description="코멘트") is_pk: bool = Field(False, description="기본 키(Primary Key) 여부") + class TableInfo(BaseModel): """단일 테이블의 이름과 컬럼 목록을 담는 모델""" + name: str = Field(..., description="테이블 이름") - columns: List[ColumnInfo] = Field([], description="컬럼 목록") + columns: list[ColumnInfo] = Field([], description="컬럼 목록") comment: str | None = Field(None, description="테이블 코멘트") + class SchemaInfoResult(BasicResult): """DB 스키마 상세 정보 조회 결과를 위한 확장 모델""" - schema: List[TableInfo] = Field([], description="테이블 및 컬럼 정보 목록") + + schema: list[TableInfo] = Field([], description="테이블 및 컬럼 정보 목록") + class SchemaListResult(BasicResult): - schemas: List[str] = Field([], description="스키마 이름 목록") + schemas: list[str] = Field([], description="스키마 이름 목록") + class TableListResult(BasicResult): - tables: List[str] = Field([], description="테이블 이름 목록") + tables: list[str] = Field([], description="테이블 이름 목록") + class ColumnListResult(BasicResult): - columns: List[ColumnInfo] = Field([], description="컬럼 정보 목록") + columns: list[ColumnInfo] = Field([], description="컬럼 정보 목록") From 667553e233f293a658fa1ee62502765e5e8ab725 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 13:52:33 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20api=20=EC=97=94=EB=93=9C?= =?UTF-8?q?=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/chat_tab_api.py | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/app/api/chat_tab_api.py b/app/api/chat_tab_api.py index 7d57c6e..8d3cf04 100644 --- a/app/api/chat_tab_api.py +++ b/app/api/chat_tab_api.py @@ -61,6 +61,30 @@ def get_all_chat_tab( return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_TAB) +@router.get( + "/find/{tabId}", + response_model=ResponseMessage[ChatMessagesResponse], + summary="특정 탭의 메시지 전체 조회", +) +def get_chat_messages_by_tabId( + tabId: str = Path(..., description="채팅 탭 고유 ID"), service: ChatTabService = chat_tab_service_dependency +) -> ResponseMessage[list[ChatMessagesResponse]]: + """tabId를 기준으로 해당 chat_tab의 전체 메시지를 가져옵니다.""" + chat_tab = service.get_chat_tab_by_tabId(tabId) + + chat_messages = service.get_chat_messages_by_tabId(tabId) + + response_data = ChatMessagesResponse( + id=chat_tab.id, + name=chat_tab.name, + created_at=chat_tab.created_at, + updated_at=chat_tab.updated_at, + messages=chat_messages, + ) + + return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_MESSAGES) + + @router.put( "/modify/{tabId}", response_model=ResponseMessage[ChatTabResponse], @@ -103,27 +127,3 @@ def delete_chat_tab( """ service.delete_chat_tab(tabId) return ResponseMessage.success(code=CommonCode.SUCCESS_CHAT_TAB_DELETE) - - -@router.get( - "/find/{tabId}/messages", - response_model=ResponseMessage[ChatMessagesResponse], - summary="특정 탭의 메시지 전체 조회", -) -def get_chat_messages_by_tabId( - tabId: str = Path(..., description="채팅 탭 고유 ID"), service: ChatTabService = chat_tab_service_dependency -) -> ResponseMessage[list[ChatMessagesResponse]]: - """tabId를 기준으로 해당 chat_tab의 전체 메시지를 가져옵니다.""" - chat_tab = service.get_chat_tab_by_tabId(tabId) - - chat_messages = service.get_chat_messages_by_tabId(tabId) - - response_data = ChatMessagesResponse( - id=chat_tab.id, - name=chat_tab.name, - created_at=chat_tab.created_at, - updated_at=chat_tab.updated_at, - messages=chat_messages, - ) - - return ResponseMessage.success(value=response_data, code=CommonCode.SUCCESS_GET_CHAT_MESSAGES) From 9007dc7560c30309d76b2f9daf93d248676ff692 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 16:18:58 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=95=88?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EC=8A=A4=ED=82=A4=EB=A7=88=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EB=B0=8F=20=EC=8B=A4=EC=A0=9C=20=ED=98=B8=EC=B6=9C?= =?UTF-8?q?=EC=9D=B4=20=EC=95=88=EB=90=98=EB=8D=98=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/api/chat_tab_api.py | 4 ++-- app/schemas/chat_tab/create_model.py | 9 --------- app/schemas/chat_tab/update_model.py | 6 +----- app/services/chat_tab_service.py | 11 ++++++----- 4 files changed, 9 insertions(+), 21 deletions(-) delete mode 100644 app/schemas/chat_tab/create_model.py diff --git a/app/api/chat_tab_api.py b/app/api/chat_tab_api.py index 8d3cf04..a29af95 100644 --- a/app/api/chat_tab_api.py +++ b/app/api/chat_tab_api.py @@ -2,7 +2,7 @@ from app.core.response import ResponseMessage from app.core.status import CommonCode -from app.schemas.chat_tab.create_model import ChatTabCreate +from app.schemas.chat_tab.base_model import ChatTabBase from app.schemas.chat_tab.response_model import ChatMessagesResponse, ChatTabResponse from app.schemas.chat_tab.update_model import ChatTabUpdate from app.services.chat_tab_service import ChatTabService, chat_tab_service @@ -19,7 +19,7 @@ description="새로운 Chat Tab을 생성하여 로컬 데이터베이스에 저장합니다.", ) def store_chat_tab( - chatName: ChatTabCreate, service: ChatTabService = chat_tab_service_dependency + chatName: ChatTabBase, service: ChatTabService = chat_tab_service_dependency ) -> ResponseMessage[ChatTabResponse]: """ - **name**: 새로운 Chat_tab 이름 (예: "채팅 타이틀") diff --git a/app/schemas/chat_tab/create_model.py b/app/schemas/chat_tab/create_model.py deleted file mode 100644 index 31104cd..0000000 --- a/app/schemas/chat_tab/create_model.py +++ /dev/null @@ -1,9 +0,0 @@ -from app.schemas.chat_tab.base_model import ChatTabBase -from app.schemas.chat_tab.validation_utils import validate_chat_tab_name - - -class ChatTabCreate(ChatTabBase): - """새로운 Chat Tab 생성을 위한 스키마""" - - def validate_with_name(self) -> None: - validate_chat_tab_name(self.name) diff --git a/app/schemas/chat_tab/update_model.py b/app/schemas/chat_tab/update_model.py index 3236c71..e9ca291 100644 --- a/app/schemas/chat_tab/update_model.py +++ b/app/schemas/chat_tab/update_model.py @@ -1,13 +1,9 @@ from pydantic import Field from app.schemas.chat_tab.base_model import ChatTabBase -from app.schemas.chat_tab.validation_utils import validate_chat_tab_name class ChatTabUpdate(ChatTabBase): """채팅 탭 이름 수정을 위한 스키마""" - name: str | None = Field(None, description="수정할 채팅 탭 이름") - - def validate_with_name(self) -> None: - validate_chat_tab_name(self.name) + name: str = Field(None, description="수정할 채팅 탭 이름") diff --git a/app/services/chat_tab_service.py b/app/services/chat_tab_service.py index 0950cdd..c7a3f25 100644 --- a/app/services/chat_tab_service.py +++ b/app/services/chat_tab_service.py @@ -8,10 +8,10 @@ from app.core.utils import generate_prefixed_uuid from app.repository.chat_message_repository import ChatMessageRepository, chat_message_repository from app.repository.chat_tab_repository import ChatTabRepository, chat_tab_repository -from app.schemas.chat_tab.create_model import ChatTabCreate +from app.schemas.chat_tab.base_model import ChatTabBase from app.schemas.chat_tab.db_model import ChatMessageInDB, ChatTabInDB from app.schemas.chat_tab.update_model import ChatTabUpdate -from app.schemas.chat_tab.validation_utils import validate_chat_tab_id, validate_chat_tab_name +from app.schemas.chat_tab.validation_utils import validate_chat_tab_id # 삭제 예정 chat_tab_repository_dependency = Depends(lambda: chat_tab_repository) chat_tab_repository_dependency = Depends(lambda: chat_tab_repository) @@ -26,9 +26,9 @@ def __init__( self.tab_repository = tab_repository self.message_repository = message_repository - def store_chat_tab(self, chatName: ChatTabCreate) -> ChatTabInDB: + def store_chat_tab(self, chatName: ChatTabBase) -> ChatTabInDB: """새로운 AI 채팅을 데이터베이스에 저장합니다.""" - validate_chat_tab_name(chatName.name) + chatName.validate_chat_tab_name() new_id = generate_prefixed_uuid(DBSaveIdEnum.chat_tab.value) @@ -51,7 +51,7 @@ def store_chat_tab(self, chatName: ChatTabCreate) -> ChatTabInDB: def updated_chat_tab(self, chatID: str, chatName: ChatTabUpdate) -> ChatTabInDB: """TabID에 해당하는 AIChatTab name을 수정합니다.""" - validate_chat_tab_name(chatName.name) + chatName.validate_chat_tab_name() try: updated_chat_tab = self.tab_repository.updated_chat_tab(chatID, chatName.name) @@ -84,6 +84,7 @@ def get_all_chat_tab(self) -> ChatTabInDB: def get_chat_tab_by_tabId(self, tabId: str) -> ChatTabInDB: """데이터베이스에 저장된 특정 Chat_tab을 조회합니다.""" + # 리팩토링 예정 validate_chat_tab_id(tabId) try: From 77efca3e794995bfad8cab253a863a628b7e4a95 Mon Sep 17 00:00:00 2001 From: mini Date: Wed, 13 Aug 2025 16:19:41 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20chat=5Ftab=5Fname=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=A6=9D=20basemodel=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/schemas/chat_tab/base_model.py | 34 ++++++++++++++++++++++-- app/schemas/chat_tab/validation_utils.py | 26 +----------------- 2 files changed, 33 insertions(+), 27 deletions(-) diff --git a/app/schemas/chat_tab/base_model.py b/app/schemas/chat_tab/base_model.py index 84d600b..9c4e9c8 100644 --- a/app/schemas/chat_tab/base_model.py +++ b/app/schemas/chat_tab/base_model.py @@ -1,7 +1,37 @@ +import re + from pydantic import BaseModel, Field +from app.core.exceptions import APIException +from app.core.status import CommonCode + class ChatTabBase(BaseModel): - """모든 AI Chat Tab 스키마의 기본 모델""" + """ + 모든 AI Chat Tab 스키마의 기본 모델 + - 새로운 Chat Tab 생성을 위한 스키마 + - 채팅 탭 이름 수정을 위한 스키마 + """ + + name: str | None = Field(..., description="새로운 채팅 탭 이름") + + def validate_chat_tab_name(self) -> None: + """채팅 탭 이름에 대한 유효성 검증 로직을 수행합니다.""" + # 1. 문자열이 None, 문자열 전체가 공백 문자인지 확인 + if not self.name or self.name.isspace(): + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_FORMAT) + + # 2. 길이 제한 + if len(self.name) > 128: + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_LENGTH) + + # 3. 특수문자 및 SQL 예약어 확인 + # SQL 예약어와 위험한 특수문자를 검사합니다. + sql_keywords = ["SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "OR", "AND"] + for keyword in sql_keywords: + if keyword in self.name.upper(): + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) - name: str = Field(..., description="새로운 채팅 탭 이름") + # 특정 특수문자를 검사하는 예시 + if re.search(r"[;\"'`<>]", self.name): + raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) diff --git a/app/schemas/chat_tab/validation_utils.py b/app/schemas/chat_tab/validation_utils.py index 378075b..82470df 100644 --- a/app/schemas/chat_tab/validation_utils.py +++ b/app/schemas/chat_tab/validation_utils.py @@ -1,33 +1,9 @@ -import re - from app.core.enum.db_key_prefix_name import DBSaveIdEnum from app.core.exceptions import APIException from app.core.status import CommonCode -# Util 폴더안 or base_model.py 안으로 이동 리팩토링 진행 예정 -def validate_chat_tab_name(name: str | None) -> None: - """채팅 탭 이름에 대한 유효성 검증 로직을 수행합니다.""" - # 1. 문자열이 None, 문자열 전체가 공백 문자인지 확인 - if not name or name.isspace(): - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_FORMAT) - - # 2. 길이 제한 - if len(name) > 128: - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_LENGTH) - - # 3. 특수문자 및 SQL 예약어 확인 - # SQL 예약어와 위험한 특수문자를 검사합니다. - sql_keywords = ["SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "OR", "AND"] - for keyword in sql_keywords: - if keyword in name.upper(): - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) - - # 특정 특수문자를 검사하는 예시 - if re.search(r"[;\"'`<>]", name): - raise APIException(CommonCode.INVALID_CHAT_TAB_NAME_CONTENT) - - +# 리팩토링 예정 def validate_chat_tab_id(id: str | None) -> None: """채팅 탭 ID에 대한 유효성 검증 로직을 수행합니다."""