Skip to content

Conversation

@mjy926
Copy link
Contributor

@mjy926 mjy926 commented Nov 28, 2025

  • history에 unit을 추가했습니다.
  • key가 중복될 경우 기존의 내용을 수정합니다.

@mjy926 mjy926 self-assigned this Nov 28, 2025
@mjy926 mjy926 requested a review from a team as a code owner November 28, 2025 13:19
@coderabbitai
Copy link

coderabbitai bot commented Nov 28, 2025

Walkthrough

히스토리 엔티티에 선택적 history_unit 필드를 추가하고, 일괄 업데이트 인터페이스로 변경했으며 히스토리 삭제 기능(서비스·엔드포인트·예외)을 도입했습니다. 관련 스키마, DB 마이그레이션 및 관리자 목록 컬럼도 함께 변경되었습니다.

Changes

Cohort / File(s) Summary
예외 처리
wacruit/src/apps/history/exceptions.py
새로운 HistoryNotFoundException 예외 클래스 추가 (상태코드 404, 메시지 "History key not found")
데이터 모델
wacruit/src/apps/history/models.py
History 모델에 선택적 필드 `history_unit: Mapped[str50
저장소 로직
wacruit/src/apps/history/repositories.py
update_history 서명이 단일 객체에서 List[History] 입력/출력으로 변경되어 일괄 업데이트로 동작하도록 수정; 키가 존재하면 값/단위 갱신, 없으면 삽입; 새로운 delete_history(history_key: str) -> bool 추가
API 스키마
wacruit/src/apps/history/schemas.py
HistoryItemRequest(history_key, history_value, 선택적 history_unit) 추가; UpdateHistoryRequestitems: List[HistoryItemRequest]로 재구성; DeleteHistoryRequest 추가; HistoryResponsehistory_unit 필드 추가
서비스 레이어
wacruit/src/apps/history/services.py
update_history가 요청의 items를 기반으로 일괄 처리하도록 변경; get_history에서 기존의 operation_period 자동 추가 로직 제거; delete_history(delete_request: DeleteHistoryRequest) 메서드 추가 및 삭제 실패 시 HistoryNotFoundException 발생
HTTP 엔드포인트
wacruit/src/apps/history/views.py
새로운 DELETE /v3/history 엔드포인트 추가(관리자 권한 필요, DeleteHistoryRequest 수용, HTTP 204 No Content)
데이터베이스 마이그레이션
wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py
history 테이블에 history_unit 컬럼 추가 (String(50), nullable); upgrade/downgrade 정의
관리자 UI
wacruit/src/admin/views.py
HistoryAdmin의 표시 컬럼에 History.history_unit 추가

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • 주의 필요 영역:
    • repositories.pyupdate_history 서명 변경으로 인해 호출자(다른 서비스/유닛테스트) 호환성 영향 검토
    • services.py에서 get_historyoperation_period 제거에 따른 소비자 영향 범위 확인
    • DELETE 엔드포인트 권한 검사 및 오류 응답(404) 흐름 검증
    • 마이그레이션: history_unit이 nullable로 추가된 점과 기존 데이터 마이그레이션 전략 확인
    • 스키마 변경에 따른 클라이언트/문서(예: API 사용법) 동기화 여부 확인

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive 제목 'Fix: history response'는 PR의 주요 변경사항(history에 unit 필드 추가, 중복 키 처리)을 명확하게 설명하지 못하고 너무 일반적입니다. 더 구체적인 제목으로 변경하세요. 예: 'Add history_unit field and implement bulk update with duplicate key handling' 또는 'Add history unit field and delete endpoint'
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed PR 설명이 변경사항의 핵심 내용(unit 필드 추가, 중복 키 업데이트 처리)과 관련이 있으나 DELETE 엔드포인트 추가 등 다른 중요 변경사항을 언급하지 않습니다.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/history-response

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a8e857 and a8d358f.

📒 Files selected for processing (4)
  • wacruit/src/admin/views.py (1 hunks)
  • wacruit/src/apps/history/models.py (1 hunks)
  • wacruit/src/apps/history/schemas.py (1 hunks)
  • wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • wacruit/src/apps/history/models.py
  • wacruit/src/apps/history/schemas.py
🧰 Additional context used
🧬 Code graph analysis (1)
wacruit/src/admin/views.py (1)
wacruit/src/apps/history/models.py (1)
  • History (8-14)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: pytest
🔇 Additional comments (3)
wacruit/src/admin/views.py (1)

508-508: LGTM!

History.history_unit 필드를 admin 컬럼 목록에 추가하는 것이 모델 정의와 일치하며 올바르게 구현되었습니다.

wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py (2)

21-23: 이전 리뷰에서 제기된 문제가 해결되었습니다.

history_unit 컬럼이 이제 nullable=True로 추가되어 기존 데이터가 있는 테이블에서도 안전하게 마이그레이션을 실행할 수 있습니다. 이는 모델 정의(history_unit: Mapped[str50 | None])와도 일치합니다.


27-30: LGTM!

다운그레이드 로직이 올바르게 구현되어 있으며, 업그레이드 작업을 적절히 롤백합니다.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (3)
wacruit/src/apps/history/repositories.py (3)

5-5: 사용되지 않는 import를 제거하세요.

sqlalchemy.delete가 import되었지만 코드에서 사용되지 않습니다. delete_history 메서드는 session.delete() 인스턴스 메서드를 사용하며, SQLAlchemy의 delete() 구문을 사용하지 않습니다.

-from sqlalchemy import delete
 from sqlalchemy import select

27-44: 쿼리 패턴을 일관되게 유지하세요.

update_history 메서드(Line 30)는 레거시 query() API를 사용하지만, get_history 메서드(Line 24)는 SQLAlchemy 2.0 스타일의 select()를 사용합니다. 일관성을 위해 동일한 패턴을 사용하는 것을 권장합니다.

Line 30을 SQLAlchemy 2.0 스타일로 변경:

-        existing_key = (
-            self.session.query(History).filter(History.history_key.in_(keys)).all()
-        )
+        query = select(History).where(History.history_key.in_(keys))
+        existing_key = list(self.session.execute(query).scalars().all())

54-59: 쿼리 패턴을 일관되게 유지하세요.

delete_history 메서드도 레거시 query() API를 사용합니다. 코드베이스 전체에서 일관성을 위해 SQLAlchemy 2.0 스타일의 select()를 사용하는 것을 권장합니다.

-    def delete_history(self, history_key: str) -> bool:
-        history = self.session.query(History).filter_by(history_key=history_key).first()
-        if not history:
-            return False
-        self.session.delete(history)
-        return True
+    def delete_history(self, history_key: str) -> bool:
+        query = select(History).where(History.history_key == history_key)
+        history = self.session.execute(query).scalar_one_or_none()
+        if not history:
+            return False
+        self.session.delete(history)
+        return True
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7adaa79 and 8a8e857.

📒 Files selected for processing (7)
  • wacruit/src/apps/history/exceptions.py (1 hunks)
  • wacruit/src/apps/history/models.py (1 hunks)
  • wacruit/src/apps/history/repositories.py (3 hunks)
  • wacruit/src/apps/history/schemas.py (1 hunks)
  • wacruit/src/apps/history/services.py (2 hunks)
  • wacruit/src/apps/history/views.py (2 hunks)
  • wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (5)
wacruit/src/apps/history/exceptions.py (1)
wacruit/src/apps/common/exceptions.py (1)
  • WacruitException (6-24)
wacruit/src/apps/history/repositories.py (3)
wacruit/src/apps/history/services.py (2)
  • update_history (21-34)
  • delete_history (36-39)
wacruit/src/apps/history/views.py (2)
  • update_history (17-27)
  • delete_history (38-43)
wacruit/src/apps/history/models.py (1)
  • History (8-14)
wacruit/src/apps/history/schemas.py (1)
wacruit/src/apps/common/schemas.py (1)
  • OrmModel (19-25)
wacruit/src/apps/history/views.py (4)
wacruit/src/apps/history/schemas.py (1)
  • DeleteHistoryRequest (18-19)
wacruit/src/apps/history/repositories.py (1)
  • delete_history (54-59)
wacruit/src/apps/history/services.py (2)
  • delete_history (36-39)
  • HistoryService (12-39)
wacruit/src/tests/announcement/conftest.py (1)
  • admin_user (34-47)
wacruit/src/apps/history/services.py (5)
wacruit/src/apps/history/exceptions.py (1)
  • HistoryNotFoundException (4-6)
wacruit/src/apps/history/models.py (1)
  • History (8-14)
wacruit/src/apps/history/repositories.py (3)
  • update_history (27-44)
  • get_history (23-25)
  • delete_history (54-59)
wacruit/src/apps/history/schemas.py (1)
  • DeleteHistoryRequest (18-19)
wacruit/src/apps/history/views.py (3)
  • update_history (17-27)
  • get_history (31-34)
  • delete_history (38-43)
🪛 Ruff (0.14.6)
wacruit/src/apps/history/views.py

39-39: Unused function argument: admin_user

(ARG001)

🔇 Additional comments (7)
wacruit/src/apps/history/models.py (1)

14-14: LGTM!

history_unit 필드 추가가 적절하며 마이그레이션 스키마와 일치합니다.

wacruit/src/apps/history/exceptions.py (1)

4-6: LGTM!

HistoryNotFoundException 예외 구현이 적절하며 기존 패턴을 따릅니다.

wacruit/src/apps/history/views.py (1)

37-43: LGTM!

DELETE 엔드포인트 구현이 적절합니다. admin_user 매개변수는 의도적으로 사용되지 않으며, FastAPI의 의존성 주입을 통한 권한 검증에 사용됩니다.

참고: Ruff의 ARG001 경고는 false positive입니다. AdminUser 의존성은 엔드포인트에 대한 관리자 권한을 보장하기 위해 필요합니다.

wacruit/src/apps/history/schemas.py (2)

8-11: LGTM!

HistoryItemRequest 스키마가 적절하게 정의되었습니다. history_unit을 선택 사항으로 설정한 것은 유연성을 제공합니다.


18-19: LGTM!

DeleteHistoryRequest 스키마가 간결하고 적절합니다.

wacruit/src/apps/history/services.py (1)

36-39: LGTM!

delete_history 메서드가 적절하게 구현되었으며, repository의 boolean 반환 값을 기반으로 HistoryNotFoundException을 발생시킵니다.

wacruit/src/apps/history/repositories.py (1)

44-44: Based on my verification, I can now provide the rewritten review comment.


Return value is not used by callers; no practical issue exists.

The repository's update_history() method technically returns objects that may lack auto-generated IDs for new records. However, the service layer (line 32 of services.py) discards this return value entirely and instead calls get_history() to fetch fresh data from the database (line 34). The API layer receives fresh, correct data with all IDs populated. The return statement in the repository is redundant but causes no actual problem in practice.

Likely an incorrect or invalid review comment.

id: int
history_key: str
history_value: str
history_unit: str
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Step 1: Find and examine the schemas.py file
find . -name "schemas.py" -path "*/history/*" -type f

Repository: wafflestudio/wacruit-server

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Read the schemas.py file to see the current schema definitions
cat -n ./wacruit/src/apps/history/schemas.py

Repository: wafflestudio/wacruit-server

Length of output: 733


🏁 Script executed:

#!/bin/bash
# Find the History ORM model definition
find . -name "models.py" -path "*/history/*" -type f

Repository: wafflestudio/wacruit-server

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Read the History ORM model
cat -n ./wacruit/src/apps/history/models.py

Repository: wafflestudio/wacruit-server

Length of output: 541


🏁 Script executed:

#!/bin/bash
# Find database migrations related to history
find . -name "*migration*" -o -name "*alembic*" | head -20

Repository: wafflestudio/wacruit-server

Length of output: 118


🏁 Script executed:

#!/bin/bash
# List migration files
ls -la ./wacruit/src/database/migrations/versions/ | head -30

Repository: wafflestudio/wacruit-server

Length of output: 2944


🏁 Script executed:

#!/bin/bash
# Search for history table in migrations
rg -n "history" ./wacruit/src/database/migrations/versions/ -i

Repository: wafflestudio/wacruit-server

Length of output: 2203


🏁 Script executed:

#!/bin/bash
# Read the add_history_unit migration
cat -n ./wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py

Repository: wafflestudio/wacruit-server

Length of output: 1025


🏁 Script executed:

#!/bin/bash
# Search for HistoryResponse.from_orm usage
rg -n "HistoryResponse|from_orm" . -A3 -B3 --type=py | grep -A5 -B5 "from_orm"

Repository: wafflestudio/wacruit-server

Length of output: 33306


🏁 Script executed:

#!/bin/bash
# Search for HistoryItemRequest and UpdateHistoryRequest usage
rg -n "HistoryItemRequest|UpdateHistoryRequest" ./wacruit/src/apps/history -A5 -B5

Repository: wafflestudio/wacruit-server

Length of output: 4500


🏁 Script executed:

#!/bin/bash
# Read the full history services.py
cat -n ./wacruit/src/apps/history/services.py

Repository: wafflestudio/wacruit-server

Length of output: 1711


🏁 Script executed:

#!/bin/bash
# Search for history tests
find . -name "*test*history*" -o -path "*/tests/*" -name "*history*" | head -20

Repository: wafflestudio/wacruit-server

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check if there are any existing history records to understand migration impact
# Also verify the migration one more time for any server_default we might have missed
cat ./wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py

Repository: wafflestudio/wacruit-server

Length of output: 815


Based on my thorough verification of the codebase, I can now provide the rewritten review comment.

Schema mismatch: history_unit field allows None in input but requires non-null value in database and response schema.

The issue is confirmed:

  1. Input schema (HistoryItemRequest, line 11): history_unit: str | None = None — accepts None
  2. Service layer (services.py, line 28): Passes item.history_unit directly to History constructor, which can be None
  3. ORM model (models.py, line 14): history_unit: Mapped[str50] — NOT NULL in database
  4. Response schema (HistoryResponse, line 26): history_unit: str — requires non-null
  5. Migration (2025_11_27_2152): Adds column with nullable=False and no server_default — will fail on existing data or fail when inserting None
  6. Serialization (views.py, lines 25, 34): Calls HistoryResponse.from_orm() which will fail validation if history_unit is None

Options to fix:

Option 1: Make history_unit optional in response schema

class HistoryResponse(OrmModel):
    id: int
    history_key: str
    history_value: str
-   history_unit: str
+   history_unit: str | None = None

Option 2: Make history_unit required in input schema and add migration default
Modify HistoryItemRequest to require history_unit, and update the migration to include server_default="" or similar.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
history_unit: str
history_unit: str | None = None

Comment on lines 21 to 23
op.add_column(
"history", sa.Column("history_unit", sa.String(length=50), nullable=False)
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

NOT NULL 제약 조건이 있는 컬럼을 기본값 없이 추가하면 마이그레이션이 실패합니다.

history_unit 컬럼이 nullable=False로 추가되지만 기본값이 제공되지 않습니다. history 테이블에 기존 데이터가 있는 경우 마이그레이션이 실패합니다.

다음 중 하나의 방법으로 수정하세요:

옵션 1: 기본값 제공 (권장)

     op.add_column(
-        "history", sa.Column("history_unit", sa.String(length=50), nullable=False)
+        "history", sa.Column("history_unit", sa.String(length=50), nullable=False, server_default="")
     )

옵션 2: 2단계 마이그레이션

  1. 먼저 nullable=True로 컬럼 추가
  2. 기존 데이터 업데이트
  3. nullable=False로 변경
     op.add_column(
-        "history", sa.Column("history_unit", sa.String(length=50), nullable=False)
+        "history", sa.Column("history_unit", sa.String(length=50), nullable=True)
     )
+    # Update existing rows
+    op.execute("UPDATE history SET history_unit = '' WHERE history_unit IS NULL")
+    # Make it non-nullable
+    op.alter_column("history", "history_unit", nullable=False)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
op.add_column(
"history", sa.Column("history_unit", sa.String(length=50), nullable=False)
)
op.add_column(
"history", sa.Column("history_unit", sa.String(length=50), nullable=False, server_default="")
)
Suggested change
op.add_column(
"history", sa.Column("history_unit", sa.String(length=50), nullable=False)
)
op.add_column(
"history", sa.Column("history_unit", sa.String(length=50), nullable=True)
)
# Update existing rows
op.execute("UPDATE history SET history_unit = '' WHERE history_unit IS NULL")
# Make it non-nullable
op.alter_column("history", "history_unit", nullable=False)
🤖 Prompt for AI Agents
In
wacruit/src/database/migrations/versions/2025_11_27_2152-60e3aaf301ce_add_history_unit.py
around lines 21-23, the migration adds history_unit with nullable=False but no
default which will fail if history already has rows; fix by either (A) adding a
server_default (e.g., sa.text("'your_default'") or sa.literal_column) when
creating the column and then optionally removing the server_default in a
subsequent alter, or (B) perform a two-step migration: first add the column as
nullable=True, run an update to populate existing rows with the desired value,
then alter the column to nullable=False; implement one of these approaches in
this migration or split into two migrations accordingly.

@mjy926 mjy926 merged commit cf7354a into develop Nov 29, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants