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: 2 additions & 2 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ jobs:

- name: Run tests
run: |
echo ">>> [CI] Running tests..."
./gradlew test --no-daemon
echo ">>> [CI] Running tests without integration tests..."
./gradlew test -PskipIntegrationTests --no-daemon
echo ">>> [CI] Tests passed"

docker-build-and-push:
Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ dependencies {

tasks.named('test') {
useJUnitPlatform()

if (project.hasProperty('skipIntegrationTests')) {
exclude '**/*IntegrationTest.class'
}
}

def generated = "$buildDir/generated/sources/annotationProcessor/java/main"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import io.hypersistence.utils.hibernate.id.Tsid;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@SQLRestriction("deleted_at is null")
@Table(
indexes = @Index(name = "idx_chat_message_room_created_message", columnList = "chat_room_no, created_at, message_no")
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@
import jakarta.persistence.Index;
import jakarta.persistence.Table;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

import java.time.LocalDateTime;

@Entity
@SQLRestriction("deleted_at is null")
@Table(
indexes = {
@Index(name = "idx_chat_room_last_message_at", columnList = "last_message_at"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.hypersistence.utils.hibernate.id.Tsid;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

import java.time.LocalDateTime;

Expand All @@ -12,14 +13,11 @@
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@SQLRestriction("deleted_at is null")
@Table(
indexes = {
@Index(name = "idx_chat_room_member_user_room", columnList = "user_no, chat_room_no")
},
uniqueConstraints = @UniqueConstraint(
name = "uk_chat_room_member_room_user",
columnNames = {"chat_room_no", "user_no"}
)
}
)
public class ChatRoomMember extends BaseEntity {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ int decreaseUnreadCount(@Param("chatRoomNo") String chatRoomNo,
@Param("fromTime") LocalDateTime fromTime,
@Param("userNo") String userNo);

@Modifying(clearAutomatically = true)
@Query("DELETE FROM ChatMessage m WHERE m.chatRoom.chatRoomNo = :chatRoomNo")
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE ChatMessage m SET m.deletedAt = CURRENT_TIMESTAMP WHERE m.chatRoom.chatRoomNo = :chatRoomNo AND m.deletedAt IS NULL")
void deleteByChatRoomNo(@Param("chatRoomNo") String chatRoomNo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,6 @@ Optional<ChatRoomMember> findByChatRoomNoAndUserNoForUpdate(
boolean existsByChatRoom_ChatRoomNoAndUserNo(String chatRoomNo, String userNo);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM ChatRoomMember m WHERE m.chatRoom = :chatRoom")
@Query("UPDATE ChatRoomMember m SET m.deletedAt = CURRENT_TIMESTAMP WHERE m.chatRoom = :chatRoom AND m.deletedAt IS NULL")
void deleteAllByChatRoom(@Param("chatRoom") ChatRoom chatRoom);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ Optional<ChatRoom> findByRoomNoAndChatRoomTypeAndApplicantUserNo(
String roomNo, ChatRoomType chatRoomType, String applicantUserNo);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM ChatRoom c WHERE c.chatRoomNo = :chatRoomNo")
@Query("UPDATE ChatRoom c SET c.deletedAt = CURRENT_TIMESTAMP WHERE c.chatRoomNo = :chatRoomNo AND c.deletedAt IS NULL")
void deleteByChatRoomNo(@Param("chatRoomNo") String chatRoomNo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,8 @@ public boolean isMemberByChatRoomNo(String chatRoomNo, String userNo) {
}

public void leave(ChatRoomMember member) {
chatRoomMemberRepository.delete(member);
member.delete();
chatRoomMemberRepository.save(member);
}

public void deleteAllByChatRoom(ChatRoom chatRoom) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ public void updateLastMessage(ChatRoom chatRoom, String content, String senderNo
}

public void delete(ChatRoom chatRoom) {
chatRoomRepository.delete(chatRoom);
chatRoom.delete();
chatRoomRepository.save(chatRoom);
}

public void deleteByChatRoomNo(String chatRoomNo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@ public abstract class ChecklistBase {
@Column(name = "updated_at", nullable = false)
private LocalDateTime updatedAt;

@Column(name = "deleted_at")
private LocalDateTime deletedAt;
Comment on lines +126 to +127

public boolean isDeleted() {
return deletedAt != null;
}

public void delete() {
deletedAt = LocalDateTime.now();
}
Comment on lines +126 to +135
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚖️ Poor tradeoff

BaseEntity와 동일한 소프트 삭제 패턴이 중복 구현되어 있습니다.

deletedAt, isDeleted(), delete()BaseEntity에 이미 동일하게 정의된 패턴입니다. ChecklistBaseBaseEntity를 상속하지 않기 때문에 부득이하게 중복이 발생했지만, 향후 두 구현이 따로 관리되면 타임존 처리나 감사(Auditing) 로직 변경 시 한 쪽이 누락될 위험이 있습니다.

ChecklistBaseBaseEntity를 상속하도록 리팩토링하거나, 소프트 삭제 로직을 별도의 @MappedSuperclass(예: SoftDeletableEntity)로 추출하는 방향을 장기적으로 검토해 보시면 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/com/project/dorumdorum/domain/checklist/domain/entity/ChecklistBase.java`
around lines 126 - 135, ChecklistBase currently reimplements the soft-delete
fields/methods (deletedAt, isDeleted(), delete()) already present in BaseEntity;
refactor by either making class ChecklistBase extend BaseEntity and removing the
duplicated deletedAt/isDeleted()/delete() members, or extract those members into
a new `@MappedSuperclass` (e.g., SoftDeletableEntity) and have ChecklistBase
extend that; ensure the `@Column`(name = "deleted_at") mapping and any
auditing/timezone handling are moved to the chosen superclass and remove the
duplicate definitions from ChecklistBase so only the superclass owns the
soft-delete behavior.


public void updateChecklist(
String bedtime,
String wakeUp,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.hibernate.annotations.SQLRestriction;

@Entity
@SQLRestriction("deleted_at is null")
@Table(
name = "room_rule",
indexes = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ public interface RoomRuleRepository extends JpaRepository<RoomRule, String> {
Optional<RoomRule> findByRoomNo(@Param("roomNo") String roomNo);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM RoomRule rr WHERE rr.room.roomNo = :roomNo")
@Query("UPDATE RoomRule rr SET rr.deletedAt = CURRENT_TIMESTAMP WHERE rr.room.roomNo = :roomNo AND rr.deletedAt IS NULL")
void deleteByRoomNo(@Param("roomNo") String roomNo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ public class DeleteRoomUseCase {
* 방 삭제
* - 방장 권한 검증
* - 방장 외 룸메이트 존재 시 삭제 불가
* - 방 소프트 삭제 → 룸메이트 퇴실 처리 → flush(deletedAt DB 반영)
* - RoomRequest, RoomRule, RoomLike 연관 데이터 삭제
* - 방 소프트 삭제 → 룸메이트 퇴실 처리
* - RoomRequest, RoomRule, RoomLike 연관 데이터 삭제 (@Modifying flushAutomatically로 자동 flush)
* - 채팅방 삭제 이벤트 발행
*/
public void execute(String requesterNo, String roomNo) {
Expand All @@ -54,7 +54,6 @@ public void execute(String requesterNo, String roomNo) {

room.delete();
roommateService.leaveRoom(requesterNo, roomNo);
roomService.flush();

roomRequestService.deleteAllByRoom(room);
roomRuleService.deleteByRoomNo(roomNo);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ public void execute(String requesterNo, String roomNo, String kickedUserNo) {

roommateService.leaveRoom(kickedUserNo, roomNo);
room.minusCurrentMate();
roomService.flush();

eventPublisher.publishEvent(new RoommateKickedEvent(roomNo, kickedUserNo));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
import io.hypersistence.utils.hibernate.id.Tsid;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@SQLRestriction("deleted_at is null")
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_room_like_user_room", columnNames = {"user_no", "room_no"})
},
indexes = {
}
)
Comment on lines 15 to 18
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

indexes 블록 정리를 고려해 주세요.

현재 인덱스가 정의되어 있지 않다면 @Table 어노테이션에서 indexes 속성을 제거하여 코드를 간결하게 유지할 수 있습니다. 다만 스키마에서 Partial Index를 별도로 관리하고 있다면 현재 상태로 두셔도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/project/dorumdorum/domain/room/domain/entity/RoomLike.java`
around lines 15 - 18, RoomLike 엔티티의 `@Table` 어노테이션에 빈 indexes 속성이 남아 있어 불필요하게
보입니다; RoomLike 클래스에서 `@Table`(...)을 찾아 indexes 속성이 비어있으면 해당 속성 전체를 제거해 어노테이션을 간결하게
하거나, 만약 DB 스키마에서 Partial Index를 별도 관리 중이라 유지해야 한다면 주석으로 이유를 명시해두세요.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@
import io.hypersistence.utils.hibernate.id.Tsid;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@SQLRestriction("deleted_at is null")
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_room_request_user_room_direction", columnNames = {"user_no", "room_no", "direction"})
},
indexes = {
@Index(name = "idx_room_request_room_created", columnList = "room_no, created_at")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
public interface RoomLikeRepository extends JpaRepository<RoomLike, String> {

boolean existsByUserNoAndRoom(String userNo, Room room);
void deleteByUserNoAndRoom(String userNo, Room room);

// N+1 DELETE 문제 — @Modifying JPQL 벌크 DELETE로 교체
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM RoomLike l WHERE l.room = :room")
@Query("UPDATE RoomLike l SET l.deletedAt = CURRENT_TIMESTAMP WHERE l.userNo = :userNo AND l.room = :room AND l.deletedAt IS NULL")
void deleteByUserNoAndRoom(@Param("userNo") String userNo, @Param("room") Room room);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("UPDATE RoomLike l SET l.deletedAt = CURRENT_TIMESTAMP WHERE l.room = :room AND l.deletedAt IS NULL")
void deleteAllByRoom(@Param("room") Room room);
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@ public interface RoomRequestRepository extends JpaRepository<RoomRequest, String
boolean existsByUserNoAndRoom(String userNo, Room room);
Optional<RoomRequest> findByUserNoAndRoomAndDirection(String userNo, Room room, Direction direction);

// N+1 DELETE 문제 — @Modifying JPQL 벌크 DELETE로 교체
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("DELETE FROM RoomRequest r WHERE r.room = :room")
@Query("UPDATE RoomRequest r SET r.deletedAt = CURRENT_TIMESTAMP WHERE r.room = :room AND r.deletedAt IS NULL")
void deleteAllByRoom(@Param("room") Room room);
}

Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public RoomRequest findById(String requestNo) {
}

public void delete(RoomRequest entity) {
roomRequestRepository.delete(entity);
entity.delete();
roomRequestRepository.save(entity);
}

public List<RoomRequestApplicationResponse> findApplicationsByRoom(Room room) {
Expand All @@ -54,7 +55,8 @@ public void cancelJoinRequest(String userNo, Room room) {
.findByUserNoAndRoomAndDirection(userNo, room, Direction.USER_TO_ROOM)
.orElseThrow(() -> new RestApiException(ROOM_REQUEST_NOT_FOUND));

roomRequestRepository.delete(request);
request.delete();
roomRequestRepository.save(request);
}

public void deleteAllByRoom(Room room) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,6 @@ public FindRoomsResponse findMyRoom(String userNo) {
.orElseThrow(() -> new RestApiException(ROOM_NOT_FOUND));
}

public void flush() {
roomRepository.flush();
}

public List<FindRoomsResponse> findLikedRooms(String userNo) {
return roomRepository.findLikedRooms(userNo);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
import io.hypersistence.utils.hibernate.id.Tsid;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.SQLRestriction;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder
@SQLRestriction("deleted_at is null")
@Table(
uniqueConstraints = {
@UniqueConstraint(name = "uk_roommate_user_no", columnNames = "user_no")
},
indexes = {
@Index(name = "idx_roommate_room_no", columnList = "room_no")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public boolean isHostOfRoom(String userNo, String roomNo) {
public void leaveRoom(String userNo, String roomNo) {
Roommate roommate = roommateRepository.findByUserNoAndRoomNo(userNo, roomNo)
.orElseThrow(() -> new RestApiException(_NOT_FOUND));
// uk_roommate_user_no 유니크 제약으로 인해 소프트 삭제 불가 — 하드 삭제 유지
roommateRepository.delete(roommate);
roommate.delete();
roommateRepository.save(roommate);
Comment on lines 66 to +70
}

public List<MyRoommateResponse> findMyRoommates(String userNo) {
Expand Down
29 changes: 24 additions & 5 deletions src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,34 @@ CREATE UNIQUE INDEX IF NOT EXISTS uk_user_email
CREATE UNIQUE INDEX IF NOT EXISTS uk_user_student_no
ON users (student_no);

ALTER TABLE IF EXISTS room_rule ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP;

ALTER TABLE IF EXISTS roommate DROP CONSTRAINT IF EXISTS uk_roommate_user_no;
DROP INDEX IF EXISTS uk_roommate_user_no;
CREATE UNIQUE INDEX IF NOT EXISTS uk_roommate_user_no
ON roommate (user_no);
ON roommate (user_no)
WHERE deleted_at IS NULL;

ALTER TABLE IF EXISTS room_like DROP CONSTRAINT IF EXISTS uk_room_like_user_room;
DROP INDEX IF EXISTS uk_room_like_user_room;
CREATE UNIQUE INDEX IF NOT EXISTS uk_room_like_user_room
ON room_like (user_no, room_no);
ON room_like (user_no, room_no)
WHERE deleted_at IS NULL;

CREATE UNIQUE INDEX IF NOT EXISTS uk_device_user_device
ON devices (user_no, device_id);

ALTER TABLE IF EXISTS room_request DROP CONSTRAINT IF EXISTS uk_room_request_user_room_direction;
DROP INDEX IF EXISTS uk_room_request_user_room_direction;
CREATE UNIQUE INDEX IF NOT EXISTS uk_room_request_user_room_direction
ON room_request (user_no, room_no, direction);
ON room_request (user_no, room_no, direction)
WHERE deleted_at IS NULL;

ALTER TABLE IF EXISTS chat_room_member DROP CONSTRAINT IF EXISTS uk_chat_room_member_room_user;
DROP INDEX IF EXISTS uk_chat_room_member_room_user;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_member_room_user
ON chat_room_member (chat_room_no, user_no)
WHERE deleted_at IS NULL;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

ALTER TABLE IF EXISTS users
ALTER COLUMN gender SET NOT NULL;
Expand All @@ -30,13 +47,15 @@ CREATE INDEX IF NOT EXISTS idx_room_status_gender_remaining_created
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;

DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP';
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;

DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT';
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
Comment on lines 47 to +58
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

uk_chat_room_group도 방어적으로 DROP CONSTRAINT IF EXISTS를 함께 추가하는 것이 어떨까요?

라인 9-34에서 적용한 패턴(이전 리뷰 반영)과 비교했을 때, chat_room의 경우 uk_chat_room_direct는 라인 47-48에서 DROP CONSTRAINT, 라인 55에서 DROP INDEX로 양쪽 케이스를 모두 처리하지만, uk_chat_room_group은 라인 50의 DROP INDEX IF EXISTS만 존재합니다.

현재 코드베이스 기준으로는 GROUP 인덱스가 INDEX 형태로만 존재했을 가능성이 높지만, 과거 마이그레이션 이력이나 환경별 차이를 고려하면 direct와 동일하게 양쪽 모두 방어적으로 처리해두는 편이 일관성 측면에서도, 운영 안정성 측면에서도 더 좋을 것 같습니다.

🛡️ 제안 수정
 ALTER TABLE IF EXISTS chat_room
-    DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
+    DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
+ALTER TABLE IF EXISTS chat_room
+    DROP CONSTRAINT IF EXISTS uk_chat_room_group;

 DROP INDEX IF EXISTS uk_chat_room_group;
 CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
     ON chat_room (room_no)
     WHERE chat_room_type = 'GROUP' AND deleted_at IS 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
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP';
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;
DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT';
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_direct;
ALTER TABLE IF EXISTS chat_room
DROP CONSTRAINT IF EXISTS uk_chat_room_group;
DROP INDEX IF EXISTS uk_chat_room_group;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_group
ON chat_room (room_no)
WHERE chat_room_type = 'GROUP' AND deleted_at IS NULL;
DROP INDEX IF EXISTS uk_chat_room_direct;
CREATE UNIQUE INDEX IF NOT EXISTS uk_chat_room_direct
ON chat_room (room_no, applicant_user_no)
WHERE chat_room_type = 'DIRECT' AND deleted_at IS NULL;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/resources/schema.sql` around lines 47 - 58, The GROUP unique must be
defensively dropped like DIRECT: add a DROP CONSTRAINT IF EXISTS
uk_chat_room_group before (or alongside) the existing DROP INDEX IF EXISTS
uk_chat_room_group in the chat_room migration SQL so both constraint and index
removal are handled (mirror the pattern used for uk_chat_room_direct); update
the block that references uk_chat_room_group to include that DROP CONSTRAINT IF
EXISTS statement to ensure consistency and safety across environments.


CREATE INDEX IF NOT EXISTS idx_chat_room_room_no
ON chat_room (room_no);
Loading
Loading