diff --git a/backend/build.gradle b/backend/build.gradle index 575981e..7aa086d 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -34,6 +34,7 @@ dependencies { // Flyway + implementation 'org.springframework.boot:spring-boot-flyway' implementation 'org.flywaydb:flyway-core' implementation 'org.flywaydb:flyway-database-postgresql' diff --git a/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/Assignment.java b/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/Assignment.java index 089f951..b439938 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/Assignment.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/Assignment.java @@ -1,8 +1,8 @@ package com.example.Piroin.project.domain.assignment.entity; -import com.example.Piroin.project.domain.curriculum.entity.StudySession; import jakarta.persistence.*; import lombok.*; +import java.time.LocalDate; @Entity @Table(name = "assignment") @@ -14,15 +14,15 @@ public class Assignment { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "session_id") - private StudySession session; + private Integer id; // SERIAL 타입에 매칭 @Column(nullable = false) private String title; - private String content; -} + @Column(length = 255) + private String week; + + @Column(name = "session_date") + private LocalDate sessionDate; // DATE 타입에 매칭 +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/AssignmentItem.java b/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/AssignmentItem.java index 1203f55..b81e9d3 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/AssignmentItem.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/assignment/entity/AssignmentItem.java @@ -23,7 +23,7 @@ public class AssignmentItem { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Integer id; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AdminAttendanceController.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AdminAttendanceController.java index 11fb13a..7416663 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AdminAttendanceController.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AdminAttendanceController.java @@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDate; +import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; @@ -33,8 +34,17 @@ public class AdminAttendanceController { @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "잘못된 요청") }) @PostMapping("/admin/attendance/start") - public AttendanceCodeResponse startAttendance(@PathVariable Long studySessionId) { - AttendanceCode code = attendanceService.generateCodeAndCreateAttendances(studySessionId); + public AttendanceCodeResponse startAttendance() { + + // 1. 오늘 날짜 구하기 (LocalDate 활용) + LocalDate today = LocalDate.now(); + + // 2. 서비스가 원하는 "yyyy-MM-dd" 형식의 문자열로 포맷팅 + String todayStr = today.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")); + + // 3. 포맷팅된 오늘 날짜 문자열을 서비스에 넘겨줍니다. + AttendanceCode code = attendanceService.generateCodeAndCreateAttendances(todayStr); + return AttendanceCodeResponse.from(code); } @@ -71,7 +81,7 @@ public String expireActiveAttendance() { @PutMapping("/admin/users/{userId}/status") public boolean updateUserStatus( @Parameter(description = "사용자 ID", example = "1") - @PathVariable Long userId, + @PathVariable Integer userId, @RequestBody UpdateUserStatusReq req) { return attendanceService.updateUserStatus(userId, req); } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AttendanceController.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AttendanceController.java index 30a4fce..477476a 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AttendanceController.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/controller/AttendanceController.java @@ -44,11 +44,11 @@ public ApiResponse markAttendance( content = @Content(schema = @Schema(implementation = MarkAttendanceReq.class)) ) @RequestBody MarkAttendanceReq req, - @AuthenticationPrincipal Long userId + @AuthenticationPrincipal Integer userId ) { + // [수정] 서비스 메서드 스펙 변경에 맞춰 req.getStudySessionId()를 제거했습니다. AttendanceMarkResponse response = attendanceService.markAttendance( - userId, - req.getStudySessionId(), + Long.valueOf(userId), req.getCode() ); @@ -77,7 +77,7 @@ public ApiResponse markAttendance( }) @GetMapping("/user") public ApiResponse> getAttendanceByUserId(@AuthenticationPrincipal Long userId) { - return ApiResponse.success(attendanceService.findByUserId(userId)); + return ApiResponse.success(attendanceService.findByUserId(Math.toIntExact(userId))); } // 3. 특정 유저의 특정 일자 출석 정보 @@ -93,7 +93,7 @@ public ApiResponse> getAttendanceByUserIdAndDate( @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, @AuthenticationPrincipal Long userId ) { - return ApiResponse.success(attendanceService.findByUserIdAndDate(userId, date)); + return ApiResponse.success(attendanceService.findByUserIdAndDate(Math.toIntExact(userId), date)); } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/AttendanceSlotRes.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/AttendanceSlotRes.java index 44daaf8..ee842b1 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/AttendanceSlotRes.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/AttendanceSlotRes.java @@ -1,7 +1,6 @@ package com.example.Piroin.project.domain.attendance.dto; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; @@ -9,13 +8,15 @@ @Setter @Schema(description = "출석 차시별 상태") public class AttendanceSlotRes { - private Long studySessionId; + + @Schema(description = "출석 코드 ID") + private Integer attendanceCodeId; // 변수명을 의미에 맞게 변경! + private Boolean status; - public AttendanceSlotRes(Long studySessionId, Boolean status) { - this.studySessionId = studySessionId; + // 생성자 파라미터와 주입부도 변경 + public AttendanceSlotRes(Integer attendanceCodeId, Boolean status) { + this.attendanceCodeId = attendanceCodeId; this.status = status; } - -} - +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/MarkAttendanceReq.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/MarkAttendanceReq.java index f815067..d777a57 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/MarkAttendanceReq.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/dto/MarkAttendanceReq.java @@ -8,8 +8,8 @@ @NoArgsConstructor @Schema(description = "출석 체크 요청") public class MarkAttendanceReq { - @Schema(description = "스터디 세션 ID", example = "1") - private Long studySessionId; +// @Schema(description = "스터디 세션 ID", example = "1") +// private Long studySessionId; @Schema(description = "출석 코드", example = "1234") private String code; diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/Attendance.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/Attendance.java index 2411395..683e7a2 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/Attendance.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/Attendance.java @@ -1,20 +1,13 @@ package com.example.Piroin.project.domain.attendance.entity; -import com.example.Piroin.project.domain.curriculum.entity.StudySession; import com.example.Piroin.project.domain.user.entity.User; import jakarta.persistence.*; import lombok.*; +import javax.xml.crypto.dsig.Manifest; + @Entity -@Table( - name = "attendance", - uniqueConstraints = { - @UniqueConstraint( - name = "uq_attendance_user_session", - columnNames = {"user_id", "study_session_id"} - ) - } -) +@Table(name = "attendance") @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor @@ -23,22 +16,22 @@ public class Attendance { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Integer id; // SERIAL 타입에 매칭 (Long -> Integer) @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "user_id", nullable = false) - private User user; + @JoinColumn(name = "attendance_code_id", nullable = false) + private AttendanceCode attendanceCode; // attendance_code_id 매핑 @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_session_id") - private StudySession studySession; + @JoinColumn(name = "user_id", nullable = false) + private User user; // user_id 매핑 @Column(nullable = false) - private Boolean status; + private Boolean status; // BOOLEAN 타입에 매칭 public void updateStatus(Boolean status) { this.status = status; } -} +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/AttendanceCode.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/AttendanceCode.java index acb4f4a..efe7f82 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/AttendanceCode.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/entity/AttendanceCode.java @@ -1,6 +1,5 @@ package com.example.Piroin.project.domain.attendance.entity; -import com.example.Piroin.project.domain.curriculum.entity.StudySession; import jakarta.persistence.*; import lombok.*; @@ -14,20 +13,24 @@ public class AttendanceCode { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Integer id; // SERIAL 타입에 매칭 (Long -> Integer) - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_session_id") - private StudySession studySession; + @Column(name = "attendance_date") + private String attendanceDate; + + @Column(name = "attendance_order") + private String attendanceOrder; // '1, 2, 3' 코멘트 항목 @Column(nullable = false, length = 20) private String code; @Column(name = "is_expired", nullable = false) - private Boolean isExpired; + private Boolean isExpired; // BOOLEAN 타입에 매칭 + + @Column(name = "field3") + private String field3; public void expire() { this.isExpired = true; } -} - +} \ No newline at end of file diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceCodeRepository.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceCodeRepository.java index 7666ce0..6002531 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceCodeRepository.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceCodeRepository.java @@ -14,16 +14,6 @@ public interface AttendanceCodeRepository extends JpaRepository { - @Query(""" - select count(ac) - from AttendanceCode ac - where ac.studySession.sessionDate = :date - """) - int countByStudySessionDate(@Param("date") LocalDate date); - - List findByIsExpiredFalse(); - - // [추가] 모든 활성화된 코드를 한 번에 만료 처리 (벌크 연산) @Modifying @Query("update AttendanceCode ac set ac.isExpired = true where ac.isExpired = false") @@ -31,12 +21,15 @@ select count(ac) Optional findFirstByIsExpiredFalseOrderByIdDesc(); - Optional findByCodeAndIsExpiredFalse(String code); +// Optional findByCodeAndStudySessionId(String code, Long studySessionId); - List findByStudySessionId(Long studySessionId); + // 특정 날짜에 발급된 코드 개수 조회 + long countByAttendanceDate(String attendanceDate); - Optional findByCodeAndStudySessionId(String code, Long studySessionId); + // 만료되지 않은 코드 목록 조회 + List findByIsExpiredFalse(); + Optional findByCode(String code); } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceRepository.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceRepository.java index fcbad59..f027731 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceRepository.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/repository/AttendanceRepository.java @@ -1,8 +1,11 @@ package com.example.Piroin.project.domain.attendance.repository; +import com.example.Piroin.project.domain.attendance.entity.AttendanceCode; import com.example.Piroin.project.domain.user.entity.User; import com.example.Piroin.project.domain.attendance.entity.Attendance; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDate; @@ -11,19 +14,33 @@ public interface AttendanceRepository extends JpaRepository { - List findByUserId(Long userId); - - Optional findByUserIdAndStudySessionId(Long userId, Long studySessionId); + // List findByUserId(Long userId); - boolean existsByUserIdAndStudySessionId(Long userId, Long studySessionId); + //Optional findByUserIdAndStudySessionId(Integer userId, Long studySessionId); - List findByStudySessionId(Long studySessionId); - List findByStudySessionIdAndStatusFalse(Long studySessionId); + // 연관관계 필드명이 attendanceCode 라면 내부 ID인 Id를 조합하여 명명 + Optional findByUserIdAndAttendanceCodeId(Long userId, Long attendanceCodeId); - List findByUserIdAndStudySessionSessionDate(Long userId, LocalDate date); + //List findByUserIdAndStudySessionSessionDate(Integer userId, LocalDate date); int countByUserAndStatusFalse(User user); + + // 1. 특정 출석 코드 ID에 해당하는 결석 데이터 조회 + List findByAttendanceCodeIdAndStatusFalse(Integer attendanceCodeId); + + // 2. 특정 유저 ID와 출석 코드의 날짜 조건으로 조회 (엔티티 그래프 참조: attendanceCode.attendanceDate) + @Query("SELECT a FROM Attendance a WHERE a.user.id = :userId AND a.attendanceCode.attendanceDate = :attendanceDate") + List findByUserIdAndDate(@Param("userId") Integer userId, @Param("attendanceDate") String attendanceDate); + + // 3. 특정 유저의 모든 출석 데이터 조회 + List findByUserId(Long userId); + + // 특정 날짜에 발급된 출석 코드의 개수를 세는 메서드 + //long countByAttendanceDate(String attendanceDate); + + // 현재 만료되지 않은(활성화된) 출석 코드 목록을 가져오는 메서드 + //List findByIsExpiredFalse(); } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/attendance/service/AttendanceService.java b/backend/src/main/java/com/example/Piroin/project/domain/attendance/service/AttendanceService.java index 690e97d..b0483a4 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/attendance/service/AttendanceService.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/attendance/service/AttendanceService.java @@ -48,48 +48,50 @@ public class AttendanceService { // 1. 출석 시작 코드 (출석코드 생성 함수) - // 출석 시작은 이제 date/order가 아니라 studySessionId를 받아야 함. @Transactional - public AttendanceCode generateCodeAndCreateAttendances(Long studySessionId) { - StudySession studySession = curriculumRepository.findById(studySessionId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); + public AttendanceCode generateCodeAndCreateAttendances(String dateStr) { // [수정] 세션 ID 대신 날짜를 직접 받음 - LocalDate sessionDate = studySession.getDate(); + // 1. [삭제] 더 이상 세션을 조회해서 날짜를 파싱할 필요가 없습니다. (curriculumRepository 조회 제거) - int codeCountOfDay = attendanceCodeRepository.countByStudySessionDate(sessionDate); + // 2. 해당 날짜에 생성된 출석 코드 개수 조회 + long codeCountOfDay = attendanceCodeRepository.countByAttendanceDate(dateStr); if (codeCountOfDay >= 3) { throw new IllegalStateException("하루에 최대 3회까지만 출석 코드를 생성할 수 있습니다."); } + // 3. 기존 활성화된 코드들 만료 처리 List activeCodes = attendanceCodeRepository.findByIsExpiredFalse(); - for (AttendanceCode activeCode : activeCodes) { activeCode.expire(); } + // 4. 4자리 랜덤 코드 생성 및 차수(Order) 계산 String code = String.valueOf(ThreadLocalRandom.current().nextInt(1000, 10000)); + String attendanceOrder = String.valueOf(codeCountOfDay + 1); // 1회차, 2회차, 3회차 + // 5. 새로운 AttendanceCode 생성 및 저장 AttendanceCode attendanceCode = AttendanceCode.builder() - .studySession(studySession) + .attendanceDate(dateStr) // [수정] 파라미터로 받은 날짜 주입 + .attendanceOrder(attendanceOrder) .code(code) .isExpired(false) .build(); attendanceCodeRepository.save(attendanceCode); + // 6. 모든 MEMBER 유저에 대해 '현재 생성된 출석 코드' 기준 초기 출석 데이터 생성 List users = userRepository.findByRole(Role.MEMBER); for (User user : users) { - if (!attendanceRepository.existsByUserIdAndStudySessionId(user.getId(), studySessionId)) { - Attendance attendance = Attendance.builder() - .user(user) - .studySession(studySession) - .status(false) - .build(); - - attendanceRepository.save(attendance); - } + // [확인] 이미 완벽하게 studySession 대신 attendanceCode를 주입하도록 잘 짜두셨습니다! + Attendance attendance = Attendance.builder() + .user(user) + .attendanceCode(attendanceCode) + .status(false) + .build(); + + attendanceRepository.save(attendance); } return attendanceCode; @@ -104,13 +106,13 @@ public Optional getActiveAttendanceCode() { // 3. 출석 체크 @Transactional - public AttendanceMarkResponse markAttendance(Long userId, Long studySessionId, String inputCode) { - // 사용자가 입력한 코드가 이 세션의 코드가 맞는지 확인 + public AttendanceMarkResponse markAttendance(Long userId, String inputCode) { + // 1. [수정] 오직 사용자가 입력한 코드를 기반으로 출석 코드 정보를 조회합니다. AttendanceCode code = attendanceCodeRepository - .findByCodeAndStudySessionId(inputCode, studySessionId) + .findByCode(inputCode) .orElse(null); - // 입력한 출석 코드가 해당 세션의 출석 코드와 일치하지 않는 경우 + // 입력한 출석 코드가 DB에 존재하지 않는 경우 if (code == null) { return AttendanceMarkResponse.invalidCode(); } @@ -120,11 +122,14 @@ public AttendanceMarkResponse markAttendance(Long userId, Long studySessionId, S return AttendanceMarkResponse.codeExpired(); } + // 2. [수정] 이제 Attendance도 studySessionId 대신 AttendanceCode와의 연관관계(예: attendanceCodeId) + // 혹은 조회된 code의 날짜/차수 정보를 기반으로 기존 출석 기록을 찾아야 합니다. + // (여기서는 이전 답변 시나리오 1인 'attendanceCodeId'로 매핑했다고 가정했을 때의 예시입니다.) Attendance attendance = attendanceRepository - .findByUserIdAndStudySessionId(userId, studySessionId) + .findByUserIdAndAttendanceCodeId(userId, Long.valueOf(code.getId())) .orElse(null); - // 해당 사용자와 세션에 대한 출석 기록이 존재하지 않는 경우 + // 해당 사용자와 출석 코드에 대한 출석 기록이 존재하지 않는 경우 if (attendance == null) { return AttendanceMarkResponse.error("출석 정보를 찾을 수 없습니다."); } @@ -133,11 +138,12 @@ public AttendanceMarkResponse markAttendance(Long userId, Long studySessionId, S if (Boolean.TRUE.equals(attendance.getStatus())) { return AttendanceMarkResponse.alreadyMarked(); } + // 출석 상태를 출석 완료(true)로 변경 attendance.updateStatus(true); // 출석 상태 변경 후 보증금 재계산 - depositService.recalculateDeposit(userId); // 아직 recalculateDeposit 부분 생성 안 해서 오류 나는 게 정상. + depositService.recalculateDeposit(userId); // 아직 생성 안 하신 부분 오류 패스! return AttendanceMarkResponse.success(); } @@ -146,17 +152,20 @@ public AttendanceMarkResponse markAttendance(Long userId, Long studySessionId, S // 4. 출석 코드 만료시키기. @Transactional public String expireActiveAttendanceCode() { + // 1. 활성화된 최신 출석 코드 조회 AttendanceCode activeCode = attendanceCodeRepository .findFirstByIsExpiredFalseOrderByIdDesc() .orElseThrow(() -> new IllegalStateException("현재 활성화된 출석 코드가 없습니다.")); + // 2. 코드 만료 처리 activeCode.expire(); - Long studySessionId = activeCode.getStudySession().getId(); - + // 3. 변경된 구조: 만료된 '출석 코드의 ID'를 기반으로 결석자(status = false) 조회 + Integer attendanceCodeId = activeCode.getId(); List absents = - attendanceRepository.findByStudySessionIdAndStatusFalse(studySessionId); + attendanceRepository.findByAttendanceCodeIdAndStatusFalse(attendanceCodeId); + // 4. 결석자 대상 보증금 재계산 (User ID 타입 Integer 반영) for (Attendance attendance : absents) { depositService.recalculateDeposit(attendance.getUser().getId()); } @@ -166,38 +175,44 @@ public String expireActiveAttendanceCode() { // 5. 유저의 특정 날짜의 출석 현황을 조회하는 함수 - public List findByUserIdAndDate(Long userId, LocalDate date) { + public List findByUserIdAndDate(Integer userId, LocalDate date) { // Long -> Integer + // DB의 VARCHAR(255) 날짜 포맷과 맞추기 위해 String으로 변환 (예: "2026-05-17") + String dateStr = date.toString(); + // 변경된 구조: User ID와 AttendanceCode의 날짜 조건으로 조회 List attendances = - attendanceRepository.findByUserIdAndStudySessionSessionDate(userId, date); + attendanceRepository.findByUserIdAndDate(userId, dateStr); return attendances.stream() .map(attendance -> new AttendanceSlotRes( - attendance.getStudySession().getId(), // 임시로 세션 ID를 슬롯 식별값으로 사용 - attendance.getStatus() // Boolean getter는 isStatus()가 아니라 getStatus() + attendance.getAttendanceCode().getId(), // 세션 ID 대신 출석 코드 ID를 슬롯 식별값으로 사용 + attendance.getStatus() )) - .sorted(Comparator.comparing(AttendanceSlotRes::getStudySessionId)) + .sorted(Comparator.comparing(AttendanceSlotRes::getAttendanceCodeId)) // 정렬 기준 변경 .toList(); } - public List findByUserId(Long userId) { - List attendances = attendanceRepository.findByUserId(userId); + // 6. 유저의 전체 출석 현황을 날짜별로 묶어서 조회하는 함수 + public List findByUserId(Integer userId) { // Long -> Integer + List attendances = attendanceRepository.findByUserId(Long.valueOf(userId)); - Map> grouped = attendances.stream() + // 변경된 구조: AttendanceCode에 저장된 String 날짜를 기준으로 그룹화(groupingBy) + Map> grouped = attendances.stream() .collect(Collectors.groupingBy( - attendance -> attendance.getStudySession().getSessionDate() + attendance -> attendance.getAttendanceCode().getAttendanceDate() )); return grouped.entrySet().stream() .map(entry -> { - LocalDate date = entry.getKey(); + // String으로 정렬/그룹화된 키를 다시 LocalDate 객체로 변환하여 DTO에 주입 + LocalDate date = LocalDate.parse(entry.getKey()); List slots = entry.getValue().stream() .map(attendance -> new AttendanceSlotRes( - attendance.getStudySession().getId(), + attendance.getAttendanceCode().getId(), attendance.getStatus() )) - .sorted(Comparator.comparing(AttendanceSlotRes::getStudySessionId)) + .sorted(Comparator.comparing(AttendanceSlotRes::getAttendanceCodeId)) .toList(); AttendanceStatusRes dto = new AttendanceStatusRes(); @@ -206,14 +221,14 @@ public List findByUserId(Long userId) { return dto; }) - .sorted(Comparator.comparing(AttendanceStatusRes::getDate).reversed()) + .sorted(Comparator.comparing(AttendanceStatusRes::getDate).reversed()) // 최신날짜 순 정렬 .toList(); } // 6. 유저 상태 변경 (관리자) // 컨트롤러 부분은 출석만 받는데 여기는 출석&과제 둘 다 받아서 추후에 수정 예정 @Transactional - public boolean updateUserStatus(Long userId, UpdateUserStatusReq req) { + public boolean updateUserStatus(Integer userId, UpdateUserStatusReq req) { boolean updated = false; // 출석 상태 변경 코드 @@ -244,7 +259,7 @@ public boolean updateUserStatus(Long userId, UpdateUserStatusReq req) { // 출석 변경 → 보증금 재계산 (과제 변경도 포함이 되어 있나..?) if (updated) { - depositService.recalculateDeposit(userId); + depositService.recalculateDeposit(Long.valueOf(userId)); } return updated; diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/repository/CurriculumRepository.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/repository/CurriculumRepository.java index 8989cd9..aea0b3f 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/repository/CurriculumRepository.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/repository/CurriculumRepository.java @@ -11,7 +11,7 @@ Q&A 서비스에서 세션 존재 여부 확인 시 사용 JpaRepository<엔티티 타입, PK 타입> 을 상속하면 findById, save, delete 등 기본 메서드가 자동으로 제공 */ -public interface CurriculumRepository extends JpaRepository { +public interface CurriculumRepository extends JpaRepository { List findByStatusOrderBySessionDateAscDayPartAsc(SessionStatus status); List findByStatusOrderBySessionDateDescDayPartDesc(SessionStatus status); diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/service/CurriculumService.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/service/CurriculumService.java index 6599d79..f53e27c 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/service/CurriculumService.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/service/CurriculumService.java @@ -50,7 +50,7 @@ public CurriculumResDTO.CreateSessionRes createSession(CurriculumReqDTO.CreateSe @Transactional public CurriculumResDTO.UpdateSessionRes updateSession(Long sessionId, CurriculumReqDTO.UpdateSessionReq req) { - StudySession session = curriculumRepository.findById(sessionId) + StudySession session = curriculumRepository.findById(Math.toIntExact(sessionId)) .orElseThrow(() -> new CurriculumException(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다.")); session.update(req.getGeneration(), req.getWeek(), req.getSessionDate(), req.getDayPart(), @@ -63,7 +63,7 @@ public CurriculumResDTO.UpdateSessionRes updateSession(Long sessionId, Curriculu @Transactional public void deleteSession(Long sessionId) { - StudySession session = curriculumRepository.findById(sessionId) + StudySession session = curriculumRepository.findById(Math.toIntExact(sessionId)) .orElseThrow(() -> new CurriculumException(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다.")); curriculumRepository.delete(session); diff --git a/backend/src/main/java/com/example/Piroin/project/domain/deposit/entity/Deposit.java b/backend/src/main/java/com/example/Piroin/project/domain/deposit/entity/Deposit.java index 79cea9e..2fee4c3 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/deposit/entity/Deposit.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/deposit/entity/Deposit.java @@ -22,7 +22,7 @@ public class Deposit { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Integer id; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id", nullable = false) diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java b/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java index 99f1cec..49ae66c 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/controller/QuestionController.java @@ -44,10 +44,10 @@ public ResponseEntity> getQue public ResponseEntity> createQuestion( @PathVariable Long sessionId, @RequestBody QuestionReqDTO.CreateReq request, - @AuthenticationPrincipal Long userId + @AuthenticationPrincipal Integer userId ) { return ResponseUtil.success(QuestionSuccessCode.QUESTION_CREATED, - questionService.createQuestion(sessionId, request, userId)); + questionService.createQuestion(sessionId, request, Long.valueOf(userId))); } // 댓글/대댓글 등록 diff --git a/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java b/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java index a788c6a..c79fd40 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/question/service/QuestionService.java @@ -269,7 +269,7 @@ private Question findQuestion(Long questionId) { } private StudySession findSession(Long sessionId) { - return curriculumRepository.findById(sessionId) + return curriculumRepository.findById(Math.toIntExact(sessionId)) .orElseThrow(() -> new QuestionException(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다.")); } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 713cb76..a839863 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -4,19 +4,20 @@ jwt: spring: datasource: - url: ${DB_URL} - username: ${DB_USER} - password: ${DB_PASSWORD} + url: jdbc:postgresql://${RDS_ENDPOINT}:5432/${RDS_DB_NAME} + username: ${RDS_USERNAME} + password: ${RDS_PASSWORD} driver-class-name: org.postgresql.Driver + flyway: +# repair-on-migrate: true + enabled: true + jpa: hibernate: - # create: 실행할 때마다 테이블을 새로 만듦 (연습용) - # update: 변경된 부분만 수정해서 반영 (개발용 권장) - ddl-auto: update - show-sql: true # 콘솔에 SQL 문이 찍히게 설정 + ddl-auto: validate + show-sql: true properties: hibernate: format_sql: true - dialect: org.hibernate.dialect.PostgreSQLDialect packagesToScan: com.example.Piroin.project.domain diff --git a/backend/src/main/resources/db/migration/V1__init.sql b/backend/src/main/resources/db/migration/V1__init.sql index c5d41bb..00f9043 100644 --- a/backend/src/main/resources/db/migration/V1__init.sql +++ b/backend/src/main/resources/db/migration/V1__init.sql @@ -1,210 +1,151 @@ -CREATE TYPE role_enum AS ENUM ('ADMIN', 'MEMBER'); - -CREATE TYPE session_day_part_enum AS ENUM ('AM', 'PM'); - -CREATE TYPE session_status_enum AS ENUM ( - 'BEFORE_SESSION', - 'IN_SESSION', - 'AFTER_SESSION' -); - -CREATE TYPE assignment_status_enum AS ENUM ( - 'SUCCESS', - 'INSUFFICIENT', - 'FAILURE' -); - -CREATE TYPE understanding_choice_enum AS ENUM ( - 'UNDERSTOOD', - 'NOT_UNDERSTOOD' -); +-- 1. 테이블 생성 (기본키 포함) CREATE TABLE users ( - id BIGINT NOT NULL, - password VARCHAR(255) NOT NULL, - name VARCHAR(100) NOT NULL, - email VARCHAR(255), - phone VARCHAR(50), - role role_enum NOT NULL DEFAULT 'MEMBER', - generation INT, - CONSTRAINT pk_users PRIMARY KEY (id) + id BIGSERIAL NOT NULL, + password VARCHAR(100) NOT NULL, + name VARCHAR(100) NOT NULL, + email VARCHAR(255) NULL, + phone VARCHAR(50) NULL, + role VARCHAR(20) NOT NULL DEFAULT 'MEMBER', -- ENUM 대신 VARCHAR + CHECK 제약 조건 활용 + generation INT NULL, + CONSTRAINT PK_USERS PRIMARY KEY (id), + CONSTRAINT CHK_USERS_ROLE CHECK (role IN ('ADMIN', 'MEMBER')) ); CREATE TABLE study_session ( - id BIGINT NOT NULL, - created_by BIGINT NOT NULL, - generation INT NOT NULL, - week BIGINT NOT NULL, - session_date DATE NOT NULL, - day_part session_day_part_enum NOT NULL, - title VARCHAR(255) NOT NULL, - host_name VARCHAR(100), - status session_status_enum NOT NULL DEFAULT 'BEFORE_SESSION', - description TEXT, - session_material_url TEXT, - assignment_url TEXT, - recording_url TEXT, - created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT pk_study_session PRIMARY KEY (id), - CONSTRAINT fk_study_session_created_by - FOREIGN KEY (created_by) REFERENCES users (id) + id BIGSERIAL NOT NULL, + created_by BIGINT NOT NULL, + generation INT NULL, + week BIGINT NOT NULL, + session_date DATE NOT NULL, + day_part VARCHAR(10) NOT NULL, + title VARCHAR(255) NOT NULL, + host_name VARCHAR(100) NOT NULL, + status VARCHAR(30) NOT NULL DEFAULT 'BEFORE_SESSION', + description VARCHAR(1000) NULL, + session_material_name VARCHAR(255) NULL, + session_material_url VARCHAR(1000) NULL, + assignment_name VARCHAR(255) NULL, + assignment_url VARCHAR(1000) NULL, + recording_url VARCHAR(1000) NULL, + recording_password VARCHAR(60) NULL, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT PK_STUDY_SESSION PRIMARY KEY (id), + CONSTRAINT CHK_STUDY_SESSION_DAY_PART CHECK (day_part IN ('AM', 'PM')), + CONSTRAINT CHK_STUDY_SESSION_STATUS CHECK (status IN ('BEFORE_SESSION', 'IN_SESSION', 'AFTER_SESSION')) ); -CREATE TABLE attendance_code ( - id BIGINT NOT NULL, - study_session_id BIGINT, - code VARCHAR(20) NOT NULL, - is_expired BOOLEAN NOT NULL, - CONSTRAINT pk_attendance_code PRIMARY KEY (id), - CONSTRAINT fk_attendance_code_session - FOREIGN KEY (study_session_id) REFERENCES study_session (id) -); - -CREATE TABLE attendance ( - id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - study_session_id BIGINT, - status BOOLEAN NOT NULL, - CONSTRAINT pk_attendance PRIMARY KEY (id), - CONSTRAINT uq_attendance_user_session - UNIQUE (user_id, study_session_id), - CONSTRAINT fk_attendance_user - FOREIGN KEY (user_id) REFERENCES users (id), - CONSTRAINT fk_attendance_session - FOREIGN KEY (study_session_id) REFERENCES study_session (id) +CREATE TABLE assignment ( + id SERIAL NOT NULL, + title VARCHAR(255) NOT NULL, + week VARCHAR(255) NULL, + session_date DATE NULL, + CONSTRAINT PK_ASSIGNMENT PRIMARY KEY (id) ); -CREATE TABLE deposit ( - id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - amount INT NOT NULL, - descent_assignment INT NOT NULL, - descent_attendance INT NOT NULL, - ascent_defence INT NOT NULL, - CONSTRAINT pk_deposit PRIMARY KEY (id), - CONSTRAINT uq_deposit_user - UNIQUE (user_id), - CONSTRAINT fk_deposit_user - FOREIGN KEY (user_id) REFERENCES users (id) +CREATE TABLE assignment_item ( + id SERIAL NOT NULL, + user_id BIGINT NOT NULL, -- FK 대상이므로 SERIAL에서 INT로 수정 + assignment_id INT NOT NULL, -- FK 대상이므로 SERIAL에서 INT로 수정 + submitted VARCHAR(20) NOT NULL DEFAULT 'SUCCESS', + CONSTRAINT PK_ASSIGNMENT_ITEM PRIMARY KEY (id), + CONSTRAINT CHK_ASSIGNMENT_ITEM_SUBMITTED CHECK (submitted IN ('SUCCESS', 'INSUFFICIENT', 'FAILURE')) ); -CREATE TABLE assignment ( - id BIGINT NOT NULL, - session_id BIGINT, - title VARCHAR(255) NOT NULL, - content VARCHAR(255), - CONSTRAINT pk_assignment PRIMARY KEY (id), - CONSTRAINT fk_assignment_session - FOREIGN KEY (session_id) REFERENCES study_session (id) +CREATE TABLE attendance_code ( + id SERIAL NOT NULL, + attendance_date VARCHAR(255) NULL, + attendance_order VARCHAR(255) NULL, + code VARCHAR(20) NOT NULL, + is_expired BOOLEAN NOT NULL, -- TINYINT(1)에서 BOOLEAN으로 수정 + Field3 VARCHAR(255) NULL, + CONSTRAINT PK_ATTENDANCE_CODE PRIMARY KEY (id) ); -CREATE TABLE assignment_item ( - id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - assignment_id BIGINT NOT NULL, - submitted assignment_status_enum NOT NULL DEFAULT 'SUCCESS', - CONSTRAINT pk_assignment_item PRIMARY KEY (id), - CONSTRAINT uq_assignment_item_user_assignment - UNIQUE (user_id, assignment_id), - CONSTRAINT fk_assignment_item_user - FOREIGN KEY (user_id) REFERENCES users (id), - CONSTRAINT fk_assignment_item_assignment - FOREIGN KEY (assignment_id) REFERENCES assignment (id) +CREATE TABLE attendance ( + id SERIAL NOT NULL, + attendance_code_id INT NOT NULL, + user_id BIGINT NOT NULL, + status BOOLEAN NOT NULL, -- TINYINT(1)에서 BOOLEAN으로 수정 + CONSTRAINT PK_ATTENDANCE PRIMARY KEY (id) ); CREATE TABLE question ( - id BIGINT NOT NULL, - session_id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - content TEXT NOT NULL, - image_url TEXT, - is_resolved BOOLEAN NOT NULL, - like_count INT NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP, - CONSTRAINT pk_question PRIMARY KEY (id), - CONSTRAINT fk_question_session - FOREIGN KEY (session_id) REFERENCES study_session (id), - CONSTRAINT fk_question_user - FOREIGN KEY (user_id) REFERENCES users (id) + id BIGSERIAL NOT NULL, + session_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + content VARCHAR(1000) NOT NULL, + image_url VARCHAR(1000) NULL, + is_resolved BOOLEAN NOT NULL, -- TINYINT(1)에서 BOOLEAN으로 수정 + like_count INT NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP NULL, + CONSTRAINT PK_QUESTION PRIMARY KEY (id) ); CREATE TABLE question_comment ( - id BIGINT NOT NULL, - question_id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - content TEXT NOT NULL, - image_url TEXT, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP, - CONSTRAINT pk_question_comment PRIMARY KEY (id), - CONSTRAINT fk_question_comment_question - FOREIGN KEY (question_id) REFERENCES question (id), - CONSTRAINT fk_question_comment_user - FOREIGN KEY (user_id) REFERENCES users (id) + id BIGSERIAL NOT NULL, + question_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + parent_comment_id BIGINT NULL, + content VARCHAR(1000) NOT NULL, + image_url VARCHAR(1000) NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMP NULL, + CONSTRAINT PK_QUESTION_COMMENT PRIMARY KEY (id) ); -CREATE TABLE question_like ( - id BIGINT NOT NULL, - question_id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - created_at TIMESTAMP NOT NULL, - CONSTRAINT pk_question_like PRIMARY KEY (id), - CONSTRAINT uq_question_like_question_user - UNIQUE (question_id, user_id), - CONSTRAINT fk_question_like_question - FOREIGN KEY (question_id) REFERENCES question (id), - CONSTRAINT fk_question_like_user - FOREIGN KEY (user_id) REFERENCES users (id) +CREATE TABLE question_anonymous_identity ( + id BIGSERIAL NOT NULL, + user_id BIGINT NOT NULL, + question_id BIGINT NOT NULL, + anonymous_no INT NOT NULL DEFAULT 1, + created_at TIMESTAMP NOT NULL, + CONSTRAINT PK_QUESTION_ANONYMOUS_IDENTITY PRIMARY KEY (id) ); -CREATE TABLE question_anonymous_identity ( - id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - question_id BIGINT NOT NULL, - anonymous_no INT NOT NULL DEFAULT 1, - created_at TIMESTAMP NOT NULL, - CONSTRAINT pk_question_anonymous_identity PRIMARY KEY (id), - CONSTRAINT uq_question_anon_question_user - UNIQUE (question_id, user_id), - CONSTRAINT uq_question_anon_question_no - UNIQUE (question_id, anonymous_no), - CONSTRAINT fk_question_anon_user - FOREIGN KEY (user_id) REFERENCES users (id), - CONSTRAINT fk_question_anon_question - FOREIGN KEY (question_id) REFERENCES question (id) +CREATE TABLE question_like ( + id BIGSERIAL NOT NULL, -- PK 타입 매칭을 위해 BIGSERIAL 수정 + question_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL, + CONSTRAINT PK_QUESTION_LIKE PRIMARY KEY (id) ); CREATE TABLE understanding_check ( - id BIGINT NOT NULL, - session_id BIGINT NOT NULL, - created_by BIGINT NOT NULL, - title VARCHAR(255) NOT NULL, - description VARCHAR(255), - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT pk_understanding_check PRIMARY KEY (id), - CONSTRAINT fk_understanding_check_session - FOREIGN KEY (session_id) REFERENCES study_session (id), - CONSTRAINT fk_understanding_check_created_by - FOREIGN KEY (created_by) REFERENCES users (id) + id BIGSERIAL NOT NULL, -- BIGINT PK용 BIGSERIAL 수정 + session_id BIGINT NOT NULL, + created_by BIGINT NOT NULL, + title VARCHAR(255) NOT NULL, + description VARCHAR(255) NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + CONSTRAINT PK_UNDERSTANDING_CHECK PRIMARY KEY (id) ); CREATE TABLE understanding_response ( - id BIGINT NOT NULL, - check_id BIGINT NOT NULL, - user_id BIGINT NOT NULL, - choice understanding_choice_enum NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT pk_understanding_response PRIMARY KEY (id), - CONSTRAINT uq_understanding_response_check_user - UNIQUE (check_id, user_id), - CONSTRAINT fk_understanding_response_check - FOREIGN KEY (check_id) REFERENCES understanding_check (id), - CONSTRAINT fk_understanding_response_user - FOREIGN KEY (user_id) REFERENCES users (id) + id BIGSERIAL NOT NULL, + check_id BIGINT NOT NULL, + user_id BIGINT NOT NULL, + choice VARCHAR(20) NOT NULL, + created_at TIMESTAMP NOT NULL, + updated_at TIMESTAMP NOT NULL, + CONSTRAINT PK_UNDERSTANDING_RESPONSE PRIMARY KEY (id), + CONSTRAINT CHK_UNDERSTANDING_RESPONSE_CHOICE CHECK (choice IN ('UNDERSTOOD', 'NOT_UNDERSTOOD')) +); + +CREATE TABLE deposit ( + id SERIAL NOT NULL, + user_id BIGINT NOT NULL, + amount INT NOT NULL, + descent_assignment INT NOT NULL, + descent_attendance INT NOT NULL, + ascent_defence INT NOT NULL, + CONSTRAINT PK_DEPOSIT PRIMARY KEY (id) ); + +-- 2. 코멘트(주석) 설정 +COMMENT ON COLUMN attendance_code.attendance_order IS '1, 2, 3'; \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V2__add_recording_password_to_study_session.sql b/backend/src/main/resources/db/migration/V2__add_recording_password_to_study_session.sql deleted file mode 100644 index 6529279..0000000 --- a/backend/src/main/resources/db/migration/V2__add_recording_password_to_study_session.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE study_session - ADD COLUMN recording_password VARCHAR(60); \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V3__add_material_and_assignment_name_to_study_session.sql b/backend/src/main/resources/db/migration/V3__add_material_and_assignment_name_to_study_session.sql deleted file mode 100644 index b952e79..0000000 --- a/backend/src/main/resources/db/migration/V3__add_material_and_assignment_name_to_study_session.sql +++ /dev/null @@ -1,5 +0,0 @@ -ALTER TABLE study_session - ADD COLUMN session_material_name VARCHAR(255); - -ALTER TABLE study_session - ADD COLUMN assignment_name VARCHAR(255); \ No newline at end of file diff --git a/backend/src/main/resources/db/migration/V4__add_parent_comment_to_question_comment.sql b/backend/src/main/resources/db/migration/V4__add_parent_comment_to_question_comment.sql deleted file mode 100644 index 02b4c05..0000000 --- a/backend/src/main/resources/db/migration/V4__add_parent_comment_to_question_comment.sql +++ /dev/null @@ -1,7 +0,0 @@ --- question_comment 테이블에 대댓글을 위한 parent_comment_id 컬럼 추가 --- Issue #4에서 QuestionComment 엔티티에 parentComment 필드가 추가되었는데 DB에 반영되지 않아 이 마이그레이션으로 동기화 - -ALTER TABLE question_comment - ADD COLUMN parent_comment_id BIGINT, - ADD CONSTRAINT fk_question_comment_parent - FOREIGN KEY (parent_comment_id) REFERENCES question_comment (id); \ No newline at end of file