Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
47413ea
PostgreSQL_연결_및_테이블_생성 : feat : env 환경변수 주입 https://github.com/Team-R…
Chuseok22 Dec 17, 2025
9066e5c
Merge branch 'main' into 20251217_#1_PostgreSQL_연결_및_테이블_생성
Chuseok22 Dec 22, 2025
45655d0
PostgreSQL_연결_및_테이블_생성 : feat : env 환경변수 주입을 위한 settings.py 추가 https:…
Chuseok22 Dec 22, 2025
280c968
PostgreSQL_연결_및_테이블_생성 : feat : postgresql DB 세션 연결 https://github.co…
Chuseok22 Dec 22, 2025
5beeb5c
PostgreSQL_연결_및_테이블_생성 : feat : source_type enum 추가 https://github.co…
Chuseok22 Dec 22, 2025
5cb7cef
PostgreSQL_연결_및_테이블_생성 : feat : GithubCursorEntity 추가 https://github.…
Chuseok22 Dec 22, 2025
a7fec07
PostgreSQL_연결_및_테이블_생성 : feat : github_cursor_repository 추가 https://g…
Chuseok22 Dec 22, 2025
8e26b45
PostgreSQL_연결_및_테이블_생성 : feat : github_cursor 테이블 flyway migration 코드…
Chuseok22 Dec 22, 2025
6ec67cd
PostgreSQL_연결_및_테이블_생성 : feat : requirements.txt 의존성 업데이트 https://git…
Chuseok22 Dec 22, 2025
3b3d060
PostgreSQL_연결_및_테이블_생성 : feat : coderabbitai.yaml 추가 https://github.c…
Chuseok22 Dec 22, 2025
c7e3c0e
PostgreSQL_연결_및_테이블_생성 : feat : env 환경변수 local, production 분리 https:/…
Chuseok22 Dec 22, 2025
7aeea4d
PostgreSQL_연결_및_테이블_생성 : feat : repository docstring 추가 https://githu…
Chuseok22 Dec 22, 2025
474b474
PostgreSQL_연결_및_테이블_생성 : feat : 코드 포맷팅 https://github.com/Team-Romi/c…
Chuseok22 Dec 22, 2025
53a8285
PostgreSQL_연결_및_테이블_생성 : feat : postgresql DB 연결 실패 시 세션 종료 로직 개선 htt…
Chuseok22 Dec 22, 2025
5fae0f8
PostgreSQL_연결_및_테이블_생성 : feat : github env api_token 추가 https://githu…
Chuseok22 Dec 22, 2025
483169e
PostgreSQL_연결_및_테이블_생성 : feat : postgres 엔티티 created_at, updated_at 및…
Chuseok22 Dec 22, 2025
3bb40da
PostgreSQL_연결_및_테이블_생성 : feat : postgres 엔티티 created_at, updated_at 및…
Chuseok22 Dec 22, 2025
9adbab9
PostgreSQL_연결_및_테이블_생성 : feat : 서버 시작 시 PG 테이블 생성 및 애플리케이션 시작 종료 로깅 h…
Chuseok22 Dec 22, 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
18 changes: 18 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ celerybeat-schedule

# Environments
.env
.env.*
.venv
env/
venv/
Expand Down
40 changes: 39 additions & 1 deletion app/config/database.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,40 @@
# 빈 파일 - DB 연결 설정
from typing import AsyncGenerator

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(
bind=_engine,
expire_on_commit=False,
autoflush=False,
)


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:
try:
yield session
except Exception as e:
logger.error(f"Postgres DB 에러: {e}")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message uses "Postgres" but the official name of the database is "PostgreSQL". For consistency and accuracy, use "PostgreSQL" in error messages and logging.

Suggested change
logger.error(f"Postgres DB 에러: {e}")
logger.error(f"PostgreSQL DB 에러: {e}")

Copilot uses AI. Check for mistakes.
await session.rollback()
raise
finally:
await session.close()
Comment on lines +39 to +40
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly closing the session in the finally block is unnecessary when using the async context manager. The context manager automatically handles session closure. Remove the explicit await session.close() call to avoid potential double-close issues and rely on the context manager's cleanup.

Suggested change
finally:
await session.close()

Copilot uses AI. Check for mistakes.
47 changes: 46 additions & 1 deletion app/config/settings.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
# 빈 파일 - 환경 변수 설정
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
github_api_token: str | None = None

# 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
Comment on lines +18 to +42
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The settings contain sensitive credentials (API keys, tokens, database URLs) but there's no validation to ensure these required security-sensitive fields are actually provided. Consider adding validation to ensure that critical security credentials like github_api_token, ollama_api_key, qdrant_api_key, and postgres_url are not empty strings or contain placeholder values when the application starts.

Copilot uses AI. Check for mistakes.


@lru_cache(maxsize=1)
def get_settings() -> Settings:
return Settings()
Empty file added app/db/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions app/db/init_db.py
Original file line number Diff line number Diff line change
@@ -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 테이블 생성완료")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after "생성" in the Korean log message. The message should be "DB 테이블 생성 완료" with a space between "생성" and "완료" for proper Korean grammar and readability.

Suggested change
logger.info("DB 테이블 생성완료")
logger.info("DB 테이블 생성 완료")

Copilot uses AI. Check for mistakes.
16 changes: 15 additions & 1 deletion app/main.py
Original file line number Diff line number Diff line change
@@ -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")
Expand Down
43 changes: 43 additions & 0 deletions app/models/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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):
"""
SQLAlchemy Declarative Base
- 모든 엔티티는 Base 상속
"""
pass


class TimestampMixin:
"""
created_at, updated_at 자동 관리 Mixin
- created_at: DB 레벨 자동 설정
- updated_at: 애플리케이션 레벨에서 명시적 관리
Comment on lines +19 to +21
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment claims that updated_at is managed at the application level, but the implementation only sets server_default=func.now(), which means it will only be set automatically on INSERT, not on UPDATE. The field will not automatically update when a record is modified. Either the comment should be corrected to reflect the actual behavior, or an onupdate parameter should be added to automatically update this field on modifications.

Suggested change
created_at, updated_at 자동 관리 Mixin
- created_at: DB 레벨 자동 설정
- updated_at: 애플리케이션 레벨에서 명시적 관리
created_at, updated_at 타임스탬프 관리 Mixin
- created_at: DB 레벨에서 INSERT 자동 설정
- updated_at: DB 레벨에서 INSERT 자동 설정, UPDATE 시에는 별도 애플리케이션/DB 로직으로 명시적으로 갱신 필요

Copilot uses AI. Check for mistakes.
"""
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 시 자동
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated_at field is missing an onupdate trigger to automatically update the timestamp when records are modified. Currently, with only server_default=func.now(), the field will be set on INSERT but not automatically updated on UPDATE operations. Add onupdate=func.now() to ensure the timestamp is updated on modifications, or explicitly update it in application code when records change.

Suggested change
server_default=func.now(), # DB 레벨 - INSERT 시 자동
server_default=func.now(), # DB 레벨 - INSERT 시 자동
onupdate=func.now(), # UPDATE 시 자동

Copilot uses AI. Check for mistakes.
)

Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing blank line before the class definition. According to PEP 8, there should be two blank lines before top-level class definitions for better readability.

Suggested change

Copilot uses AI. Check for mistakes.
class PrimaryKeyMixin:
"""
UUID Primary Key Mixin
"""
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True),
primary_key=True,
default=uuid.uuid4,
)
Empty file added app/models/enums/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions app/models/enums/source_type.py
Original file line number Diff line number Diff line change
@@ -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"
20 changes: 20 additions & 0 deletions app/models/github_cursor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from sqlalchemy import Enum as SqlEnum
from sqlalchemy import UniqueConstraint, Index, String
from sqlalchemy.orm import Mapped, mapped_column

from app.models.base import Base, PrimaryKeyMixin, TimestampMixin
from app.models.enums.source_type import SourceType


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")
)

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)
2 changes: 0 additions & 2 deletions app/models/github_issue.py

This file was deleted.

53 changes: 53 additions & 0 deletions app/repositories/github_cursor_repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
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]:
"""
특정 repository + source_type 커서 조회
"""
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,
) -> GithubCursorEntity:
"""
커서 upsert (없으면 생성, 있으면 업데이트)
"""
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(),
},
).returning(GithubCursorEntity)

result = await session.execute(query)
return result.scalar_one()
Loading