Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ dependencies {

// Spring Boot Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'

// Flyway
implementation "org.flywaydb:flyway-core"
implementation "org.flywaydb:flyway-mysql"
}

def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public class RoomJpaEntity extends BaseJpaEntity {
@Column(name = "member_count",nullable = false)
private int memberCount = 1;

@Builder.Default
@Column(name = "room_status", nullable = false)
@Enumerated(EnumType.STRING)
private RoomStatus roomStatus = RoomStatus.RECRUITING;

Comment on lines +53 to +57
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

room_status 매핑은 적절. 다만 null 유입 방지(Bean Validation) 추가 필요

@Builder.Default만으로는 빌더가 명시적으로 null을 넣을 때/기본 생성자로 생성 후 세터로 null이 들어갈 때를 막지 못합니다. DB 제약(nullable=false) 위반 또는 INSERT 실패를 예방하려면 @NotNull을 붙여 조기 검증을 권장합니다.

적용 diff:

-    @Builder.Default
-    @Column(name = "room_status", nullable = false)
-    @Enumerated(EnumType.STRING)
-    private RoomStatus roomStatus = RoomStatus.RECRUITING;
+    @Builder.Default
+    @Column(name = "room_status", nullable = false)
+    @Enumerated(EnumType.STRING)
+    @NotNull
+    private RoomStatus roomStatus = RoomStatus.RECRUITING;

추가 import(변경 범위 밖):

import jakarta.validation.constraints.NotNull;

추가로, 생성 경로가 빌더가 아닌 경우를 대비해 PrePersist 가드도 고려 가능합니다(변경 범위 밖):

@PrePersist
void prePersistDefaultStatus() {
    if (roomStatus == null) roomStatus = RoomStatus.RECRUITING;
}
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java around
lines 53 to 57, the roomStatus field relies on @Builder.Default but can still
become null via explicit builder/null setters which would violate the DB
nullable=false constraint; add jakarta.validation.constraints.NotNull to the
roomStatus field (and import jakarta.validation.constraints.NotNull) to enforce
Bean Validation at the entity level, and optionally add a @PrePersist method to
set roomStatus to RoomStatus.RECRUITING if null for non-builder creation paths.

💡 Verification agent

🧩 Analysis chain

계층 의존 역전 가능성: RoomStatus의 패키지 위치 재검토

Enum이 adapter.out.jpa 패키지에 있고, 도메인 객체(Room)가 이를 직접 참조하면 도메인→인프라 의존이 생깁니다. Enum을 도메인 계층(e.g., konkuk.thip.room.domain.value.RoomStatus)으로 승격하거나, 도메인 Enum ↔ JPA Enum 매핑을 명확히 분리해 주세요.

아래 스크립트로 도메인 계층이 adapter의 RoomStatus를 import하는지 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# 도메인 코드에서 adapter.out.jpa.RoomStatus 참조 여부 확인
rg -nP 'import\s+konkuk\.thip\.room\.adapter\.out\.jpa\.RoomStatus' -C2 --type java
# 프로젝트 내 RoomStatus 선언 위치 확인
rg -nP '\benum\s+RoomStatus\b' -C2 --type java

Length of output: 1028


도메인 계층이 infra(adapter) enum을 직접 참조하지 않도록 분리 필요
Room.java(도메인)에서 konkuk.thip.room.adapter.out.jpa.RoomStatus를 import(Line 7)해 도메인→인프라 의존이 발생하고 있습니다.

  • src/main/java/konkuk/thip/room/domain/value 패키지에 도메인 전용 RoomStatus enum을 선언
  • JPA 어댑터 레이어에서는 도메인 enum을 참조하고, @Enumerated 필드 매핑 시 도메인 enum ↔ JPA enum 간 변환 로직(또는 AttributeConverter)을 구현하세요.
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java around
lines 53–57, the JPA layer currently defines/uses an infra enum directly causing
domain→infra coupling; create a domain-only enum class RoomStatus in
src/main/java/konkuk/thip/room/domain/value, update the JPA entity to reference
the domain enum type (not an adapter-specific enum), and implement a conversion
layer: either add a JPA AttributeConverter that maps domain RoomStatus to its DB
representation (String) and annotate the entity field with
@Convert(converter=YourRoomStatusConverter.class) or create explicit mapping
methods between the domain enum and a JPA-specific enum; finally remove any
import of adapter enum from the domain package and ensure the entity uses the
converter so persistence uses the domain enum without introducing domain→infra
dependency.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "book_id", nullable = false)
private BookJpaEntity bookJpaEntity;
Expand All @@ -68,6 +73,7 @@ public RoomJpaEntity updateFrom(Room room) {
this.endDate = room.getEndDate();
this.recruitCount = room.getRecruitCount();
this.memberCount = room.getMemberCount();
this.roomStatus = room.getRoomStatus();
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

updateFrom에서 roomStatus null 덮어쓰기 방지

room.getRoomStatus()가 null이면 @column(nullable=false) 위반 가능성이 큽니다. null일 때는 필드 유지(또는 예외)하도록 가드하세요.

적용 diff:

-        this.roomStatus = room.getRoomStatus();
+        if (room.getRoomStatus() != null) {
+            this.roomStatus = room.getRoomStatus();
+        }
+        // null을 허용하지 않으려면 다음처럼 실패 빠르게도 고려:
+        // Objects.requireNonNull(room.getRoomStatus(), "roomStatus must not be null");
📝 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
this.roomStatus = room.getRoomStatus();
// updateFrom에서 roomStatus null 덮어쓰기 방지
if (room.getRoomStatus() != null) {
this.roomStatus = room.getRoomStatus();
}
// null을 허용하지 않으려면 다음처럼 빠르게 실패시키는 방법도 고려:
// Objects.requireNonNull(room.getRoomStatus(), "roomStatus must not be null");
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/out/jpa/RoomJpaEntity.java around line
76, the updateFrom method unconditionally sets this.roomStatus =
room.getRoomStatus(), which can overwrite a non-null DB-mandated field with null
and violate @Column(nullable=false); change the assignment to check for null
first and only assign when room.getRoomStatus() != null (or alternatively throw
a ValidationException when null) so the existing roomStatus is preserved when
the incoming value is null.

return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package konkuk.thip.room.adapter.out.jpa;

public enum RoomStatus {
RECRUITING,
IN_PROGRESS,
EXPIRED
}
Comment on lines +1 to +7
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot Aug 29, 2025

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

패키지 레이어링 역참조(도메인→어댑터) 발생

Room 도메인이 adapter.out.jpa 패키지의 enum을 import합니다. 도메인이 하위 레이어를 참조하지 않도록 RoomStatus를 도메인(or common) 레이어로 승격하고, JPA 엔티티가 그 도메인 enum을 사용하도록 방향을 정리하세요.

예(이 PR 외 파일 변경 수반):

- package konkuk.thip.room.adapter.out.jpa;
+ package konkuk.thip.room.domain;

그리고 Room/RoomJpaEntity/Mapper의 import를 도메인 enum으로 교체.

📝 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
package konkuk.thip.room.adapter.out.jpa;
public enum RoomStatus {
RECRUITING,
IN_PROGRESS,
EXPIRED
}
package konkuk.thip.room.domain;
public enum RoomStatus {
RECRUITING,
IN_PROGRESS,
EXPIRED
}
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java lines 1-7, the
enum is currently placed in the adapter layer causing a domain→adapter layering
violation; move RoomStatus into the domain (or a shared common) package (e.g.,
src/main/java/konkuk/thip/room/domain/RoomStatus.java or common package), update
its package declaration accordingly, and then change all imports in Room,
RoomJpaEntity, and Mapper to reference the new domain/common enum location so
the JPA entity uses the domain enum rather than the adapter enum.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@coderabbitai enum 의존성은 유연하게 진행하기로 결정했어

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@seongjunnoh 네, 알겠습니다! enum 의존성에 대해 유연하게 접근하기로 하신 결정을 이해했어요. 실용적인 구현을 우선시하는 방향으로 진행하시는군요.

(・ω・) ♪


✏️ Learnings added
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#285
File: src/main/java/konkuk/thip/room/adapter/out/jpa/RoomStatus.java:1-7
Timestamp: 2025-08-31T05:25:14.835Z
Learning: seongjunnoh는 enum 의존성에 대해 유연한 접근을 선호하며, 도메인→어댑터 레이어 참조와 같은 아키텍처 layering 원칙보다 실용적인 구현을 우선시한다.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#113
File: src/main/java/konkuk/thip/recentSearch/adapter/out/persistence/RecentSearchCommandPersistenceAdapter.java:38-44
Timestamp: 2025-07-30T14:05:04.945Z
Learning: seongjunnoh는 코드 최적화 제안에 대해 구체적인 기술적 근거와 효율성 차이를 이해하고 싶어하며, 성능 개선 방식에 대한 상세한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#93
File: src/main/java/konkuk/thip/room/adapter/out/persistence/RoomQueryPersistenceAdapter.java:49-114
Timestamp: 2025-07-28T16:44:31.224Z
Learning: seongjunnoh는 코드 중복 문제에 대한 리팩토링 제안을 적극적으로 수용하고 함수형 인터페이스를 활용한 해결책을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#195
File: src/main/java/konkuk/thip/feed/application/mapper/FeedQueryMapper.java:0-0
Timestamp: 2025-08-13T05:22:32.287Z
Learning: seongjunnoh는 데이터 무결성과 중복 방지에 대한 고민이 깊으며, LinkedHashSet을 활용한 중복 제거와 순서 보장을 동시에 달성하는 솔루션을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#180
File: src/main/java/konkuk/thip/room/application/service/AttendanceCheckCreateService.java:22-40
Timestamp: 2025-08-14T09:15:31.371Z
Learning: seongjunnoh는 기술적 완벽성보다 실제 비즈니스 시나리오와 사용자 행동 패턴을 우선 고려하며, 발생 가능성이 낮은 엣지 케이스보다는 실용적인 구현을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/common/exception/code/ErrorCode.java:210-211
Timestamp: 2025-08-24T09:40:48.595Z
Learning: seongjunnoh는 HTTP 상태 코드 선택에 대해 기술적 근거와 코드베이스 내 일관성을 중요하게 생각하며, 구체적인 사례 분석을 통한 설명을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#278
File: src/main/java/konkuk/thip/TestTokenController.java:0-0
Timestamp: 2025-08-24T09:33:52.982Z
Learning: seongjunnoh는 Spring의 ConditionalOnProperty 동작 원리를 정확히 이해하고 있으며, 보안 이슈에 대해서도 실질적인 위험성을 기준으로 판단하는 실용적 접근을 선호한다.

Learnt from: seongjunnoh
PR: THIP-TextHip/THIP-Server#112
File: src/main/java/konkuk/thip/feed/adapter/out/persistence/repository/FeedQueryRepositoryImpl.java:272-272
Timestamp: 2025-07-30T10:44:34.115Z
Learning: seongjunnoh는 피드 커서 페이지네이션에서 LocalDateTime 단일 커서 방식을 선호하며, 복합 키 기반 커서보다 구현 단순성과 성능을 우선시한다.

Comment on lines +3 to +7
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

굳굳

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

굳굳

Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public RoomJpaEntity toJpaEntity(Room room, BookJpaEntity bookJpaEntity) {
.endDate(room.getEndDate())
.recruitCount(room.getRecruitCount())
.memberCount(room.getMemberCount())
.roomStatus(room.getRoomStatus())
.bookJpaEntity(bookJpaEntity)
.category(room.getCategory())
.build();
Expand All @@ -36,6 +37,7 @@ public Room toDomainEntity(RoomJpaEntity roomJpaEntity) {
.endDate(roomJpaEntity.getEndDate())
.recruitCount(roomJpaEntity.getRecruitCount())
.memberCount(roomJpaEntity.getMemberCount())
.roomStatus(roomJpaEntity.getRoomStatus())
.bookId(roomJpaEntity.getBookJpaEntity().getBookId())
.category(roomJpaEntity.getCategory())
.createdAt(roomJpaEntity.getCreatedAt())
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/konkuk/thip/room/domain/Room.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import konkuk.thip.common.exception.InvalidStateException;
import konkuk.thip.common.entity.StatusType;
import konkuk.thip.common.exception.code.ErrorCode;
import konkuk.thip.room.adapter.out.jpa.RoomStatus;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

도메인에서 어댑터 enum import 회피 필요

도메인 레이어가 adapter 패키지를 참조하지 않도록 RoomStatus를 도메인(or common)으로 이동시킨 뒤 import를 교체하세요.

- import konkuk.thip.room.adapter.out.jpa.RoomStatus;
+ import konkuk.thip.room.domain.RoomStatus; // enum을 도메인으로 이동했다고 가정
📝 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
import konkuk.thip.room.adapter.out.jpa.RoomStatus;
// File: src/main/java/konkuk/thip/room/domain/Room.java
import konkuk.thip.room.domain.RoomStatus;
🤖 Prompt for AI Agents
In src/main/java/konkuk/thip/room/domain/Room.java around line 7, the class
currently imports the adapter enum konkuk.thip.room.adapter.out.jpa.RoomStatus
which couples the domain to the adapter layer; move RoomStatus out of the
adapter package into the domain (or a shared common package), update the enum's
package declaration accordingly, and replace the import in Room.java with the
new package path so the domain layer no longer depends on adapter code.

import konkuk.thip.room.domain.value.Category;
import lombok.Getter;
import lombok.experimental.SuperBuilder;
Expand Down Expand Up @@ -40,6 +41,8 @@ public class Room extends BaseDomainEntity {

private int memberCount;

private RoomStatus roomStatus;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

좋습니다! 도메인 entity 도 그냥 enum 자체를 의존하는게 더 나을 것 같습니다!!

현재 도메인 entity 에서 String 타입으로 enum을 의존하는 코드들도 수정하는게 좋을듯 하네요
언제하죠 하하

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

ㅜㅜ


private Long bookId;

private Category category;
Expand All @@ -62,6 +65,7 @@ public static Room withoutId(String title, String description, boolean isPublic,
.endDate(endDate)
.recruitCount(recruitCount)
.memberCount(1) // 처음 Room 생성 시 방장 1명
.roomStatus(RoomStatus.RECRUITING) // 처음 Room 생성 시 -> 모집중
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

좋네여 LGTM

.bookId(bookId)
.category(category)
.build();
Expand Down
Empty file.
14 changes: 14 additions & 0 deletions src/main/resources/db/migration/V250828__Add_room_status.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- 1) 컬럼 추가: 기본값 RECRUITING, NOT NULL
ALTER TABLE rooms
ADD COLUMN room_status VARCHAR(32) NOT NULL DEFAULT 'RECRUITING'
COMMENT 'Room 상태: RECRUITING/IN_PROGRESS/EXPIRED';

-- 2) start_date가 현재 날짜보다 "이전"이면 IN_PROGRESS로 업데이트
UPDATE rooms
SET room_status = 'IN_PROGRESS'
WHERE start_date <= CURDATE();

-- 3) end_date가 현재 날짜보다 "이전"이면 EXPIRED로 업데이트
UPDATE rooms
SET room_status = 'EXPIRED'
WHERE end_date < CURDATE();
Comment on lines +1 to +14
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테스트(H2) 환경 호환성 주의

이 스크립트는 MySQL 전용 문법(CURDATE, COMMENT)을 사용합니다. H2에서 Flyway가 실행되면 실패할 수 있습니다. 파일을 MySQL 전용으로 분기(.mysql.sql)하거나 테스트 프로필에서 Flyway 적용을 비활성/분리하세요.

예:

  • 파일명: V250828__Add_room_status.mysql.sql 로 변경
  • (선택) H2 전용 스크립트: V250828__Add_room_status.h2.sql
🤖 Prompt for AI Agents
In src/main/resources/db/migration/V250828__Add_room_status.sql around lines
1-14: the migration uses MySQL-specific syntax (CURDATE(), COMMENT) which will
fail on H2 during tests; fix by making the migration MySQL-only (rename to
V250828__Add_room_status.mysql.sql) and either add a separate H2-compatible
migration (e.g., V250828__Add_room_status.h2.sql) with equivalent logic using H2
functions or disable/apply Flyway migrations differently under the test profile
so H2 does not try to run the MySQL script.