diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/controller/CurriculumController.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/controller/CurriculumController.java index 35cb0aa..e53b441 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/controller/CurriculumController.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/controller/CurriculumController.java @@ -8,6 +8,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.time.LocalDate; import java.util.List; import java.util.Map; @@ -19,28 +20,26 @@ public class CurriculumController { private final CurriculumService curriculumService; @GetMapping - public ResponseEntity> getAllSessions() { - return ResponseEntity.ok(curriculumService.getAllSessions()); + public ResponseEntity> getAllDays() { + return ResponseEntity.ok(curriculumService.getAllDays()); } @PostMapping - public ResponseEntity createSession( - @RequestBody CurriculumReqDTO.CreateSessionReq req) { - CurriculumResDTO.CreateSessionRes response = curriculumService.createSession(req); - return ResponseEntity.status(HttpStatus.CREATED).body(response); + public ResponseEntity createDay( + @RequestBody CurriculumReqDTO.CreateDayReq req) { + return ResponseEntity.status(HttpStatus.CREATED).body(curriculumService.createDay(req)); } - @PatchMapping("/{sessionId}") - public ResponseEntity updateSession( - @PathVariable Long sessionId, - @RequestBody CurriculumReqDTO.UpdateSessionReq req) { - CurriculumResDTO.UpdateSessionRes response = curriculumService.updateSession(sessionId, req); - return ResponseEntity.ok(response); + @PatchMapping("/{sessionDate}") + public ResponseEntity updateDay( + @PathVariable LocalDate sessionDate, + @RequestBody CurriculumReqDTO.UpdateDayReq req) { + return ResponseEntity.ok(curriculumService.updateDay(sessionDate, req)); } - @DeleteMapping("/{sessionId}") - public ResponseEntity> deleteSession(@PathVariable Long sessionId) { - curriculumService.deleteSession(sessionId); + @DeleteMapping("/{sessionDate}") + public ResponseEntity> deleteDay(@PathVariable LocalDate sessionDate) { + curriculumService.deleteDay(sessionDate); return ResponseEntity.ok(Map.of("message", "세션이 정상적으로 삭제되었습니다.")); } } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/converter/CurriculumConverter.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/converter/CurriculumConverter.java index 4000f5b..e4de760 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/converter/CurriculumConverter.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/converter/CurriculumConverter.java @@ -3,95 +3,71 @@ import com.example.Piroin.project.domain.curriculum.dto.CurriculumReqDTO; import com.example.Piroin.project.domain.curriculum.dto.CurriculumResDTO; import com.example.Piroin.project.domain.curriculum.entity.StudySession; +import com.example.Piroin.project.domain.curriculum.enums.SessionDayPart; import com.example.Piroin.project.domain.curriculum.enums.SessionStatus; import com.example.Piroin.project.domain.user.entity.User; import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; public class CurriculumConverter { - public static StudySession toStudySession(CurriculumReqDTO.CreateSessionReq req, User user) { + public static StudySession toStudySession(CurriculumReqDTO.SessionReq sessionReq, + CurriculumReqDTO.CreateDayReq req, + User user) { return StudySession.builder() .createdBy(user) .generation(req.getGeneration()) .week(req.getWeek()) .sessionDate(req.getSessionDate()) - .dayPart(req.getDayPart()) - .title(req.getTitle()) - .hostName(req.getHostName() != null ? req.getHostName() : "(미정)") + .dayPart(sessionReq.getDayPart()) + .title(sessionReq.getTitle()) + .hostName(sessionReq.getHostName() != null ? sessionReq.getHostName() : "(미정)") .status(SessionStatus.BEFORE_SESSION) - .description(req.getDescription()) - .sessionMaterialUrl(req.getSessionMaterialUrl()) - .assignmentUrl(req.getAssignmentUrl()) - .recordingUrl(req.getRecordingUrl()) - .recordingPassword(req.getRecordingPassword()) - .sessionMaterialName(req.getSessionMaterialName()) - .assignmentName(req.getAssignmentName()) + .sessionMaterialUrl(sessionReq.getSessionMaterialUrl()) + .sessionMaterialName(sessionReq.getSessionMaterialName()) + .recordingUrl(sessionReq.getRecordingUrl()) + .recordingPassword(sessionReq.getRecordingPassword()) + .assignmentUrl(sessionReq.getAssignmentUrl()) + .assignmentName(sessionReq.getAssignmentName()) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .build(); } - public static CurriculumResDTO.CreateSessionRes toCreateSessionRes(StudySession session) { - return new CurriculumResDTO.CreateSessionRes( - session.getId(), - session.getCreatedBy().getName(), - session.getGeneration(), - session.getWeek(), - session.getSessionDate(), - session.getDayPart(), - session.getTitle(), - session.getHostName(), - session.getStatus(), - session.getDescription(), - session.getSessionMaterialUrl(), - session.getAssignmentUrl(), - session.getRecordingUrl(), - session.getRecordingPassword(), - session.getSessionMaterialName(), - session.getAssignmentName(), - session.getCreatedAt() - ); - } + public static CurriculumResDTO.CreateDayRes toCreateDayRes(List sessions) { + StudySession first = sessions.get(0); - public static CurriculumResDTO.GetSessionRes toGetSessionRes(StudySession session) { - return new CurriculumResDTO.GetSessionRes( - session.getId(), - session.getWeek(), - session.getSessionDate(), - session.getDayPart(), - session.getTitle(), - session.getHostName(), - session.getStatus(), - session.getDescription(), - session.getSessionMaterialUrl(), - session.getAssignmentUrl(), - session.getRecordingUrl(), - session.getRecordingPassword(), - session.getSessionMaterialName(), - session.getAssignmentName() + // assignment는 PM 세션 기준으로 저장/조회. 운영 기간 6주 고정 + StudySession pm = sessions.stream() + .filter(s -> s.getDayPart() == SessionDayPart.PM) + .findFirst() + .orElse(null); + + return new CurriculumResDTO.CreateDayRes( + first.getSessionDate(), + first.getGeneration(), + first.getWeek(), + pm != null ? pm.getAssignmentUrl() : null, + pm != null ? pm.getAssignmentName() : null, + sessions.stream().map(CurriculumConverter::toSessionInfo).collect(Collectors.toList()), + first.getCreatedAt() ); } - public static CurriculumResDTO.UpdateSessionRes toUpdateSessionRes(StudySession session) { - return new CurriculumResDTO.UpdateSessionRes( + public static CurriculumResDTO.SessionInfo toSessionInfo(StudySession session) { + return new CurriculumResDTO.SessionInfo( session.getId(), - session.getCreatedBy().getName(), - session.getGeneration(), - session.getWeek(), - session.getSessionDate(), session.getDayPart(), session.getTitle(), session.getHostName(), session.getStatus(), - session.getDescription(), session.getSessionMaterialUrl(), - session.getAssignmentUrl(), - session.getRecordingUrl(), - session.getRecordingPassword(), session.getSessionMaterialName(), - session.getAssignmentName(), - session.getUpdatedAt() + session.getRecordingUrl(), + session.getRecordingPassword() ); } + } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumReqDTO.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumReqDTO.java index 03e03be..ceabfe1 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumReqDTO.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumReqDTO.java @@ -6,44 +6,56 @@ import lombok.NoArgsConstructor; import java.time.LocalDate; +import java.util.List; public class CurriculumReqDTO { @Getter @NoArgsConstructor - public static class CreateSessionReq { - private Long userId; + public static class CreateDayReq { private Integer generation; private Long week; private LocalDate sessionDate; + private List sessions; + } + + @Getter + @NoArgsConstructor + public static class SessionReq { private SessionDayPart dayPart; private String title; private String hostName; - private String description; private String sessionMaterialUrl; - private String assignmentUrl; + private String sessionMaterialName; private String recordingUrl; private String recordingPassword; - private String sessionMaterialName; + // PM에만 사용 + private String assignmentUrl; private String assignmentName; } @Getter @NoArgsConstructor - public static class UpdateSessionReq { + public static class UpdateDayReq { private Integer generation; private Long week; - private LocalDate sessionDate; + private LocalDate newSessionDate; + private List sessions; + } + + @Getter + @NoArgsConstructor + public static class UpdateSessionItemReq { private SessionDayPart dayPart; + private SessionStatus status; private String title; private String hostName; - private SessionStatus status; - private String description; private String sessionMaterialUrl; - private String assignmentUrl; + private String sessionMaterialName; private String recordingUrl; private String recordingPassword; - private String sessionMaterialName; + private String assignmentUrl; private String assignmentName; } + } diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumResDTO.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumResDTO.java index cc12303..e8ec7e2 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumResDTO.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/dto/CurriculumResDTO.java @@ -2,77 +2,33 @@ import com.example.Piroin.project.domain.curriculum.enums.SessionDayPart; import com.example.Piroin.project.domain.curriculum.enums.SessionStatus; -import lombok.AllArgsConstructor; -import lombok.Getter; - import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; public class CurriculumResDTO { - @Getter - @AllArgsConstructor - public static class CreateSessionRes { - private Long id; - private String createdBy; - private Integer generation; - private Long week; - private LocalDate sessionDate; - private SessionDayPart dayPart; - private String title; - private String hostName; - private SessionStatus status; - private String description; - private String sessionMaterialUrl; - private String assignmentUrl; - private String recordingUrl; - private String recordingPassword; - private String sessionMaterialName; - private String assignmentName; - private LocalDateTime createdAt; - } - - @Getter - @AllArgsConstructor - public static class GetSessionRes { - private Long id; - private Long week; - private LocalDate sessionDate; - private SessionDayPart dayPart; - private String title; - private String hostName; - private SessionStatus status; - private String description; - private String sessionMaterialUrl; - private String assignmentUrl; - private String recordingUrl; - private String recordingPassword; - private String sessionMaterialName; - private String assignmentName; - } - - @Getter - @AllArgsConstructor - public static class UpdateSessionRes { - private Long id; - private String createdBy; - private Integer generation; - private Long week; - private LocalDate sessionDate; - private SessionDayPart dayPart; - private String title; - private String hostName; - private SessionStatus status; - private String description; - private String sessionMaterialUrl; - private String assignmentUrl; - private String recordingUrl; - private String recordingPassword; - private String sessionMaterialName; - private String assignmentName; - private LocalDateTime updatedAt; - } + public record SessionInfo( + Long sessionId, + SessionDayPart dayPart, + String title, + String hostName, + SessionStatus status, + String sessionMaterialUrl, + String sessionMaterialName, + String recordingUrl, + String recordingPassword + ) {} + + public record CreateDayRes( + LocalDate sessionDate, + Integer generation, + Long week, + String assignmentUrl, + String assignmentName, + List sessions, + LocalDateTime createdAt + ) {} public record QnaSessionsResponse( List activeSessions, diff --git a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/entity/StudySession.java b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/entity/StudySession.java index a0692dc..bef1b5b 100644 --- a/backend/src/main/java/com/example/Piroin/project/domain/curriculum/entity/StudySession.java +++ b/backend/src/main/java/com/example/Piroin/project/domain/curriculum/entity/StudySession.java @@ -76,29 +76,24 @@ public class StudySession { private LocalDateTime createdAt; private LocalDateTime updatedAt; - public void update(Integer generation, Long week, LocalDate sessionDate, SessionDayPart dayPart, - String title, String hostName, SessionStatus status, String description, - String sessionMaterialUrl, String assignmentUrl, String recordingUrl, - String recordingPassword, String sessionMaterialName, String assignmentName) { + public void updateFull(Integer generation, Long week, LocalDate sessionDate, SessionStatus status, String title, String hostName, + String sessionMaterialUrl, String sessionMaterialName, + String recordingUrl, String recordingPassword, + String assignmentUrl, String assignmentName) { if (generation != null) this.generation = generation; if (week != null) this.week = week; if (sessionDate != null) this.sessionDate = sessionDate; - if (dayPart != null) this.dayPart = dayPart; - if (title != null) this.title = title; - if (hostName != null) this.hostName = hostName; if (status != null) this.status = status; - if (description != null) this.description = description; - if (sessionMaterialUrl != null) this.sessionMaterialUrl = sessionMaterialUrl; - if (assignmentUrl != null) this.assignmentUrl = assignmentUrl; - if (recordingUrl != null) this.recordingUrl = recordingUrl; - if (recordingPassword != null) this.recordingPassword = recordingPassword; - if (sessionMaterialName != null) this.sessionMaterialName = sessionMaterialName; - if (assignmentName != null) this.assignmentName = assignmentName; + this.title = title; + this.hostName = (hostName != null && !hostName.isBlank()) ? hostName : "(미정)"; + this.sessionMaterialUrl = sessionMaterialUrl; + this.sessionMaterialName = sessionMaterialName; + this.recordingUrl = recordingUrl; + this.recordingPassword = recordingPassword; + this.assignmentUrl = assignmentUrl; + this.assignmentName = assignmentName; this.updatedAt = LocalDateTime.now(); } - public LocalDate getDate() { - return null; - } } 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 fa17314..03c3e1e 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 @@ -3,12 +3,9 @@ import com.example.Piroin.project.domain.curriculum.entity.StudySession; import com.example.Piroin.project.domain.curriculum.enums.SessionStatus; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; -import java.time.DayOfWeek; +import java.time.LocalDate; import java.util.List; -import java.util.Optional; /* StudySession(세션) DB 접근 인터페이스 @@ -22,6 +19,10 @@ public interface CurriculumRepository extends JpaRepository List findByWeek(Long week); + List findBySessionDate(LocalDate sessionDate); + + List findAllByOrderBySessionDateAscDayPartAsc(); + // @Query(""" // SELECT s // FROM StudySession s 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..1f41745 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 @@ -9,12 +9,15 @@ import com.example.Piroin.project.domain.curriculum.repository.CurriculumRepository; import com.example.Piroin.project.domain.user.entity.User; import com.example.Piroin.project.domain.user.repository.UserRepository; +import com.example.Piroin.project.global.util.SecurityUtil; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDate; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; @Service @@ -25,48 +28,77 @@ public class CurriculumService { private final UserRepository userRepository; @Transactional(readOnly = true) - public List getAllSessions() { - return curriculumRepository.findAll().stream() - .map(CurriculumConverter::toGetSessionRes) + public List getAllDays() { + Map> grouped = curriculumRepository.findAllByOrderBySessionDateAscDayPartAsc() + .stream() + .collect(Collectors.groupingBy(StudySession::getSessionDate, Collectors.toList())); + + return grouped.entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(entry -> CurriculumConverter.toCreateDayRes(entry.getValue())) .collect(Collectors.toList()); } @Transactional - public CurriculumResDTO.CreateSessionRes createSession(CurriculumReqDTO.CreateSessionReq req) { + public CurriculumResDTO.CreateDayRes createDay(CurriculumReqDTO.CreateDayReq req) { if (req.getGeneration() == null) throw new CurriculumException(HttpStatus.BAD_REQUEST, "기수는 필수입니다."); if (req.getWeek() == null) throw new CurriculumException(HttpStatus.BAD_REQUEST, "주차는 필수입니다."); if (req.getSessionDate() == null) throw new CurriculumException(HttpStatus.BAD_REQUEST, "세션 날짜는 필수입니다."); - if (req.getDayPart() == null) throw new CurriculumException(HttpStatus.BAD_REQUEST, "오전/오후는 필수입니다."); - if (req.getTitle() == null || req.getTitle().isBlank()) throw new CurriculumException(HttpStatus.BAD_REQUEST, "제목은 필수입니다."); + if (req.getSessions() == null || req.getSessions().size() != 2) + throw new CurriculumException(HttpStatus.BAD_REQUEST, "AM/PM 세션 2개를 함께 입력해야 합니다."); + + req.getSessions().forEach(s -> { + if (s.getDayPart() == null) throw new CurriculumException(HttpStatus.BAD_REQUEST, "dayPart는 필수입니다."); + if (s.getTitle() == null || s.getTitle().isBlank()) throw new CurriculumException(HttpStatus.BAD_REQUEST, "세션 제목은 필수입니다."); + }); + + if (!curriculumRepository.findBySessionDate(req.getSessionDate()).isEmpty()) + throw new CurriculumException(HttpStatus.CONFLICT, "해당 날짜에 이미 세션이 존재합니다."); - User user = userRepository.findById(req.getUserId()) + User user = userRepository.findById(SecurityUtil.getCurrentUserId()) .orElseThrow(() -> new CurriculumException(HttpStatus.NOT_FOUND, "사용자를 찾을 수 없습니다.")); - StudySession session = CurriculumConverter.toStudySession(req, user); - StudySession savedSession = curriculumRepository.save(session); + List sessions = req.getSessions().stream() + .map(sessionReq -> CurriculumConverter.toStudySession(sessionReq, req, user)) + .collect(Collectors.toList()); - return CurriculumConverter.toCreateSessionRes(savedSession); + return CurriculumConverter.toCreateDayRes(curriculumRepository.saveAll(sessions)); } @Transactional - public CurriculumResDTO.UpdateSessionRes updateSession(Long sessionId, CurriculumReqDTO.UpdateSessionReq req) { - StudySession session = curriculumRepository.findById(sessionId) - .orElseThrow(() -> new CurriculumException(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다.")); - - session.update(req.getGeneration(), req.getWeek(), req.getSessionDate(), req.getDayPart(), - req.getTitle(), req.getHostName(), req.getStatus(), req.getDescription(), - req.getSessionMaterialUrl(), req.getAssignmentUrl(), req.getRecordingUrl(), - req.getRecordingPassword(), req.getSessionMaterialName(), req.getAssignmentName()); - - return CurriculumConverter.toUpdateSessionRes(session); + public CurriculumResDTO.CreateDayRes updateDay(LocalDate sessionDate, CurriculumReqDTO.UpdateDayReq req) { + List sessions = curriculumRepository.findBySessionDate(sessionDate); + if (sessions.isEmpty()) throw new CurriculumException(HttpStatus.NOT_FOUND, "해당 세션을 찾을 수 없습니다."); + + LocalDate newDate = req.getNewSessionDate(); + if (newDate != null && !newDate.equals(sessionDate) && !curriculumRepository.findBySessionDate(newDate).isEmpty()) + throw new CurriculumException(HttpStatus.CONFLICT, "해당 날짜에 이미 세션이 존재합니다."); + + req.getSessions().forEach(sessionReq -> { + if (sessionReq.getTitle() == null || sessionReq.getTitle().isBlank()) + throw new CurriculumException(HttpStatus.BAD_REQUEST, "세션 제목은 필수입니다."); + + sessions.stream() + .filter(s -> s.getDayPart() == sessionReq.getDayPart()) + .findFirst() + .ifPresent(s -> s.updateFull( + req.getGeneration(), req.getWeek(), newDate, sessionReq.getStatus(), + sessionReq.getTitle(), sessionReq.getHostName(), + sessionReq.getSessionMaterialUrl(), + sessionReq.getSessionMaterialName(), sessionReq.getRecordingUrl(), + sessionReq.getRecordingPassword(), sessionReq.getAssignmentUrl(), + sessionReq.getAssignmentName() + )); + }); + + return CurriculumConverter.toCreateDayRes(sessions); } @Transactional - public void deleteSession(Long sessionId) { - StudySession session = curriculumRepository.findById(sessionId) - .orElseThrow(() -> new CurriculumException(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다.")); - - curriculumRepository.delete(session); + public void deleteDay(LocalDate sessionDate) { + List sessions = curriculumRepository.findBySessionDate(sessionDate); + if (sessions.isEmpty()) throw new CurriculumException(HttpStatus.NOT_FOUND, "해당 세션을 찾을 수 없습니다."); + curriculumRepository.deleteAll(sessions); } @Transactional(readOnly = true) diff --git a/backend/src/main/java/com/example/Piroin/project/global/config/SecurityConfig.java b/backend/src/main/java/com/example/Piroin/project/global/config/SecurityConfig.java index 5387ffc..95e7579 100644 --- a/backend/src/main/java/com/example/Piroin/project/global/config/SecurityConfig.java +++ b/backend/src/main/java/com/example/Piroin/project/global/config/SecurityConfig.java @@ -36,8 +36,8 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { // curriculum: GET은 로그인한 누구나, POST/PATCH/DELETE는 ADMIN만 -> 이중 보안 느낌 .requestMatchers(HttpMethod.GET, "/api/curriculums").authenticated() .requestMatchers(HttpMethod.POST, "/api/curriculums").hasRole("ADMIN") - .requestMatchers(HttpMethod.PATCH, "/api/curriculums/{id}").hasRole("ADMIN") - .requestMatchers(HttpMethod.DELETE, "/api/curriculums/{id}").hasRole("ADMIN") + .requestMatchers(HttpMethod.PATCH, "/api/curriculums/{sessionDate}").hasRole("ADMIN") + .requestMatchers(HttpMethod.DELETE, "/api/curriculums/{sessionDate}").hasRole("ADMIN") // understanding check: 생성은 ADMIN만 가능 .requestMatchers(HttpMethod.POST, "/api/sessions/{sessionId}/understanding-checks").hasRole("ADMIN") diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 631371a..cabf9f2 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -1,8 +1,6 @@ -jwt: - secret: ${JWT_SECRET} - expiration: ${JWT_EXPIRATION} - spring: + config: + import: optional:file:.env[.properties] datasource: url: jdbc:postgresql://${RDS_ENDPOINT}:5432/${RDS_DB_NAME} username: ${RDS_USERNAME} @@ -22,6 +20,10 @@ spring: format_sql: true packagesToScan: com.example.Piroin.project.domain +jwt: + secret: ${JWT_SECRET} + expiration: ${JWT_EXPIRATION} + management: endpoints: web: diff --git a/frontend/src/pages/qna/QnADetailePage.js b/frontend/src/pages/qna/QnADetailePage.js new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/pages/qna/QnADetailePage.module.css b/frontend/src/pages/qna/QnADetailePage.module.css new file mode 100644 index 0000000..e69de29