From 4e1f327c738f2a5adf67bb575e7120e721442b63 Mon Sep 17 00:00:00 2001 From: daolove0323 Date: Wed, 6 Nov 2024 02:41:37 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20authorization=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../wouldyouin/_common/error/ErrorCode.java | 2 ++ .../exception/UnauthorizedException.java | 10 ++++++ .../api/AdvertisementController.java | 13 +++++-- .../curation/api/CurationController.java | 29 ++++++++------- .../curation/application/CurationService.java | 23 ++++++++---- .../wouldyouin/event/api/EventController.java | 36 +++++++++++-------- .../event/api/dto/EventCreateRequest.java | 3 -- .../event/application/EventService.java | 34 +++++++++++------- 8 files changed, 99 insertions(+), 51 deletions(-) create mode 100644 src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java index d155e76b..f4d7f9e6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java @@ -16,6 +16,8 @@ public enum ErrorCode { FAIL_TO_READ_IMAGE(HttpStatus.CONFLICT.value(), "-20400", "Fail to read image"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "-20400", "Unauthorized, %s id is not matched."), + ENTITY_PARAM_IS_NULL(HttpStatus.BAD_REQUEST.value(), "-20400", "%s is null"); private final Integer status; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java new file mode 100644 index 00000000..b7abfe7f --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java @@ -0,0 +1,10 @@ +package org.ktc2.cokaen.wouldyouin._common.exception; + +import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; + +public class UnauthorizedException extends BusinessException { + + public UnauthorizedException(String message) { + super(message, ErrorCode.UNAUTHORIZED); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java index 874e4949..e8beda9e 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java @@ -8,6 +8,9 @@ import org.ktc2.cokaen.wouldyouin.advertisement.api.dto.AdvertisementRequest; import org.ktc2.cokaen.wouldyouin.advertisement.api.dto.AdvertisementResponse; import org.ktc2.cokaen.wouldyouin.advertisement.application.AdvertisementService; +import org.ktc2.cokaen.wouldyouin.auth.Authorize; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -41,7 +44,8 @@ public ResponseEntity> getAdvertisementBy @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) public ResponseEntity> createAdvertisement( @Valid @RequestPart AdvertisementRequest advertisementRequest, - @RequestPart(required = false) MultipartFile image) { + @RequestPart(required = false) MultipartFile image, + @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { return ApiResponse.created(advertisementService.create(advertisementRequest, image)); } @@ -49,12 +53,15 @@ public ResponseEntity> createAdvertisemen public ResponseEntity> updateAdvertisement( @PathVariable Long adId, @Valid @RequestPart AdvertisementRequest advertisementRequest, - @RequestPart(required = false) MultipartFile image) { + @RequestPart(required = false) MultipartFile image, + @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { return ApiResponse.ok(advertisementService.update(adId, advertisementRequest, image)); } @DeleteMapping("/{adId}") - public ResponseEntity> deleteAdvertisement(@PathVariable Long adId) { + public ResponseEntity> deleteAdvertisement( + @PathVariable Long adId, + @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { advertisementService.delete(adId); return ApiResponse.noContent(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java index 5b977371..284d40b9 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java @@ -5,12 +5,14 @@ import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; import org.ktc2.cokaen.wouldyouin._common.persist.Area; +import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationResponse; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationSliceResponse; import org.ktc2.cokaen.wouldyouin.curation.application.CurationService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -33,9 +35,9 @@ public class CurationController { @GetMapping public ResponseEntity> getCurationsByAreaOrderByCreatedDateDesc( @RequestParam Area area, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page}") Integer page, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page-size}") Integer size, - @RequestParam(defaultValue = "${spring.controller.pageable.default-last-id}") Long lastId + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = Long.MAX_VALUE + "") Long lastId ) { return ApiResponse.ok(curationService.getAllByAreaOrderByCreatedDateDesc( area, PageRequest.of(page, size), lastId)); @@ -44,9 +46,9 @@ public ResponseEntity> getCurationsByArea @GetMapping("/curators/{curatorId}") public ResponseEntity> getCurationsByCuratorIdOrderByCreatedDateDesc( @PathVariable("curatorId") Long curatorId, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page}") Integer page, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page-size}") Integer size, - @RequestParam(defaultValue = "${spring.controller.pageable.default-last-id}") Long lastId + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = Long.MAX_VALUE + "") Long lastId ) { return ApiResponse.ok(curationService.getAllByCuratorIdOrderByCreatedDateDesc( curatorId, PageRequest.of(page, size), lastId)); @@ -61,20 +63,23 @@ public ResponseEntity> getCurationByCurationId @PostMapping public ResponseEntity> createCuration( @Valid @RequestBody CurationCreateRequest curationCreateRequest, - MemberIdentifier curator) { + @Authorize({MemberType.curator, MemberType.admin}) MemberIdentifier curator) { return ApiResponse.created(curationService.create(curator.id(), curationCreateRequest)); } @PutMapping("/{curationId}") - public ResponseEntity> updateCuration(@PathVariable Long curationId, + public ResponseEntity> updateCuration( + @PathVariable Long curationId, @Valid @RequestBody CurationEditRequest curationEditRequest, - MemberIdentifier curator) { - return ApiResponse.ok(curationService.update(curator.id(), curationEditRequest)); + @Authorize({MemberType.curator, MemberType.admin}) MemberIdentifier curator) { + return ApiResponse.ok(curationService.update(curator.id(), curationId, curationEditRequest)); } @DeleteMapping("/{curationId}") - public ResponseEntity> deleteCuration(@PathVariable Long curationId) { - curationService.delete(curationId); + public ResponseEntity> deleteCuration( + @PathVariable Long curationId, + @Authorize({MemberType.curator, MemberType.admin}) MemberIdentifier curator) { + curationService.delete(curator.id(), curationId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java index 04f960ea..68e85b61 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java @@ -3,6 +3,7 @@ import java.util.List; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; import org.ktc2.cokaen.wouldyouin._common.persist.Area; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; @@ -75,24 +76,32 @@ public CurationResponse create(Long curatorId, CurationCreateRequest curationCre return CurationResponse.from(curation); } + // Todo: 큐레이터 id 검증 로직 분리 @Transactional - public CurationResponse update(Long curationId, CurationEditRequest curationEditRequest) { - Curation target = getByIdOrThrow(curationId); + public CurationResponse update(Long curatorId, Long curationId, CurationEditRequest curationEditRequest) { + Curation curation = getByIdOrThrow(curationId); + if (curatorId != curation.getCurator().getId()) { + throw new UnauthorizedException("Curator"); + } List curationCards = curationEditRequest.getCurationCards().stream() .map(curationCardService::create) .toList(); List events = curationEditRequest.getEventIds().stream() .map(eventService::getByIdOrThrow) .toList(); - target.getCurationCards().forEach(card -> curationCardService.delete(card.getId())); - target.updateFrom(curationEditRequest, curationCards, events); - curationCards.forEach(curationCard -> curationCardService.setCuration(curationCard, target)); - return CurationResponse.from(target); + curation.getCurationCards().forEach(card -> curationCardService.delete(card.getId())); + curation.updateFrom(curationEditRequest, curationCards, events); + curationCards.forEach(curationCard -> curationCardService.setCuration(curationCard, curation)); + return CurationResponse.from(curation); } + // Todo: 큐레이터 id 검증 로직 분리 @Transactional - public void delete(Long curationId) { + public void delete(Long curatorId, Long curationId) { Curation curation = getByIdOrThrow(curationId); + if (curatorId != curation.getCurator().getId()) { + throw new UnauthorizedException("Curator"); + } curation.getCurationCards() .forEach(curationCard -> curationCardService.delete(curationCard.getId())); curationRepository.deleteById(curationId); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java index 9e3bae26..0624658a 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java @@ -7,12 +7,15 @@ import org.ktc2.cokaen.wouldyouin._common.persist.Area; import org.ktc2.cokaen.wouldyouin._common.persist.Category; import org.ktc2.cokaen.wouldyouin._common.persist.Location; +import org.ktc2.cokaen.wouldyouin.auth.Authorize; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.curation.api.dto.LocationFilter; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; import org.ktc2.cokaen.wouldyouin.event.application.EventService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -38,21 +41,23 @@ public ResponseEntity> getEventsByFilterOrde @RequestParam Location currentLocation, @RequestParam(defaultValue = "전체") Category category, @RequestParam(defaultValue = "전체") Area area, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page}") Integer page, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page-size}") Integer size, - @RequestParam(defaultValue = "${spring.controller.pageable.default-last-id}") Long lastId + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = Long.MAX_VALUE + "") Long lastId ) { - return ApiResponse.ok(eventService.getAllByFilterOrderByDistanceAsc(locationFilter, currentLocation, category, area, PageRequest.of(page, size), lastId)); + return ApiResponse.ok(eventService.getAllByFilterOrderByDistanceAsc( + locationFilter, currentLocation, category, area, PageRequest.of(page, size), lastId)); } @GetMapping("/hosts/{hostId}") public ResponseEntity> getEventsByHostId( @PathVariable Long hostId, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page}") Integer page, - @RequestParam(defaultValue = "${spring.controller.pageable.default-page-size}") Integer size, - @RequestParam(defaultValue = "${spring.controller.pageable.default-last-id}") Long lastId + @RequestParam(defaultValue = "0") Integer page, + @RequestParam(defaultValue = "10") Integer size, + @RequestParam(defaultValue = Long.MAX_VALUE + "") Long lastId ) { - return ApiResponse.ok(eventService.getAllByHostIdOrderByCreatedDateDesc(hostId, PageRequest.of(page, size), lastId)); + return ApiResponse.ok(eventService.getAllByHostIdOrderByCreatedDateDesc( + hostId, PageRequest.of(page, size), lastId)); } @GetMapping("/{eventId}") @@ -63,20 +68,23 @@ public ResponseEntity> getEventByEventId( @PostMapping public ResponseEntity> createEvent( - @Valid @RequestBody EventCreateRequest eventCreateRequest) { - return ApiResponse.created(eventService.create(eventCreateRequest)); + @Valid @RequestBody EventCreateRequest eventCreateRequest, + @Authorize(MemberType.host) MemberIdentifier host) { + return ApiResponse.created(eventService.create(host.id(), eventCreateRequest)); } @PutMapping("/{eventId}") public ResponseEntity> updateEvent(@PathVariable Long eventId, - @Valid @RequestBody EventEditRequest eventEditRequest) { - return ApiResponse.ok(eventService.update(eventId, eventEditRequest)); + @Valid @RequestBody EventEditRequest eventEditRequest, + @Authorize(MemberType.host) MemberIdentifier host) { + return ApiResponse.ok(eventService.update(host.id(), eventId, eventEditRequest)); } @DeleteMapping("/{eventId}") public ResponseEntity> deleteEvent( - @PathVariable("eventId") Long eventId) { - eventService.delete(eventId); + @PathVariable("eventId") Long eventId, + @Authorize(MemberType.host) MemberIdentifier host) { + eventService.delete(host.id(), eventId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java index 5dc6884a..b461333d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java @@ -20,9 +20,6 @@ @Builder public class EventCreateRequest { - @NotNull(message = "hostId는 필수입니다.") - private Long hostId; - @NotBlank(message = "제목은 필수입니다.") private String title; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java index 982c8e02..f62dbe1b 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin.Image.application.EventImageService; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; import org.ktc2.cokaen.wouldyouin._common.persist.Area; import org.ktc2.cokaen.wouldyouin._common.persist.Category; import org.ktc2.cokaen.wouldyouin._common.persist.Location; @@ -63,31 +64,40 @@ public Event getByIdOrThrow(Long id) throws EntityNotFoundException { } @Transactional - public EventResponse create(EventCreateRequest eventCreateRequest) { - return EventResponse.from(eventRepository.save(eventCreateRequest.toEntity( - hostService.getByIdOrThrow(eventCreateRequest.getHostId()), + public EventResponse create(Long hostId, EventCreateRequest eventCreateRequest) { + return EventResponse.from( + eventRepository.save(eventCreateRequest.toEntity( + hostService.getByIdOrThrow(hostId), eventCreateRequest.getImageIds().stream() .map(eventImageService::getById) .toList()))); } + // Todo: 호스트 id 검증 로직 분리 @Transactional - public EventResponse update(Long id, EventEditRequest eventEditRequest) { - Event target = getByIdOrThrow(id); - target.updateFrom(eventEditRequest, eventEditRequest.getImageIds().stream() + public EventResponse update(Long hostId, Long eventId, EventEditRequest eventEditRequest) { + Event event = getByIdOrThrow(eventId); + if (hostId != event.getId()) { + throw new UnauthorizedException("Host"); + } + event.updateFrom(eventEditRequest, eventEditRequest.getImageIds().stream() .map(eventImageService::getById) .toList()); - return EventResponse.from(target); + return EventResponse.from(event); } + // Todo: 호스트 id 검증 로직 분리 @Transactional - public void decreaseLeftSeat(Long id, Integer count) { - getByIdOrThrow(id).decreaseLeftSeat(count); + public void delete(Long hostId, Long eventId) { + Event event = getByIdOrThrow(eventId); + if (hostId != event.getId()) { + throw new UnauthorizedException("Host"); + } + eventRepository.deleteById(eventId); } @Transactional - public void delete(Long id) { - getByIdOrThrow(id); - eventRepository.deleteById(id); + public void decreaseLeftSeat(Long id, Integer count) { + getByIdOrThrow(id).decreaseLeftSeat(count); } } \ No newline at end of file