From 47413eaa2033a16b383855875bc3983497290c6b Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Wed, 17 Dec 2025 17:28:45 +0900 Subject: [PATCH 01/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20env=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=20https://github.com/Team-Romi/chatbot/issue?= =?UTF-8?q?s/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/infra/config/__init__.py | 0 app/common/infra/config/settings.py | 31 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/common/infra/config/__init__.py create mode 100644 app/common/infra/config/settings.py diff --git a/app/common/infra/config/__init__.py b/app/common/infra/config/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/common/infra/config/settings.py b/app/common/infra/config/settings.py new file mode 100644 index 0000000..a328435 --- /dev/null +++ b/app/common/infra/config/settings.py @@ -0,0 +1,31 @@ +from anyio.functools import lru_cache +from pydantic.v1 import BaseSettings, Field + + +class Settings(BaseSettings): + """ + .env 에서 환경변수 로딩 + """ + + github_api_base_url: str = Field(alias="GITHUB_API_BASE_URL") + + ollama_base_url: str = Field(alias="OLLAMA_BASE_URL") + ollama_api_key: str = Field(alias="OLLAMA_API_KEY") + ollama_model: str = Field(alias="OLLAMA_MODEL") + ollama_timeout_seconds: int = Field(alias="OLLAMA_TIMEOUT_SECONDS") + + qdrant_base_url: str = Field(alias="QDRANT_BASE_URL") + qdrant_collection: str = Field(alias="QDRANT_COLLECTION") + qdrant_api_key: str = Field(alias="QDRANT_API_KEY") + + text_chunk_max_chars: int = Field(alias="TEXT_CHUNK_MAX_CHARS") + text_chunk_overlap_chars: int = Field(alias="TEXT_CHUNK_OVERLAP_CHARS") + text_chunk_hard_max_chars: int = Field(alias="TEXT_CHUNK_HARD_MAX_CHARS") + + concurrency_embedding_max_concurrency: int = Field(alias="CONCURRENCY_EMBEDDING_MAX_CONCURRENCY") + + postgres_url: str = Field(alias="POSTGRES_URL") + +@lru_cache(maxsize=1) +def get_settings() -> Settings: + return Settings \ No newline at end of file From 45655d039c40ce47b1464938a6710adb3dc4773b Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:14:32 +0900 Subject: [PATCH 02/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20env=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85=EC=9D=84=20=EC=9C=84=ED=95=9C=20settings.py?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20https://github.com/Team-Romi/chatbot/is?= =?UTF-8?q?sues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/common/infra/config/__init__.py | 0 app/common/infra/config/settings.py | 31 ------------------- app/config/settings.py | 46 ++++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 32 deletions(-) delete mode 100644 app/common/infra/config/__init__.py delete mode 100644 app/common/infra/config/settings.py diff --git a/app/common/infra/config/__init__.py b/app/common/infra/config/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/common/infra/config/settings.py b/app/common/infra/config/settings.py deleted file mode 100644 index a328435..0000000 --- a/app/common/infra/config/settings.py +++ /dev/null @@ -1,31 +0,0 @@ -from anyio.functools import lru_cache -from pydantic.v1 import BaseSettings, Field - - -class Settings(BaseSettings): - """ - .env 에서 환경변수 로딩 - """ - - github_api_base_url: str = Field(alias="GITHUB_API_BASE_URL") - - ollama_base_url: str = Field(alias="OLLAMA_BASE_URL") - ollama_api_key: str = Field(alias="OLLAMA_API_KEY") - ollama_model: str = Field(alias="OLLAMA_MODEL") - ollama_timeout_seconds: int = Field(alias="OLLAMA_TIMEOUT_SECONDS") - - qdrant_base_url: str = Field(alias="QDRANT_BASE_URL") - qdrant_collection: str = Field(alias="QDRANT_COLLECTION") - qdrant_api_key: str = Field(alias="QDRANT_API_KEY") - - text_chunk_max_chars: int = Field(alias="TEXT_CHUNK_MAX_CHARS") - text_chunk_overlap_chars: int = Field(alias="TEXT_CHUNK_OVERLAP_CHARS") - text_chunk_hard_max_chars: int = Field(alias="TEXT_CHUNK_HARD_MAX_CHARS") - - concurrency_embedding_max_concurrency: int = Field(alias="CONCURRENCY_EMBEDDING_MAX_CONCURRENCY") - - postgres_url: str = Field(alias="POSTGRES_URL") - -@lru_cache(maxsize=1) -def get_settings() -> Settings: - return Settings \ No newline at end of file diff --git a/app/config/settings.py b/app/config/settings.py index c17f4ec..6e39fc3 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -1,2 +1,46 @@ -# 빈 파일 - 환경 변수 설정 +from functools import lru_cache +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + """ + .env 에서 환경변수 로딩 + """ + + model_config = SettingsConfigDict( + env_file=".env", + env_file_encoding="utf-8", + case_sensitive=False, + extra="ignore" + ) + + # GitHub API 설정 + github_api_base_url: str + + # Ollama 설정 + ollama_base_url: str + ollama_api_key: str + ollama_model: str + ollama_timeout_seconds: int + + # Qdrant 설정 + qdrant_base_url: str + qdrant_collection: str + qdrant_api_key: str + + # 텍스트 청크 설정 + text_chunk_max_chars: int + text_chunk_overlap_chars: int + text_chunk_hard_max_chars: int + + # 동시성 설정 + concurrency_embedding_max_concurrency: int + + # PostgreSQL 설정 + postgres_url: str + + +@lru_cache(maxsize=1) +def get_settings() -> Settings: + return Settings() From 280c968baeddf9e96d477009fc1c2c9dc630c7f8 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:15:03 +0900 Subject: [PATCH 03/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20postgresql=20DB=20=EC=84=B8=EC=85=98=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20https://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/database.py | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/config/database.py b/app/config/database.py index 80d75cc..a840f5a 100644 --- a/app/config/database.py +++ b/app/config/database.py @@ -1,2 +1,27 @@ -# 빈 파일 - DB 연결 설정 +from typing import AsyncGenerator +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine + +from app.config.settings import get_settings + +_settings = get_settings() + +_engine = create_async_engine( + _settings.postgres_url, + pool_pre_ping=True, +) + +_async_session_factory = async_sessionmaker( + bind=_engine, + expire_on_commit=False, + autoflush=False, +) + + +def get_async_session_factory() -> async_sessionmaker[AsyncSession]: + return _async_session_factory + + +async def get_async_session() -> AsyncGenerator[AsyncSession, None]: + async with _async_session_factory() as session: + yield session From 5beeb5c065589518d0ced8d7820a86c59cb4e812 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:15:22 +0900 Subject: [PATCH 04/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20source=5Ftype=20enum=20=EC=B6=94=EA=B0=80=20http?= =?UTF-8?q?s://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/enums/__init__.py | 0 app/models/enums/source_type.py | 17 +++++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 app/models/enums/__init__.py create mode 100644 app/models/enums/source_type.py diff --git a/app/models/enums/__init__.py b/app/models/enums/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/models/enums/source_type.py b/app/models/enums/source_type.py new file mode 100644 index 0000000..9501338 --- /dev/null +++ b/app/models/enums/source_type.py @@ -0,0 +1,17 @@ +from enum import Enum + + +class SourceType(str, Enum): + """ + 깃허브 임베딩 대상 SourceType + - Repository: 레포 파일/문서 (README 등) + - ISSUE: 이슈 + - PULL_REQUEST: PR + - COMMIT: 커밋 + - RELEASE: 릴리즈 + """ + REPOSITORY = "REPOSITORY" + ISSUE = "ISSUE" + PULL_REQUEST = "PULL_REQUEST" + COMMIT = "COMMIT" + RELEASE = "RELEASE" From 5cb7cef0d4aa0de1f7d440861e86e6b4deb2fa23 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:15:46 +0900 Subject: [PATCH 05/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20GithubCursorEntity=20=EC=B6=94=EA=B0=80=20https:?= =?UTF-8?q?//github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/base.py | 9 +++++++++ app/models/github_cursor.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 app/models/base.py create mode 100644 app/models/github_cursor.py diff --git a/app/models/base.py b/app/models/base.py new file mode 100644 index 0000000..8300947 --- /dev/null +++ b/app/models/base.py @@ -0,0 +1,9 @@ +from sqlalchemy.orm import DeclarativeBase + + +class Base(DeclarativeBase): + """ + SQLAlchemy Declarative Base + - 모든 엔티티는 Base 상속 + """ + pass \ No newline at end of file diff --git a/app/models/github_cursor.py b/app/models/github_cursor.py new file mode 100644 index 0000000..ff20d32 --- /dev/null +++ b/app/models/github_cursor.py @@ -0,0 +1,29 @@ +import uuid +from datetime import datetime + +from sqlalchemy import Enum as SqlEnum +from sqlalchemy import UniqueConstraint, Index, String, DateTime, func +from sqlalchemy.dialects.postgresql.base import UUID +from sqlalchemy.orm import Mapped, mapped_column + +from app.models.base import Base +from app.models.enums.source_type import SourceType + + +class GithubCursorEntity(Base): + __tablename__ = "github_cursor" + __table_args__ = ( + UniqueConstraint("repository_name", "source_type", name="uq_github_cursor"), + Index("idx_github_cursor_repo_type", "repository_name", "source_type") + ) + + id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + + repository_name: Mapped[str] = mapped_column(String(200), nullable=False) + + source_type: Mapped[SourceType] = mapped_column(SqlEnum(SourceType, native_enum=False), nullable=False) + + cursor_value: Mapped[str] = mapped_column(String(500), nullable=False) + + created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) + updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()) From a7fec07dc4d0236a03ce4696d06a046ba2f5b962 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:16:19 +0900 Subject: [PATCH 06/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20github=5Fcursor=5Frepository=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20https://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/repositories/github_cursor_repository.py | 45 ++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 app/repositories/github_cursor_repository.py diff --git a/app/repositories/github_cursor_repository.py b/app/repositories/github_cursor_repository.py new file mode 100644 index 0000000..740a027 --- /dev/null +++ b/app/repositories/github_cursor_repository.py @@ -0,0 +1,45 @@ +import uuid +from typing import Optional + +from sqlalchemy import select, func +from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.ext.asyncio import AsyncSession + +from app.models.enums.source_type import SourceType +from app.models.github_cursor import GithubCursorEntity + + +class GithubCursorRepository: + async def find_by_repository_name_and_source_type( + self, + session: AsyncSession, + repository_name: str, + source_type: SourceType, + ) -> Optional[GithubCursorEntity]: + query = select(GithubCursorEntity).where( + GithubCursorEntity.repository_name == repository_name, + GithubCursorEntity.source_type == source_type, + ) + result = await session.execute(query) + return result.scalar_one_or_none() + + async def upsert( + self, + session: AsyncSession, + repository_name: str, + source_type: SourceType, + cursor_value: str, + ) -> None: + query = insert(GithubCursorEntity).values( + id=uuid.uuid4(), + repository_name=repository_name, + source_type=source_type, + cursor_value=cursor_value, + ).on_conflict_do_update( + index_elements=["repository_name", "source_type"], + set_={ + "cursor_value": cursor_value, + "updated_at": func.now(), + }, + ) + await session.execute(query) From 8e26b45ca51661c61f8665551d8620b8cc14232b Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:16:51 +0900 Subject: [PATCH 07/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20github=5Fcursor=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?flyway=20migration=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1=20?= =?UTF-8?q?https://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/github_issue.py | 2 -- ...251222_141233__create_github_cursor_table.sql | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) delete mode 100644 app/models/github_issue.py create mode 100644 sql/V20251222_141233__create_github_cursor_table.sql diff --git a/app/models/github_issue.py b/app/models/github_issue.py deleted file mode 100644 index 4a402cb..0000000 --- a/app/models/github_issue.py +++ /dev/null @@ -1,2 +0,0 @@ -# 빈 파일 - GitHub 이슈 모델 - diff --git a/sql/V20251222_141233__create_github_cursor_table.sql b/sql/V20251222_141233__create_github_cursor_table.sql new file mode 100644 index 0000000..4e52192 --- /dev/null +++ b/sql/V20251222_141233__create_github_cursor_table.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS github_cursor ( + id UUID PRIMARY KEY, + repository_name VARCHAR(200) NOT NULL, + source_type VARCHAR(50) NOT NULL, + cursor_value VARCHAR(500) NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + CONSTRAINT uq_github_cursor UNIQUE (repository_name, source_type), + + CONSTRAINT ck_github_cursor_source_type + CHECK (source_type IN ('REPOSITORY', 'ISSUE', 'PULL_REQUEST', 'COMMIT', 'RELEASE')) +); + +-- 인덱스 생성 +CREATE INDEX IF NOT EXISTS idx_github_cursor_repo_type + ON github_cursor (repository_name, source_type); \ No newline at end of file From 6ec67cd1c1cedec68c12bbfdfa5168ec814403a0 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:17:24 +0900 Subject: [PATCH 08/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20requirements.txt=20=EC=9D=98=EC=A1=B4=EC=84=B1?= =?UTF-8?q?=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20https://github.com/Te?= =?UTF-8?q?am-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 3e3d573..6e1246b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,13 +1,45 @@ +alembic==1.17.2 annotated-doc==0.0.4 annotated-types==0.7.0 anyio==4.12.0 +asyncpg==0.31.0 +certifi==2025.11.12 +cffi==2.0.0 +charset-normalizer==3.4.4 click==8.3.1 -fastapi==0.124.4 +cryptography==46.0.3 +Deprecated==1.3.1 +fastapi==0.127.0 +greenlet==3.3.0 +grpcio==1.76.0 +grpcio-tools==1.76.0 h11==0.16.0 +h2==4.3.0 +hpack==4.1.0 +httpcore==1.0.9 +httpx==0.28.1 +hyperframe==6.1.0 idna==3.11 +Mako==1.3.10 +MarkupSafe==3.0.3 +numpy==2.4.0 +portalocker==3.2.0 +protobuf==6.33.2 +pycparser==2.23 pydantic==2.12.5 +pydantic-settings==2.12.0 pydantic_core==2.41.5 +PyGithub==2.8.1 +PyJWT==2.10.1 +PyNaCl==1.6.1 +python-dotenv==1.2.1 +qdrant-client==1.16.2 +requests==2.32.5 +setuptools==80.9.0 +SQLAlchemy==2.0.45 starlette==0.50.0 typing-inspection==0.4.2 typing_extensions==4.15.0 -uvicorn==0.38.0 +urllib3==2.6.2 +uvicorn==0.40.0 +wrapt==2.0.1 From 3b3d060d6549a61413e387f50e8e84b0016fec4f Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:19:05 +0900 Subject: [PATCH 09/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20coderabbitai.yaml=20=EC=B6=94=EA=B0=80=20https:/?= =?UTF-8?q?/github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .coderabbit.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .coderabbit.yaml diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..62ed085 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,18 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: "ko-KR" +early_access: false +reviews: + profile: "chill" + request_changes_workflow: false + high_level_summary: true + poem: true + review_status: true + collapse_walkthrough: false + auto_review: + enabled: true + drafts: false + base_branches: + - main + - test +chat: + auto_reply: true \ No newline at end of file From c7e3c0e31c908716ddb9a909a5ad9e6487bf0d36 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 14:56:23 +0900 Subject: [PATCH 10/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20env=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20loc?= =?UTF-8?q?al,=20production=20=EB=B6=84=EB=A6=AC=20https://github.com/Team?= =?UTF-8?q?-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 83f7d2d..0ebdcfd 100644 --- a/.gitignore +++ b/.gitignore @@ -85,6 +85,7 @@ celerybeat-schedule # Environments .env +.env.* .venv env/ venv/ From 7aeea4d15be1b0807bd80b8501873bdbcf24f7c6 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:21:56 +0900 Subject: [PATCH 11/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20repository=20docstring=20=EC=B6=94=EA=B0=80=20ht?= =?UTF-8?q?tps://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/repositories/github_cursor_repository.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/repositories/github_cursor_repository.py b/app/repositories/github_cursor_repository.py index 740a027..7a5e93d 100644 --- a/app/repositories/github_cursor_repository.py +++ b/app/repositories/github_cursor_repository.py @@ -16,6 +16,9 @@ async def find_by_repository_name_and_source_type( repository_name: str, source_type: SourceType, ) -> Optional[GithubCursorEntity]: + """ + 특정 repository + source_type 커서 조회 + """ query = select(GithubCursorEntity).where( GithubCursorEntity.repository_name == repository_name, GithubCursorEntity.source_type == source_type, @@ -29,7 +32,10 @@ async def upsert( repository_name: str, source_type: SourceType, cursor_value: str, - ) -> None: + ) -> GithubCursorEntity: + """ + 커서 upsert (없으면 생성, 있으면 업데이트) + """ query = insert(GithubCursorEntity).values( id=uuid.uuid4(), repository_name=repository_name, @@ -41,5 +47,7 @@ async def upsert( "cursor_value": cursor_value, "updated_at": func.now(), }, - ) - await session.execute(query) + ).returning(GithubCursorEntity) + + result = await session.execute(query) + return result.scalar_one() From 474b47408d846e85e8de868250d4ec83729f4f16 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:22:09 +0900 Subject: [PATCH 12/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC=EB=A7=B7=ED=8C=85?= =?UTF-8?q?=20https://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/utils/logger.py | 91 ++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/app/utils/logger.py b/app/utils/logger.py index e4114fb..f6e39c8 100644 --- a/app/utils/logger.py +++ b/app/utils/logger.py @@ -10,61 +10,60 @@ def setup_logger( - name: str = "chatbot", - log_level: str = "INFO", - log_file: Optional[str] = None, - format_string: Optional[str] = None, + name: str = "chatbot", + log_level: str = "INFO", + log_file: Optional[str] = None, + format_string: Optional[str] = None, ) -> logging.Logger: - """ - 로거를 설정하고 반환합니다. + """ + 로거를 설정하고 반환합니다. - Args: - name: 로거 이름 (기본값: "chatbot") - log_level: 로그 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL) - log_file: 로그 파일 경로 (None이면 파일 로깅 안 함) - format_string: 커스텀 포맷 문자열 (None이면 기본 포맷 사용) + Args: + name: 로거 이름 (기본값: "chatbot") + log_level: 로그 레벨 (DEBUG, INFO, WARNING, ERROR, CRITICAL) + log_file: 로그 파일 경로 (None이면 파일 로깅 안 함) + format_string: 커스텀 포맷 문자열 (None이면 기본 포맷 사용) - Returns: - 설정된 Logger 인스턴스 - """ - logger = logging.getLogger(name) - - # 이미 핸들러가 설정되어 있으면 기존 로거 반환 - if logger.handlers: - return logger + Returns: + 설정된 Logger 인스턴스 + """ + logger = logging.getLogger(name) - # 로그 레벨 설정 - level = getattr(logging, log_level.upper(), logging.INFO) - logger.setLevel(level) + # 이미 핸들러가 설정되어 있으면 기존 로거 반환 + if logger.handlers: + return logger - # 기본 포맷 설정 - if format_string is None: - format_string = ( - "%(asctime)s - %(name)s - %(levelname)s - " - "%(filename)s:%(lineno)d - %(message)s" - ) - - formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S") + # 로그 레벨 설정 + level = getattr(logging, log_level.upper(), logging.INFO) + logger.setLevel(level) - # 콘솔 핸들러 설정 - console_handler = logging.StreamHandler(sys.stdout) - console_handler.setLevel(level) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) + # 기본 포맷 설정 + if format_string is None: + format_string = ( + "%(asctime)s - %(name)s - %(levelname)s - " + "%(filename)s:%(lineno)d - %(message)s" + ) - # 파일 핸들러 설정 (선택적) - if log_file: - log_path = Path(log_file) - log_path.parent.mkdir(parents=True, exist_ok=True) - - file_handler = logging.FileHandler(log_file, encoding="utf-8") - file_handler.setLevel(level) - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) + formatter = logging.Formatter(format_string, datefmt="%Y-%m-%d %H:%M:%S") - return logger + # 콘솔 핸들러 설정 + console_handler = logging.StreamHandler(sys.stdout) + console_handler.setLevel(level) + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + # 파일 핸들러 설정 (선택적) + if log_file: + log_path = Path(log_file) + log_path.parent.mkdir(parents=True, exist_ok=True) + + file_handler = logging.FileHandler(log_file, encoding="utf-8") + file_handler.setLevel(level) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + + return logger # 기본 로거 인스턴스 생성 logger = setup_logger() - From 53a82853c6b4187f14794df7c95aa2d2f7ad0b49 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:22:49 +0900 Subject: [PATCH 13/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20postgresql=20DB=20=EC=97=B0=EA=B2=B0=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EC=8B=9C=20=EC=84=B8=EC=85=98=20=EC=A2=85=EB=A3=8C?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20https://github.com?= =?UTF-8?q?/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/database.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/config/database.py b/app/config/database.py index a840f5a..973d0fd 100644 --- a/app/config/database.py +++ b/app/config/database.py @@ -1,14 +1,16 @@ from typing import AsyncGenerator -from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine +from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine, AsyncEngine from app.config.settings import get_settings +from app.utils.logger import logger _settings = get_settings() _engine = create_async_engine( _settings.postgres_url, pool_pre_ping=True, + echo=False, # SQL 쿼리 로깅 ) _async_session_factory = async_sessionmaker( @@ -18,10 +20,21 @@ ) +def get_async_engine() -> AsyncEngine: + return _engine + + def get_async_session_factory() -> async_sessionmaker[AsyncSession]: return _async_session_factory async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async with _async_session_factory() as session: - yield session + try: + yield session + except Exception as e: + logger.error(f"Postgres DB 에러: {e}") + await session.rollback() + raise + finally: + await session.close() From 5fae0f80c5ff1f4a5ee84de4d7cbe0138e389de4 Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:23:03 +0900 Subject: [PATCH 14/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20github=20env=20api=5Ftoken=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20https://github.com/Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/config/settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/config/settings.py b/app/config/settings.py index 6e39fc3..d23d74d 100644 --- a/app/config/settings.py +++ b/app/config/settings.py @@ -17,6 +17,7 @@ class Settings(BaseSettings): # GitHub API 설정 github_api_base_url: str + github_api_token: str | None = None # Ollama 설정 ollama_base_url: str From 483169ef7fb8f61316c6990605cf1a830976030c Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:23:39 +0900 Subject: [PATCH 15/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20postgres=20=EC=97=94=ED=8B=B0=ED=8B=B0=20created?= =?UTF-8?q?=5Fat,=20updated=5Fat=20=EB=B0=8F=20UUID=20PK=20=ED=97=AC?= =?UTF-8?q?=ED=8D=BC=20Mixin=20=EC=B6=94=EA=B0=80=20https://github.com/Tea?= =?UTF-8?q?m-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/base.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/app/models/base.py b/app/models/base.py index 8300947..364c29f 100644 --- a/app/models/base.py +++ b/app/models/base.py @@ -1,4 +1,9 @@ -from sqlalchemy.orm import DeclarativeBase +import uuid +from datetime import datetime + +from sqlalchemy import DateTime, func +from sqlalchemy.dialects.postgresql.base import UUID +from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column class Base(DeclarativeBase): @@ -6,4 +11,33 @@ class Base(DeclarativeBase): SQLAlchemy Declarative Base - 모든 엔티티는 Base 상속 """ - pass \ No newline at end of file + pass + + +class TimestampMixin: + """ + created_at, updated_at 자동 관리 Mixin + - created_at: DB 레벨 자동 설정 + - updated_at: 애플리케이션 레벨에서 명시적 관리 + """ + created_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + server_default=func.now(), # DB 레벨 - INSERT 시 자동 + ) + + updated_at: Mapped[datetime] = mapped_column( + DateTime(timezone=True), + nullable=False, + server_default=func.now(), # DB 레벨 - INSERT 시 자동 + ) + +class PrimaryKeyMixin: + """ + UUID Primary Key Mixin + """ + id: Mapped[uuid.UUID] = mapped_column( + UUID(as_uuid=True), + primary_key=True, + default=uuid.uuid4, + ) From 3bb40dabbd44f94bff40e3eec4252890e890f6ff Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:23:47 +0900 Subject: [PATCH 16/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20postgres=20=EC=97=94=ED=8B=B0=ED=8B=B0=20created?= =?UTF-8?q?=5Fat,=20updated=5Fat=20=EB=B0=8F=20UUID=20PK=20=ED=97=AC?= =?UTF-8?q?=ED=8D=BC=20Mixin=20=EC=A0=81=EC=9A=A9=20https://github.com/Tea?= =?UTF-8?q?m-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/models/github_cursor.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/app/models/github_cursor.py b/app/models/github_cursor.py index ff20d32..990cdf2 100644 --- a/app/models/github_cursor.py +++ b/app/models/github_cursor.py @@ -1,29 +1,20 @@ -import uuid -from datetime import datetime - from sqlalchemy import Enum as SqlEnum -from sqlalchemy import UniqueConstraint, Index, String, DateTime, func -from sqlalchemy.dialects.postgresql.base import UUID +from sqlalchemy import UniqueConstraint, Index, String from sqlalchemy.orm import Mapped, mapped_column -from app.models.base import Base +from app.models.base import Base, PrimaryKeyMixin, TimestampMixin from app.models.enums.source_type import SourceType -class GithubCursorEntity(Base): +class GithubCursorEntity(Base, PrimaryKeyMixin, TimestampMixin): __tablename__ = "github_cursor" __table_args__ = ( UniqueConstraint("repository_name", "source_type", name="uq_github_cursor"), Index("idx_github_cursor_repo_type", "repository_name", "source_type") ) - id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - repository_name: Mapped[str] = mapped_column(String(200), nullable=False) source_type: Mapped[SourceType] = mapped_column(SqlEnum(SourceType, native_enum=False), nullable=False) cursor_value: Mapped[str] = mapped_column(String(500), nullable=False) - - created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now()) - updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now()) From 9adbab9fb175b2e76a6c97a96eadbd23f5648b7d Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Mon, 22 Dec 2025 16:24:21 +0900 Subject: [PATCH 17/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20=EC=84=9C=EB=B2=84=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=EC=8B=9C=20PG=20=ED=85=8C=EC=9D=B4=EB=B8=94=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B0=8F=20=EC=95=A0=ED=94=8C=EB=A6=AC=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=8B=9C=EC=9E=91=20=EC=A2=85=EB=A3=8C=20?= =?UTF-8?q?=EB=A1=9C=EA=B9=85=20https://github.com/Team-Romi/chatbot/issue?= =?UTF-8?q?s/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/db/__init__.py | 0 app/db/init_db.py | 17 +++++++++++++++++ app/main.py | 16 +++++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 app/db/__init__.py create mode 100644 app/db/init_db.py diff --git a/app/db/__init__.py b/app/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/db/init_db.py b/app/db/init_db.py new file mode 100644 index 0000000..7c386ea --- /dev/null +++ b/app/db/init_db.py @@ -0,0 +1,17 @@ +from sqlalchemy.ext.asyncio import AsyncEngine + +from app.models.base import Base +from app.utils.logger import logger + + +def _import_all_models() -> None: + from app.models.github_cursor import GithubCursorEntity + + +async def create_tables_if_not_exists(engine: AsyncEngine) -> None: + _import_all_models() + + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + + logger.info("DB 테이블 생성완료") diff --git a/app/main.py b/app/main.py index 18f44bd..f9c9cff 100644 --- a/app/main.py +++ b/app/main.py @@ -1,8 +1,22 @@ +from contextlib import asynccontextmanager from typing import Dict from fastapi import FastAPI -app = FastAPI() +from app.config.database import get_async_engine +from app.db.init_db import create_tables_if_not_exists +from app.utils.logger import logger + + +@asynccontextmanager +async def lifespan(app: FastAPI): + logger.info("ChatBot 애플리케이션 시작") + await create_tables_if_not_exists(get_async_engine()) + yield + logger.info("애플리케이션 종료") + + +app = FastAPI(lifespan=lifespan) @app.get("/health") From f7024c5405054e439656b3637e6e004accb340cd Mon Sep 17 00:00:00 2001 From: Chuseok22 Date: Fri, 2 Jan 2026 15:15:43 +0900 Subject: [PATCH 18/18] =?UTF-8?q?PostgreSQL=5F=EC=97=B0=EA=B2=B0=5F?= =?UTF-8?q?=EB=B0=8F=5F=ED=85=8C=EC=9D=B4=EB=B8=94=5F=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?:=20feat=20:=20.env=20=ED=99=98=EA=B2=BD=EB=B3=80=EC=88=98=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=9E=90=EB=8F=99=20=EC=97=85=EB=A1=9C?= =?UTF-8?q?=EB=93=9C=20workflow=20=EC=B6=94=EA=B0=80=20https://github.com/?= =?UTF-8?q?Team-Romi/chatbot/issues/1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/romi-auto-file-upload.yml | 96 +++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .github/workflows/romi-auto-file-upload.yml diff --git a/.github/workflows/romi-auto-file-upload.yml b/.github/workflows/romi-auto-file-upload.yml new file mode 100644 index 0000000..fe7b27a --- /dev/null +++ b/.github/workflows/romi-auto-file-upload.yml @@ -0,0 +1,96 @@ +name: romi-auto-file-upload.yml + +on: + push: + branches: + - main + +jobs: + upload-files: + runs-on: ubuntu-latest + steps: + - name: 코드 체크아웃 + uses: actions/checkout@v4 + + # 타임스탬프 폴더명 생성 + - name: 타임스탬프 폴더명 생성 + run: | + # Asia/Seoul 로컬타임 사용 + export TZ='Asia/Seoul' + # YYYY-MM-DD_HH-MM-SS 형식 + TIMESTAMP=$(date '+%Y-%m-%d_%H-%M-%S') + echo "TIMESTAMP=$TIMESTAMP" >> $GITHUB_ENV + echo "BUILD_DATE=$(date '+%Y-%m-%d %H:%M')" >> $GITHUB_ENV + echo "생성된 타임스탬프: $TIMESTAMP" + + # 짧은 커밋 해시 계산 + - name: 짧은 커밋 해시 계산 + run: | + echo "SHORT_COMMIT_HASH=$(echo ${{ github.sha }} | cut -c1-7)" >> $GITHUB_ENV + echo "짧은 커밋 해시: $(echo ${{ github.sha }} | cut -c1-7)" + + - name: 서버에 파일 업로드 + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.SERVER_HOST }} + username: ${{ secrets.SERVER_USER }} + password: ${{ secrets.SERVER_PASSWORD }} + port: 2022 + envs: TIMESTAMP,SHORT_COMMIT_HASH,BUILD_DATE + script: | + set -e + + echo "환경변수 설정.." + export PW=${{ secrets.SERVER_PASSWORD }} + + # 최신 파일 저장 디렉토리 생성 + echo "메인 디렉토리 생성 중..." + echo $PW | sudo -S mkdir -p /volume1/projects/romi/github_secret + + # 타임스탬프 백업 디렉토리 생성 + echo "타임스탬프 백업 디렉토리 생성 중... ($TIMESTAMP)" + echo $PW | sudo -S mkdir -p /volume1/projects/romi/github_secret/$TIMESTAMP + + # .env 파일 업로드 (최신 + 백업) + echo ".env 파일 업로드 중..." + cat << 'EOF' | sudo tee /volume1/projects/romi/github_secret/.env > /dev/null + ${{ secrets.APPLICATION_PROD_YML }} + EOF + cat << 'EOF' | sudo tee /volume1/projects/romi/github_secret/$TIMESTAMP/.env > /dev/null + ${{ secrets.APPLICATION_PROD_YML }} + EOF + echo ".env 파일 업로드 완료" + + # 메타데이터 JSON 파일 생성 및 업로드 + echo "메타데이터 JSON 파일 생성 중..." + cat << EOF | sudo tee /volume1/projects/romi/github_secret/$TIMESTAMP/cicd-gitignore-file.json > /dev/null + { + "build_info": { + "timestamp": "$TIMESTAMP", + "workflow": "설정 파일 관리", + "run_id": "${{ github.run_id }}", + "run_number": "${{ github.run_number }}", + "job": "upload-files", + "event": "${{ github.event_name }}", + "repository": "${{ github.repository }}", + "owner": "${{ github.repository_owner }}", + "branch": "${{ github.ref_name }}", + "commit_hash": "${{ github.sha }}", + "short_hash": "$SHORT_COMMIT_HASH", + "commit_url": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}", + "actor": "${{ github.actor }}", + "build_date": "$BUILD_DATE", + "runner_os": "${{ runner.os }}" + }, + "files": [ + { + "file_name": ".env", + "file_path": "/", + "last_updated": "$BUILD_DATE" + } + ] + } + EOF + echo "메타데이터 JSON 파일 업로드 완료" + + echo "모든 파일 업로드 완료"