diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java index 0440bc91..378d1d02 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java @@ -53,6 +53,15 @@ public List toAttachmentResponse .toList(); } + @Override + public void updateAttachment( + List requests, + InquiryAnswerJpaEntity answerEntity + ) { + deleteAttachment(answerEntity); + createAttachment(requests, answerEntity); + } + private InquiryDetailResponse.AttachmentResponse createAttachResponse( InquiryAnswerAttachmentEntity attachment) { String preSignedUrl = s3Service.getPreSignedUrl(attachment.getS3Key()); @@ -74,4 +83,5 @@ private InquiryDetailResponse.AttachmentDetailResponse createAttachDetailRespons ); } + } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java index bac2eda8..8987e8fa 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java @@ -4,6 +4,7 @@ import life.mosu.mosuserver.domain.inquiry.repository.InquiryJpaRepository; import life.mosu.mosuserver.domain.inquiryAnswer.entity.InquiryAnswerJpaEntity; import life.mosu.mosuserver.domain.inquiryAnswer.repository.InquiryAnswerJpaRepository; +import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.presentation.inquiry.dto.InquiryAnswerRequest; @@ -28,14 +29,14 @@ public class InquiryAnswerService { private final InquiryAnswerTxService eventTxService; @Transactional - public void createInquiryAnswer(Long postId, InquiryAnswerRequest request) { + public void createInquiryAnswer(Long postId, InquiryAnswerRequest request, UserJpaEntity user) { isAnswerAlreadyRegister(postId); InquiryJpaEntity inquiryEntity = getInquiry(postId); Long userId = inquiryEntity.getUserId(); try { InquiryAnswerJpaEntity answerEntity = inquiryAnswerJpaRepository.save( - request.toEntity(postId)); + request.toEntity(postId, user)); answerAttachmentService.createAttachment(request.attachments(), answerEntity); inquiryEntity.updateStatusToComplete(); @@ -51,12 +52,12 @@ public void createInquiryAnswer(Long postId, InquiryAnswerRequest request) { @Transactional public void deleteInquiryAnswer(Long postId) { InquiryJpaEntity inquiryEntity = getInquiry(postId); - InquiryAnswerJpaEntity answerEntity = inquiryAnswerJpaRepository.findByInquiryId(postId) .orElseThrow(() -> new CustomRuntimeException(ErrorCode.INQUIRY_ANSWER_NOT_FOUND)); inquiryAnswerJpaRepository.delete(answerEntity); inquiryEntity.updateStatusToPending(); + answerAttachmentService.deleteAttachment(answerEntity); } @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) @@ -71,17 +72,14 @@ public InquiryDetailResponse.InquiryAnswerDetailResponse getInquiryAnswerDetail( } @Transactional - public void updateInquiryAnswer(Long postId, InquiryAnswerUpdateRequest request) { - InquiryJpaEntity inquiryEntity = getInquiry(postId); - + public void updateInquiryAnswer(Long postId, InquiryAnswerUpdateRequest request, UserJpaEntity user) { InquiryAnswerJpaEntity answerEntity = inquiryAnswerJpaRepository.findByInquiryId(postId) .orElseThrow(() -> new CustomRuntimeException(ErrorCode.INQUIRY_ANSWER_NOT_FOUND)); - answerEntity.update(request.title(), request.content()); + answerEntity.update(request.title(), request.content(), user.getName()); inquiryAnswerJpaRepository.save(answerEntity); - answerAttachmentService.deleteAttachment(answerEntity); - answerAttachmentService.createAttachment(request.attachments(), answerEntity); + answerAttachmentService.updateAttachment(request.attachments(), answerEntity); } private InquiryJpaEntity getInquiry(Long postId) { @@ -96,4 +94,6 @@ private void isAnswerAlreadyRegister(Long postId) { } + + } diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java index 0298ff05..7939d7d9 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java @@ -38,6 +38,15 @@ public void deleteAttachment(InquiryJpaEntity entity) { inquiryAttachmentJpaRepository.deleteAll(attachments); } + @Override + public void updateAttachment( + List requests, + InquiryJpaEntity inquiryEntity + ) { + deleteAttachment(inquiryEntity); + createAttachment(requests, inquiryEntity); + } + public List toAttachmentResponses( InquiryJpaEntity inquiry) { diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java index d98d6452..965f5f42 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java @@ -68,11 +68,9 @@ public void deleteInquiry(UserJpaEntity user, Long postId) { InquiryJpaEntity inquiry = getInquiry(postId); hasPermission(inquiry.getUserId(), user); - inquiryAnswerJpaRepository.findByInquiryId(postId).ifPresent(answer -> { - inquiryAnswerService.deleteInquiryAnswer(postId); - }); + inquiryAnswerService.deleteInquiryAnswer(postId); -// inquiryAttachmentService.deleteAttachment(inquiry); + inquiryAttachmentService.deleteAttachment(inquiry); inquiryJpaRepository.delete(inquiry); } diff --git a/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java index 4319be53..d784b7a1 100644 --- a/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java @@ -40,18 +40,14 @@ public void deleteAttachment(NoticeJpaEntity entity) { noticeAttachmentJpaRepository.deleteAll(attachments); } -// public List toAttachmentResponses(NoticeJpaEntity notice) { -// -// List attachments = noticeAttachmentJpaRepository.findAllByNoticeId( -// notice.getId()); -// -// return attachments.stream() -// .map(attachment -> new NoticeResponse.AttachmentResponse( -// attachment.getFileName(), -// fileUrl(attachment.getS3Key()) -// )) -// .toList(); -// } + @Override + public void updateAttachment( + List requests, + NoticeJpaEntity noticeEntity + ) { + deleteAttachment(noticeEntity); + createAttachment(requests, noticeEntity); + } public List toDetailAttResponses( NoticeJpaEntity notice) { @@ -70,7 +66,7 @@ public List toDetailAttResponses( } private String fileUrl(String s3Key) { - return s3Service.getPreSignedUrl(s3Key); + return s3Service.getPublicUrl(s3Key); } } diff --git a/src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java b/src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java index 53bb0e93..4405792c 100644 --- a/src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java +++ b/src/main/java/life/mosu/mosuserver/application/notice/NoticeService.java @@ -3,6 +3,7 @@ import java.util.List; import life.mosu.mosuserver.domain.notice.entity.NoticeJpaEntity; import life.mosu.mosuserver.domain.notice.repository.NoticeJpaRepository; +import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.presentation.notice.dto.NoticeCreateRequest; @@ -28,8 +29,8 @@ public class NoticeService { private final NoticeAttachmentService attachmentService; @Transactional - public void createNotice(NoticeCreateRequest request) { - NoticeJpaEntity noticeEntity = noticeJpaRepository.save(request.toEntity()); + public void createNotice(NoticeCreateRequest request, UserJpaEntity user) { + NoticeJpaEntity noticeEntity = noticeJpaRepository.save(request.toEntity(user)); attachmentService.createAttachment(request.attachments(), noticeEntity); } @@ -45,24 +46,23 @@ public List getNotices(int page, int size) { @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public NoticeDetailResponse getNoticeDetail(Long noticeId) { - NoticeJpaEntity notice = getNoticeOrThrow(noticeId); + NoticeJpaEntity notice = getNotice(noticeId); return toNoticeDetailResponse(notice); } @Transactional public void deleteNotice(Long noticeId) { - NoticeJpaEntity noticeEntity = getNoticeOrThrow(noticeId); + NoticeJpaEntity noticeEntity = getNotice(noticeId); noticeJpaRepository.delete(noticeEntity); } @Transactional - public void updateNotice(Long noticeId, NoticeUpdateRequest request) { - NoticeJpaEntity noticeEntity = getNoticeOrThrow(noticeId); + public void updateNotice(Long noticeId, NoticeUpdateRequest request, UserJpaEntity user) { + NoticeJpaEntity noticeEntity = getNotice(noticeId); - noticeEntity.update(request.title(), request.content(), request.author()); - attachmentService.deleteAttachment(noticeEntity); - attachmentService.createAttachment(request.attachments(), noticeEntity); + noticeEntity.update(request.title(), request.content(), user.getName()); + attachmentService.updateAttachment(request.attachments(), noticeEntity); } private NoticeResponse toNoticeResponse(NoticeJpaEntity notice) { @@ -77,7 +77,7 @@ private NoticeDetailResponse toNoticeDetailResponse(NoticeJpaEntity notice) { ); } - private NoticeJpaEntity getNoticeOrThrow(Long noticeId) { + private NoticeJpaEntity getNotice(Long noticeId) { return noticeJpaRepository.findById(noticeId) .orElseThrow(() -> new CustomRuntimeException(ErrorCode.NOTICE_NOT_FOUND)); } diff --git a/src/main/java/life/mosu/mosuserver/domain/inquiry/entity/InquiryJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/inquiry/entity/InquiryJpaEntity.java index 73ea1319..9d56c56e 100644 --- a/src/main/java/life/mosu/mosuserver/domain/inquiry/entity/InquiryJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/inquiry/entity/InquiryJpaEntity.java @@ -26,10 +26,10 @@ public class InquiryJpaEntity extends BaseTimeEntity { @Column(name = "inquiry_id", nullable = false) private Long id; - @Column(name = "title", nullable = false) + @Column(name = "title", nullable = false, length = 300) private String title; - @Column(name = "content", nullable = false) + @Column(name = "content", nullable = false, length = 1000) private String content; @Column(name = "user_id", nullable = false) diff --git a/src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/entity/InquiryAnswerJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/entity/InquiryAnswerJpaEntity.java index 3e7f08b2..a1120f15 100644 --- a/src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/entity/InquiryAnswerJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/entity/InquiryAnswerJpaEntity.java @@ -25,15 +25,18 @@ public class InquiryAnswerJpaEntity extends BaseTimeEntity { @Column(name = "inquiry_answer_id", nullable = false) private Long id; - @Column(name = "title", nullable = false, length = 3000) + @Column(name = "title", nullable = false, length = 300) private String title; - @Column(name = "content", nullable = false) + @Column(name = "content", nullable = false, length = 1000) private String content; @Column(name = "inquiry_id", nullable = false) private Long inquiryId; + @Column(name = "author", nullable = false) + private String author; + @Column(name = "user_id", nullable = false) private Long userId; @@ -42,16 +45,20 @@ public InquiryAnswerJpaEntity( final String title, final String content, final Long inquiryId, - final Long userId + final Long userId, + final String author + ) { this.title = title; this.content = content; this.inquiryId = inquiryId; this.userId = userId; + this.author = author; } - public void update(final String title, final String content) { + public void update(final String title, final String content, final String author) { this.title = title; this.content = content; + this.author = author; } } diff --git a/src/main/java/life/mosu/mosuserver/global/exception/GlobalExceptionHandler.java b/src/main/java/life/mosu/mosuserver/global/exception/GlobalExceptionHandler.java index c578e565..645da67f 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/GlobalExceptionHandler.java @@ -1,6 +1,7 @@ package life.mosu.mosuserver.global.exception; import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; import java.util.LinkedHashMap; import java.util.Map; import life.mosu.mosuserver.infra.notify.NotifyClientAdapter; @@ -15,6 +16,8 @@ import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import org.springframework.web.reactive.resource.NoResourceFoundException; @Slf4j @RestControllerAdvice @@ -123,6 +126,31 @@ public ResponseEntity handleHttpMessageNotReadableException( return ResponseEntity.status(HttpStatus.CONFLICT).body(response); } + @ExceptionHandler(NoResourceFoundException.class ) + public ResponseEntity handleNotFound(Exception ex) { + notifyIfNeeded(ex); + + ErrorResponse response = ErrorResponse.builder() + .status(HttpStatus.NOT_FOUND.value()) + .message("요청하신 리소스를 찾을 수 없습니다.") + .errors(ex.getMessage()) + .build(); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleTypeMismatch(MethodArgumentTypeMismatchException ex) { + notifyIfNeeded(ex); + ErrorResponse response = ErrorResponse.builder() + .status(HttpStatus.BAD_REQUEST.value()) + .code("TYPE_MISMATCH") + .message("요청 파라미터 타입이 올바르지 않습니다.") + .build(); + + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGeneralException(Exception ex) { notifyIfNeeded(ex); @@ -137,7 +165,9 @@ public ResponseEntity handleGeneralException(Exception ex) { } @ExceptionHandler(CustomRuntimeException.class) - public ResponseEntity handleCustomRuntimeException(CustomRuntimeException ex) { + public ResponseEntity handleCustomRuntimeException( + CustomRuntimeException ex + ) { notifyIfNeeded(ex); ErrorResponse response = ErrorResponse.builder() @@ -152,7 +182,7 @@ public ResponseEntity handleCustomRuntimeException(CustomRuntimeE private void notifyIfNeeded(Exception ex) { try { DiscordExceptionNotifyEventRequest request = DiscordExceptionNotifyEventRequest.of( - ex.getCause().toString(), + ex.getCause() != null ? ex.getCause().toString() : "Unknown Cause", ex.getMessage() ); notifier.send(request); diff --git a/src/main/java/life/mosu/mosuserver/infra/persistence/s3/AttachmentService.java b/src/main/java/life/mosu/mosuserver/infra/persistence/s3/AttachmentService.java index 083b892b..19bca804 100644 --- a/src/main/java/life/mosu/mosuserver/infra/persistence/s3/AttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/infra/persistence/s3/AttachmentService.java @@ -7,4 +7,6 @@ public interface AttachmentService { void createAttachment(List fileRequests, E entity); void deleteAttachment(E entity); + + void updateAttachment(List fileRequests, E entity); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminInquiryController.java b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminInquiryController.java index cb3b7623..8bf7da39 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminInquiryController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminInquiryController.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.presentation.admin; +import life.mosu.mosuserver.application.auth.PrincipalDetails; import life.mosu.mosuserver.application.inquiry.InquiryAnswerService; import life.mosu.mosuserver.application.inquiry.InquiryService; import life.mosu.mosuserver.domain.inquiry.entity.InquiryStatus; @@ -15,6 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -50,20 +52,22 @@ public ResponseEntity>> getInquiryList( @PostMapping("/{postId}/answer") @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> inquiryAnswer( + @AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable Long postId, @RequestBody InquiryAnswerRequest request ) { - inquiryAnswerService.createInquiryAnswer(postId, request); + inquiryAnswerService.createInquiryAnswer(postId, request, principalDetails.user()); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "답변 등록 성공")); } @PutMapping("/{postId}/answer") @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> updateInquiryAnswer( + @AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable Long postId, @RequestBody InquiryAnswerUpdateRequest request ) { - inquiryAnswerService.updateInquiryAnswer(postId, request); + inquiryAnswerService.updateInquiryAnswer(postId, request, principalDetails.user()); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "답변 수정 성공")); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/docs/AdminInquiryControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/admin/docs/AdminInquiryControllerDocs.java index 2985ca47..47a633b4 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/docs/AdminInquiryControllerDocs.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/docs/AdminInquiryControllerDocs.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import life.mosu.mosuserver.application.auth.PrincipalDetails; import life.mosu.mosuserver.domain.inquiry.entity.InquiryStatus; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.inquiry.dto.InquiryAnswerRequest; @@ -16,6 +17,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -42,6 +44,8 @@ ResponseEntity>> getInquiryList( @ApiResponse(responseCode = "200", description = "답변 등록 성공") }) ResponseEntity> inquiryAnswer( + @Parameter(hidden = true, description = "요청을 보낸 사용자 ID (관리자 권한 필요)") + @AuthenticationPrincipal PrincipalDetails principalDetails, @Parameter(name = "postId", description = "답변을 등록할 문의의 ID", in = ParameterIn.PATH) @PathVariable Long postId, @Parameter(description = "답변 내용") @@ -53,6 +57,8 @@ ResponseEntity> inquiryAnswer( @ApiResponse(responseCode = "200", description = "답변 수정 성공") }) ResponseEntity> updateInquiryAnswer( + @Parameter(hidden = true, description = "요청을 보낸 사용자 ID (관리자 권한 필요)") + @AuthenticationPrincipal PrincipalDetails principalDetails, @Parameter(name = "postId", description = "답변을 수정할 문의의 ID", in = ParameterIn.PATH) @PathVariable Long postId, @Parameter(description = "수정할 답변 내용") diff --git a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryAnswerRequest.java b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryAnswerRequest.java index 01d5d075..ca422a90 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryAnswerRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryAnswerRequest.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; import life.mosu.mosuserver.domain.inquiryAnswer.entity.InquiryAnswerJpaEntity; +import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; import life.mosu.mosuserver.presentation.common.FileRequest; public record InquiryAnswerRequest( @@ -11,17 +12,16 @@ public record InquiryAnswerRequest( @NotNull String title, @Schema(description = "문의 내용", example = "포인트는 어떻게 사용하나요?") @NotNull String content, - @Schema(description = "작성자 ID 추후 토큰에서 추출하도록 변경 예정입니다.", example = "12") - Long userId, List attachments ) { - public InquiryAnswerJpaEntity toEntity(Long postId) { + public InquiryAnswerJpaEntity toEntity(Long postId, UserJpaEntity user) { return InquiryAnswerJpaEntity.builder() .inquiryId(postId) .title(title) .content(content) - .userId(userId) + .userId(user.getId()) + .author(user.getName()) .build(); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryCreateRequest.java b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryCreateRequest.java index 265383b2..d3b4a7ba 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryCreateRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/inquiry/dto/InquiryCreateRequest.java @@ -2,14 +2,19 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; import life.mosu.mosuserver.domain.inquiry.entity.InquiryJpaEntity; import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; import life.mosu.mosuserver.presentation.common.FileRequest; public record InquiryCreateRequest( + + @Size(max = 100, message = "제목은 최대 300자까지 입력 가능합니다.") @Schema(description = "문의 제목", example = "서비스 이용 관련 질문입니다.") @NotNull String title, + + @Size(max = 1000, message = "본문은 최대 1000자까지 입력 가능합니다.") @Schema(description = "문의 내용", example = "포인트는 어떻게 사용하나요?") @NotNull String content, List attachments diff --git a/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeController.java b/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeController.java index a9207073..f863fe1d 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeController.java @@ -2,7 +2,9 @@ import jakarta.validation.Valid; import java.util.List; +import life.mosu.mosuserver.application.auth.PrincipalDetails; import life.mosu.mosuserver.application.notice.NoticeService; +import life.mosu.mosuserver.global.annotation.UserId; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.notice.dto.NoticeCreateRequest; import life.mosu.mosuserver.presentation.notice.dto.NoticeDetailResponse; @@ -12,6 +14,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -32,8 +35,9 @@ public class NoticeController implements NoticeControllerDocs { @PostMapping @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> createNotice( + @AuthenticationPrincipal PrincipalDetails principalDetails, @Valid @RequestBody NoticeCreateRequest request) { - noticeService.createNotice(request); + noticeService.createNotice(request, principalDetails.user()); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.CREATED, "게시글 등록 성공")); } @@ -63,10 +67,11 @@ public ResponseEntity> deleteNotice(@PathVariable Long @PutMapping("/{noticeId}") @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> updateNotice( + @AuthenticationPrincipal PrincipalDetails principalDetails, @PathVariable Long noticeId, @Valid @RequestBody NoticeUpdateRequest request ) { - noticeService.updateNotice(noticeId, request); + noticeService.updateNotice(noticeId, request, principalDetails.user()); return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "게시글 수정 성공")); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeControllerDocs.java index 2310bc7d..7652e558 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeControllerDocs.java +++ b/src/main/java/life/mosu/mosuserver/presentation/notice/NoticeControllerDocs.java @@ -10,12 +10,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.util.List; +import life.mosu.mosuserver.application.auth.PrincipalDetails; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.notice.dto.NoticeCreateRequest; import life.mosu.mosuserver.presentation.notice.dto.NoticeDetailResponse; import life.mosu.mosuserver.presentation.notice.dto.NoticeResponse; import life.mosu.mosuserver.presentation.notice.dto.NoticeUpdateRequest; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; @@ -28,7 +30,11 @@ public interface NoticeControllerDocs { @ApiResponse(responseCode = "201", description = "공지사항 등록 성공") }) ResponseEntity> createNotice( - @Parameter(description = "공지사항 생성에 필요한 정보") @Valid @RequestBody NoticeCreateRequest request + @Parameter(hidden = true, description = "요청을 보낸 사용자 ID (관리자 권한 필요)") + @AuthenticationPrincipal PrincipalDetails principalDetails, + + @Parameter(description = "공지사항 생성에 필요한 정보") + @Valid @RequestBody NoticeCreateRequest request ); @Operation(summary = "공지사항 목록 조회", description = "공지사항 리스트를 페이징하여 조회합니다.") @@ -67,6 +73,8 @@ ResponseEntity> deleteNotice( @ApiResponse(responseCode = "200", description = "공지사항 수정 성공") }) ResponseEntity> updateNotice( + @Parameter(hidden = true, description = "요청을 보낸 사용자 ID (관리자 권한 필요)") + @AuthenticationPrincipal PrincipalDetails principalDetails, @Parameter(name = "noticeId", description = "수정할 공지사항 ID", in = ParameterIn.PATH) @PathVariable Long noticeId, @Parameter(description = "수정할 공지사항 정보") diff --git a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeCreateRequest.java b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeCreateRequest.java index 49ec21dc..0b5e9424 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeCreateRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeCreateRequest.java @@ -4,6 +4,7 @@ import jakarta.validation.constraints.NotNull; import java.util.List; import life.mosu.mosuserver.domain.notice.entity.NoticeJpaEntity; +import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; import life.mosu.mosuserver.presentation.common.FileRequest; public record NoticeCreateRequest( @@ -14,23 +15,17 @@ public record NoticeCreateRequest( @Schema(description = "공지사항 본문 내용", example = "6월 30일 오전 2시부터 서비스 점검이 진행됩니다.") @NotNull String content, - @Schema(description = "작성자 이름", example = "관리자") - @NotNull String author, - - @Schema(description = "작성자 ID (추후 토큰 기반 자동 추출 예정)", example = "1") - Long userId, - @Schema(description = "첨부파일 리스트 (S3 key 포함)") List attachments ) { - public NoticeJpaEntity toEntity() { + public NoticeJpaEntity toEntity(UserJpaEntity user) { return NoticeJpaEntity.builder() .title(title) .content(content) - .userId(userId) - .author(author) + .userId(user.getId()) + .author(user.getName()) .build(); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeResponse.java b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeResponse.java index 965a5989..7cd77fca 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeResponse.java @@ -20,8 +20,6 @@ public record NoticeResponse( @Schema(description = "작성일시 (yyyy-MM-dd)", example = "2025-07-08") String createdAt -// @Schema(description = "첨부파일 목록") -// List attachments ) { public static NoticeResponse of(NoticeJpaEntity notice) { @@ -31,18 +29,6 @@ public static NoticeResponse of(NoticeJpaEntity notice) { notice.getContent(), notice.getAuthor(), notice.getCreatedAt() -// attachments ); } - -// public record AttachmentResponse( -// -// @Schema(description = "파일 이름", example = "service_guide.pdf") -// String fileName, -// -// @Schema(description = "S3 Presigned URL", example = "https://bucket.s3.amazonaws.com/.../service_guide.pdf") -// String url -// ) { -// -// } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeUpdateRequest.java b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeUpdateRequest.java index 1e1959f7..e88ac79a 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeUpdateRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/notice/dto/NoticeUpdateRequest.java @@ -15,13 +15,6 @@ public record NoticeUpdateRequest( @NotNull String content, - @Schema(description = "작성자 이름", example = "관리자") - @NotNull - String author, - - @Schema(description = "작성자 ID (토큰에서 추출 예정)", example = "42") - Long userId, - @Schema(description = "첨부파일 목록") List attachments