From d5cbd8ec0d5a89bc53623cb13de0a8d2c38d05a4 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sat, 7 Mar 2026 16:59:17 +0900 Subject: [PATCH 01/14] =?UTF-8?q?[feat]=20=EA=B3=B5=EC=97=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C/=EC=88=98=EC=A0=95=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/organizer/FestivalController.java | 4 ++-- .../dto/request/FestivalUpdateRequest.java | 2 ++ .../com/amp/domain/festival/entity/Festival.java | 4 ++++ .../service/organizer/FestivalService.java | 15 +++++++++++++++ src/main/java/com/amp/global/s3/S3Service.java | 11 +++++++++-- .../FestivalUpdateRequestValidationTest.java | 14 ++------------ 6 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java b/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java index 27d62fa1..84d09248 100644 --- a/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java +++ b/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java @@ -52,10 +52,10 @@ public ResponseEntity> getFestivalDetail( @Operation(summary = "공연 수정") @ApiErrorCodes(SwaggerResponseDescription.FAIL_TO_UPDATE_FESTIVAL) - @PatchMapping("/{festivalId}") + @PatchMapping(value = "/{festivalId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updateFestival( @PathVariable Long festivalId, - @RequestBody @Valid FestivalUpdateRequest request) { + @ModelAttribute @Valid FestivalUpdateRequest request) { FestivalUpdateResponse response = festivalService.updateFestival(festivalId, request); return ResponseEntity .status(SuccessStatus.FESTIVAL_UPDATE_SUCCESS.getHttpStatus()) diff --git a/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java b/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java index 63048669..cb7a2ee8 100644 --- a/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java +++ b/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java @@ -4,12 +4,14 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; +import org.springframework.web.multipart.MultipartFile; import java.util.List; public record FestivalUpdateRequest( @NotBlank(message = "공연명은 필수입니다.") String title, @NotBlank(message = "공연 장소는 필수입니다.") String location, + MultipartFile mainImage, @Valid @NotEmpty(message = "공연 일시는 필수입니다.") List schedules, @Valid @NotEmpty(message = "1개 이상의 무대/부스 정보는 필수입니다.") List stages, @NotEmpty(message = "최소 1개의 카테고리를 선택해야 합니다.") List activeCategoryIds diff --git a/src/main/java/com/amp/domain/festival/entity/Festival.java b/src/main/java/com/amp/domain/festival/entity/Festival.java index 891cd6e9..7e21ddba 100644 --- a/src/main/java/com/amp/domain/festival/entity/Festival.java +++ b/src/main/java/com/amp/domain/festival/entity/Festival.java @@ -108,4 +108,8 @@ public void updateDates(LocalDate startDate, LocalDate endDate) { public void updateStartTime(LocalTime startTime) { this.startTime = startTime; } + + public void updateMainImage(String mainImageUrl) { + this.mainImageUrl = mainImageUrl; + } } diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index 2f52e146..20903704 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -176,6 +176,21 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ stageService.syncStages(festival, request.stages()); categoryService.syncCategories(festival, request.activeCategoryIds()); + if (request.mainImage() != null && !request.mainImage().isEmpty()) { + String oldKey = s3Service.extractKey(festival.getMainImageUrl()); + String newKey = null; + try { + newKey = uploadImage(request.mainImage()); + s3Service.delete(oldKey); + festival.updateMainImage(s3Service.getPublicUrl(newKey)); + } catch (CustomException e) { + if (newKey != null) { + s3Service.delete(newKey); + } + throw e; + } + } + LocalDate startDate = calculateDate(festival.getSchedules(), FestivalSchedule::getFestivalDate, true); LocalDate endDate = calculateDate(festival.getSchedules(), FestivalSchedule::getFestivalDate, false); LocalTime startTime = calculateTime(festival.getSchedules(), FestivalSchedule::getFestivalTime); diff --git a/src/main/java/com/amp/global/s3/S3Service.java b/src/main/java/com/amp/global/s3/S3Service.java index 81bcd473..6aa15446 100644 --- a/src/main/java/com/amp/global/s3/S3Service.java +++ b/src/main/java/com/amp/global/s3/S3Service.java @@ -2,7 +2,6 @@ import com.amp.global.exception.CustomException; import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; @@ -82,5 +81,13 @@ public String getPublicUrl(String key) { s3Properties.getRegion() ) + key; } -} + public String extractKey(String publicUrl) { + String baseUrl = String.format( + s3Properties.getBaseUrl(), + s3Properties.getBucket(), + s3Properties.getRegion() + ); + return publicUrl.replace(baseUrl, ""); + } +} diff --git a/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java b/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java index e6bd687d..ade2a12c 100644 --- a/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java +++ b/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java @@ -56,17 +56,7 @@ private FestivalUpdateRequest requestWith( List stages, List activeCategoryIds ) { - return new FestivalUpdateRequest(title, location, schedules, stages, activeCategoryIds); - } - - private FestivalUpdateRequest validRequest() throws Exception { - return requestWith( - "테스트 공연", - "고양시 일산서구", - List.of(schedule()), - List.of(validStage()), - List.of(1L) - ); + return new FestivalUpdateRequest(title, location, null, schedules, stages, activeCategoryIds); } private boolean hasViolationOn(Set> violations, String field) { @@ -251,7 +241,7 @@ void passesWhenStageHasTitleAndLocation() throws Exception { assertThat(hasViolationOn(violations, "stages")).isFalse(); } } - + @Nested @DisplayName("activeCategoryIds 검증") class ActiveCategoryIdsValidation { From e5919679eff3781c68eeda893c4c406496b95f2b Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sat, 7 Mar 2026 17:23:42 +0900 Subject: [PATCH 02/14] =?UTF-8?q?[fix]=20=EB=AC=B4=EB=8C=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=20=EC=8B=9C=20=EA=B4=80=EB=A0=A8=20=ED=98=BC=EC=9E=A1?= =?UTF-8?q?=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=8F=84=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=EB=90=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/StageCongestionRepository.java | 2 ++ .../domain/congestion/service/StageService.java | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/amp/domain/congestion/repository/StageCongestionRepository.java b/src/main/java/com/amp/domain/congestion/repository/StageCongestionRepository.java index 93d0db33..ced273c7 100644 --- a/src/main/java/com/amp/domain/congestion/repository/StageCongestionRepository.java +++ b/src/main/java/com/amp/domain/congestion/repository/StageCongestionRepository.java @@ -27,4 +27,6 @@ SELECT MAX(sc2.id) "ORDER BY sc.measuredAt DESC") Optional findLatestByStageId(@Param("stageId") Long stageId); + void deleteByStageIdIn(List stageIds); + } diff --git a/src/main/java/com/amp/domain/congestion/service/StageService.java b/src/main/java/com/amp/domain/congestion/service/StageService.java index 3f3015aa..bbb78d9f 100644 --- a/src/main/java/com/amp/domain/congestion/service/StageService.java +++ b/src/main/java/com/amp/domain/congestion/service/StageService.java @@ -3,6 +3,8 @@ import com.amp.domain.festival.entity.Festival; import com.amp.domain.congestion.dto.request.StageRequest; import com.amp.domain.congestion.entity.Stage; +import com.amp.domain.congestion.repository.StageCongestionRepository; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,8 +17,11 @@ @Service @Transactional +@RequiredArgsConstructor public class StageService { + private final StageCongestionRepository stageCongestionRepository; + public void syncStages(Festival festival, List requests) { if (requests == null || requests.isEmpty()) { return; @@ -29,6 +34,15 @@ public void syncStages(Festival festival, List requests) { Set requestIds = requests.stream() .map(StageRequest::getId).filter(Objects::nonNull).collect(Collectors.toSet()); + List removedStageIds = existStages.stream() + .map(Stage::getId) + .filter(id -> !requestIds.contains(id)) + .collect(Collectors.toList()); + + if (!removedStageIds.isEmpty()) { + stageCongestionRepository.deleteByStageIdIn(removedStageIds); + } + existStages.removeIf(stage -> !requestIds.contains(stage.getId())); for (StageRequest request : requests) { From 2df06cd49f6d4810e87efdeb93ebec7f221537c3 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sat, 7 Mar 2026 17:25:37 +0900 Subject: [PATCH 03/14] =?UTF-8?q?[refactor]=20=EB=AC=B4=EB=8C=80=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=EA=B3=BC=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=98=EA=B2=8C=20JSON=20=ED=8C=8C=EC=8B=B1?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/request/FestivalUpdateRequest.java | 11 +- .../service/organizer/FestivalService.java | 51 ++++-- .../FestivalUpdateRequestValidationTest.java | 167 ++++-------------- 3 files changed, 73 insertions(+), 156 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java b/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java index cb7a2ee8..510de6af 100644 --- a/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java +++ b/src/main/java/com/amp/domain/festival/dto/request/FestivalUpdateRequest.java @@ -1,19 +1,14 @@ package com.amp.domain.festival.dto.request; -import com.amp.domain.congestion.dto.request.StageRequest; -import jakarta.validation.Valid; import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotEmpty; import org.springframework.web.multipart.MultipartFile; -import java.util.List; - public record FestivalUpdateRequest( @NotBlank(message = "공연명은 필수입니다.") String title, @NotBlank(message = "공연 장소는 필수입니다.") String location, MultipartFile mainImage, - @Valid @NotEmpty(message = "공연 일시는 필수입니다.") List schedules, - @Valid @NotEmpty(message = "1개 이상의 무대/부스 정보는 필수입니다.") List stages, - @NotEmpty(message = "최소 1개의 카테고리를 선택해야 합니다.") List activeCategoryIds + @NotBlank(message = "공연 일시는 필수입니다.") String schedules, + @NotBlank(message = "1개 이상의 무대/부스 정보는 필수입니다.") String stages, + @NotBlank(message = "1개 이상의 카테고리 선택은 필수입니다.") String activeCategoryIds ) { } diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index 20903704..9e85040b 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -63,29 +63,23 @@ public class FestivalService { public FestivalCreateResponse createFestival(FestivalCreateRequest request) { User user = authService.getCurrentUser(); - if (!(user instanceof Organizer)) { + if (!(user instanceof Organizer organizer)) { throw new CustomException(CommonErrorCode.FORBIDDEN); } - Organizer organizer = (Organizer) user; List schedules = parseJson( request.schedules(), - new TypeReference>() { - }, + new TypeReference>() {}, FestivalErrorCode.INVALID_SCHEDULE_FORMAT ); - List stages = parseJson( request.stages(), - new TypeReference>() { - }, + new TypeReference>() {}, FestivalErrorCode.INVALID_STAGE_FORMAT ); - List activeCategoryIds = parseJson( request.activeCategoryIds(), - new TypeReference>() { - }, + new TypeReference>() {}, FestivalErrorCode.INVALID_CATEGORY_FORMAT ); @@ -170,11 +164,37 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ validateOrganizer(festival, user); + List schedules = parseJson( + request.schedules(), + new TypeReference>() {}, + FestivalErrorCode.INVALID_SCHEDULE_FORMAT + ); + List stages = parseJson( + request.stages(), + new TypeReference>() {}, + FestivalErrorCode.INVALID_STAGE_FORMAT + ); + List activeCategoryIds = parseJson( + request.activeCategoryIds(), + new TypeReference>() {}, + FestivalErrorCode.INVALID_CATEGORY_FORMAT + ); + + if (schedules == null || schedules.isEmpty()) { + throw new CustomException(FestivalErrorCode.SCHEDULES_REQUIRED); + } + if (stages == null || stages.isEmpty()) { + throw new CustomException(FestivalErrorCode.STAGES_REQUIRED); + } + if (activeCategoryIds == null || activeCategoryIds.isEmpty()) { + throw new CustomException(CategoryErrorCode.CATEGORY_REQUIRED); + } + festival.updateInfo(request.title(), request.location()); - scheduleService.syncSchedules(festival, request.schedules()); - stageService.syncStages(festival, request.stages()); - categoryService.syncCategories(festival, request.activeCategoryIds()); + scheduleService.syncSchedules(festival, schedules); + stageService.syncStages(festival, stages); + categoryService.syncCategories(festival, activeCategoryIds); if (request.mainImage() != null && !request.mainImage().isEmpty()) { String oldKey = s3Service.extractKey(festival.getMainImageUrl()); @@ -212,8 +232,6 @@ public void deleteFestival(Long festivalId) { festivalScheduleRepository.softDeleteByFestivalId(festivalId); stageRepository.softDeleteByFestivalId(festivalId); festivalCategoryRepository.softDeleteByFestivalId(festivalId); - - festivalRepository.softDeleteById(festivalId); } @@ -233,7 +251,6 @@ private LocalTime calculateTime(List schedules, Function ti .orElseThrow(() -> new CustomException(FestivalErrorCode.INVALID_SCHEDULE_FORMAT)); } - @LogExecutionTime("이미지 업로드") private String uploadImage(MultipartFile image) { try { return s3Service.upload(image, "festivals"); @@ -246,6 +263,7 @@ private T parseJson(String json, TypeReference typeReference, FestivalErr if (json == null || json.trim().isEmpty()) { return null; } + try { return objectMapper.readValue(json, typeReference); } catch (Exception e) { @@ -258,7 +276,6 @@ private Festival findFestival(Long id) { .orElseThrow(() -> new CustomException(FestivalErrorCode.FESTIVAL_NOT_FOUND)); } - private void validateOrganizer(Festival festival, User user) { if (!festival.getOrganizer().getId().equals(user.getId())) { throw new CustomException(CommonErrorCode.FORBIDDEN); diff --git a/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java b/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java index ade2a12c..e54a02b6 100644 --- a/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java +++ b/src/test/java/com/amp/domain/festival/dto/request/FestivalUpdateRequestValidationTest.java @@ -1,9 +1,5 @@ package com.amp.domain.festival.dto.request; -import com.amp.domain.congestion.dto.request.StageRequest; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import jakarta.validation.Validator; @@ -12,7 +8,6 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; @@ -21,40 +16,22 @@ class FestivalUpdateRequestValidationTest { private static Validator validator; - private static ObjectMapper objectMapper; + + private static final String VALID_SCHEDULES = "[{\"festivalDate\":\"2026-08-01\",\"festivalTime\":\"18:00\"}]"; + private static final String VALID_STAGES = "[{\"title\":\"메인 무대\"}]"; + private static final String VALID_CATEGORY_IDS = "[1]"; @BeforeAll - static void setUp() throws Exception { + static void setUp() { validator = Validation.buildDefaultValidatorFactory().getValidator(); - objectMapper = new ObjectMapper() - .registerModule(new JavaTimeModule()) - .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - } - - private ScheduleRequest schedule() throws Exception { - return objectMapper.readValue( - "{\"festivalDate\":\"2026-08-01\",\"festivalTime\":\"18:00\"}", - ScheduleRequest.class - ); - } - - private StageRequest validStage() throws Exception { - return objectMapper.readValue("{\"title\":\"메인 무대\"}", StageRequest.class); - } - - private StageRequest stageWith(String title, String location) throws Exception { - String json = location != null - ? String.format("{\"title\":\"%s\",\"location\":\"%s\"}", title, location) - : String.format("{\"title\":\"%s\"}", title); - return objectMapper.readValue(json, StageRequest.class); } private FestivalUpdateRequest requestWith( String title, String location, - List schedules, - List stages, - List activeCategoryIds + String schedules, + String stages, + String activeCategoryIds ) { return new FestivalUpdateRequest(title, location, null, schedules, stages, activeCategoryIds); } @@ -70,18 +47,18 @@ class TitleValidation { @Test @DisplayName("title이 null이면 유효성 검증에 실패한다") - void failsWhenTitleIsNull() throws Exception { + void failsWhenTitleIsNull() { Set> violations = validator.validate( - requestWith(null, "고양시 일산서구", List.of(schedule()), List.of(validStage()), List.of(1L)) + requestWith(null, "고양시 일산서구", VALID_SCHEDULES, VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "title")).isTrue(); } @Test @DisplayName("title이 빈 문자열이면 유효성 검증에 실패한다") - void failsWhenTitleIsBlank() throws Exception { + void failsWhenTitleIsBlank() { Set> violations = validator.validate( - requestWith("", "고양시 일산서구", List.of(schedule()), List.of(validStage()), List.of(1L)) + requestWith("", "고양시 일산서구", VALID_SCHEDULES, VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "title")).isTrue(); } @@ -93,102 +70,73 @@ class LocationValidation { @Test @DisplayName("location이 null이면 유효성 검증에 실패한다") - void failsWhenLocationIsNull() throws Exception { + void failsWhenLocationIsNull() { Set> violations = validator.validate( - requestWith("테스트 공연", null, List.of(schedule()), List.of(validStage()), List.of(1L)) + requestWith("테스트 공연", null, VALID_SCHEDULES, VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "location")).isTrue(); } @Test @DisplayName("location이 빈 문자열이면 유효성 검증에 실패한다") - void failsWhenLocationIsBlank() throws Exception { + void failsWhenLocationIsBlank() { Set> violations = validator.validate( - requestWith("테스트 공연", "", List.of(schedule()), List.of(validStage()), List.of(1L)) + requestWith("테스트 공연", "", VALID_SCHEDULES, VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "location")).isTrue(); } } @Nested - @DisplayName("schedules 리스트 검증") + @DisplayName("schedules 검증") class SchedulesValidation { @Test @DisplayName("schedules가 null이면 유효성 검증에 실패한다") - void failsWhenSchedulesIsNull() throws Exception { + void failsWhenSchedulesIsNull() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", null, List.of(validStage()), List.of(1L)) + requestWith("테스트 공연", "고양시 일산서구", null, VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "schedules")).isTrue(); } @Test - @DisplayName("schedules가 빈 리스트이면 유효성 검증에 실패한다") - void failsWhenSchedulesIsEmpty() throws Exception { + @DisplayName("schedules가 빈 문자열이면 유효성 검증에 실패한다") + void failsWhenSchedulesIsBlank() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(), List.of(validStage()), List.of(1L)) + requestWith("테스트 공연", "고양시 일산서구", "", VALID_STAGES, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "schedules")).isTrue(); } } @Nested - @DisplayName("schedule 내부 필드 검증 - @Valid 캐스케이드") - class ScheduleFieldValidation { - - @Test - @DisplayName("festivalDate가 null이면 유효성 검증에 실패한다") - void failsWhenFestivalDateIsNull() throws Exception { - ScheduleRequest noDate = objectMapper.readValue( - "{\"festivalTime\":\"18:00\"}", ScheduleRequest.class - ); - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(noDate), List.of(validStage()), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "schedules")).isTrue(); - } - - @Test - @DisplayName("festivalTime이 null이면 유효성 검증에 실패한다") - void failsWhenFestivalTimeIsNull() throws Exception { - ScheduleRequest noTime = objectMapper.readValue( - "{\"festivalDate\":\"2026-08-01\"}", ScheduleRequest.class - ); - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(noTime), List.of(validStage()), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "schedules")).isTrue(); - } - } - - @Nested - @DisplayName("stages 리스트 검증") - class InvalidStagesList { + @DisplayName("stages 검증") + class StagesValidation { @Test @DisplayName("stages가 null이면 유효성 검증에 실패한다") - void failsWhenStagesIsNull() throws Exception { + void failsWhenStagesIsNull() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), null, List.of(1L)) + requestWith("테스트 공연", "고양시 일산서구", VALID_SCHEDULES, null, VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "stages")).isTrue(); } @Test - @DisplayName("stages가 빈 리스트이면 유효성 검증에 실패한다") - void failsWhenStagesIsEmpty() throws Exception { + @DisplayName("stages가 빈 문자열이면 유효성 검증에 실패한다") + void failsWhenStagesIsBlank() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(), List.of(1L)) + requestWith("테스트 공연", "고양시 일산서구", VALID_SCHEDULES, "", VALID_CATEGORY_IDS) ); assertThat(hasViolationOn(violations, "stages")).isTrue(); } @Test @DisplayName("stages 위반 시 에러 메시지가 올바르다") - void violationMessageIsCorrect() throws Exception { + void violationMessageIsCorrect() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), null, List.of(1L)) + requestWith("테스트 공연", "고양시 일산서구", VALID_SCHEDULES, null, VALID_CATEGORY_IDS) ); String message = violations.stream() .filter(v -> v.getPropertyPath().toString().equals("stages")) @@ -199,67 +147,24 @@ void violationMessageIsCorrect() throws Exception { } } - @Nested - @DisplayName("stage 내부 필드 검증 - @Valid 캐스케이드") - class StageFieldValidation { - - @Test - @DisplayName("stage의 title이 null이면 유효성 검증에 실패한다") - void failsWhenStageTitleIsNull() throws Exception { - StageRequest nullTitle = objectMapper.readValue("{}", StageRequest.class); - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(nullTitle), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "stages")).isTrue(); - } - - @Test - @DisplayName("stage의 title이 빈 문자열이면 유효성 검증에 실패한다") - void failsWhenStageTitleIsBlank() throws Exception { - StageRequest blankTitle = objectMapper.readValue("{\"title\":\"\"}", StageRequest.class); - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(blankTitle), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "stages")).isTrue(); - } - - @Test - @DisplayName("title만 있고 location이 없어도 유효성 검증을 통과한다 (location은 선택)") - void passesWhenStageHasTitleOnly() throws Exception { - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(stageWith("메인 무대", null)), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "stages")).isFalse(); - } - - @Test - @DisplayName("title과 location이 모두 있으면 유효성 검증을 통과한다") - void passesWhenStageHasTitleAndLocation() throws Exception { - Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(stageWith("메인 무대", "A구역")), List.of(1L)) - ); - assertThat(hasViolationOn(violations, "stages")).isFalse(); - } - } - @Nested @DisplayName("activeCategoryIds 검증") class ActiveCategoryIdsValidation { @Test @DisplayName("activeCategoryIds가 null이면 유효성 검증에 실패한다") - void failsWhenCategoryIdsIsNull() throws Exception { + void failsWhenCategoryIdsIsNull() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(validStage()), null) + requestWith("테스트 공연", "고양시 일산서구", VALID_SCHEDULES, VALID_STAGES, null) ); assertThat(hasViolationOn(violations, "activeCategoryIds")).isTrue(); } @Test - @DisplayName("activeCategoryIds가 빈 리스트이면 유효성 검증에 실패한다") - void failsWhenCategoryIdsIsEmpty() throws Exception { + @DisplayName("activeCategoryIds가 빈 문자열이면 유효성 검증에 실패한다") + void failsWhenCategoryIdsIsBlank() { Set> violations = validator.validate( - requestWith("테스트 공연", "고양시 일산서구", List.of(schedule()), List.of(validStage()), List.of()) + requestWith("테스트 공연", "고양시 일산서구", VALID_SCHEDULES, VALID_STAGES, "") ); assertThat(hasViolationOn(violations, "activeCategoryIds")).isTrue(); } From ed1a21cda2825a3f68d3f11b9f798df634e43bf4 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sat, 7 Mar 2026 18:46:46 +0900 Subject: [PATCH 04/14] =?UTF-8?q?[refactor]=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EB=94=94=20=ED=8C=8C=EC=8B=B1=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festival/service/organizer/FestivalService.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index 9e85040b..30c3a418 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -78,7 +78,7 @@ public FestivalCreateResponse createFestival(FestivalCreateRequest request) { FestivalErrorCode.INVALID_STAGE_FORMAT ); List activeCategoryIds = parseJson( - request.activeCategoryIds(), + normalizeJsonArray(request.activeCategoryIds()), new TypeReference>() {}, FestivalErrorCode.INVALID_CATEGORY_FORMAT ); @@ -175,7 +175,7 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ FestivalErrorCode.INVALID_STAGE_FORMAT ); List activeCategoryIds = parseJson( - request.activeCategoryIds(), + normalizeJsonArray(request.activeCategoryIds()), new TypeReference>() {}, FestivalErrorCode.INVALID_CATEGORY_FORMAT ); @@ -259,6 +259,12 @@ private String uploadImage(MultipartFile image) { } } + private String normalizeJsonArray(String value) { + if (value == null) return null; + String trimmed = value.trim(); + return trimmed.startsWith("[") ? trimmed : "[" + trimmed + "]"; + } + private T parseJson(String json, TypeReference typeReference, FestivalErrorCode errorCode) { if (json == null || json.trim().isEmpty()) { return null; From cf78eef709754c278bd1287f022b14c53a0d3bf5 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sun, 8 Mar 2026 16:37:02 +0900 Subject: [PATCH 05/14] =?UTF-8?q?[refactor]=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EB=A9=94=EC=86=8C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/organizer/FestivalService.java | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index 30c3a418..f38624d3 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -69,29 +69,24 @@ public FestivalCreateResponse createFestival(FestivalCreateRequest request) { List schedules = parseJson( request.schedules(), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_SCHEDULE_FORMAT ); List stages = parseJson( request.stages(), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_STAGE_FORMAT ); List activeCategoryIds = parseJson( normalizeJsonArray(request.activeCategoryIds()), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_CATEGORY_FORMAT ); - if (schedules == null || schedules.isEmpty()) { - throw new CustomException(FestivalErrorCode.SCHEDULES_REQUIRED); - } - if (stages == null || stages.isEmpty()) { - throw new CustomException(FestivalErrorCode.STAGES_REQUIRED); - } - if (activeCategoryIds == null || activeCategoryIds.isEmpty()) { - throw new CustomException(CategoryErrorCode.CATEGORY_REQUIRED); - } + checkNullField(schedules, stages, activeCategoryIds); ScheduleRequest earliestSchedule = schedules.stream() .min(Comparator.comparing(ScheduleRequest::getFestivalDate) @@ -166,29 +161,24 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ List schedules = parseJson( request.schedules(), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_SCHEDULE_FORMAT ); List stages = parseJson( request.stages(), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_STAGE_FORMAT ); List activeCategoryIds = parseJson( normalizeJsonArray(request.activeCategoryIds()), - new TypeReference>() {}, + new TypeReference>() { + }, FestivalErrorCode.INVALID_CATEGORY_FORMAT ); - if (schedules == null || schedules.isEmpty()) { - throw new CustomException(FestivalErrorCode.SCHEDULES_REQUIRED); - } - if (stages == null || stages.isEmpty()) { - throw new CustomException(FestivalErrorCode.STAGES_REQUIRED); - } - if (activeCategoryIds == null || activeCategoryIds.isEmpty()) { - throw new CustomException(CategoryErrorCode.CATEGORY_REQUIRED); - } + checkNullField(schedules, stages, activeCategoryIds); festival.updateInfo(request.title(), request.location()); @@ -287,4 +277,16 @@ private void validateOrganizer(Festival festival, User user) { throw new CustomException(CommonErrorCode.FORBIDDEN); } } + + private static void checkNullField(List schedules, List stages, List activeCategoryIds) { + if (schedules == null || schedules.isEmpty()) { + throw new CustomException(FestivalErrorCode.SCHEDULES_REQUIRED); + } + if (stages == null || stages.isEmpty()) { + throw new CustomException(FestivalErrorCode.STAGES_REQUIRED); + } + if (activeCategoryIds == null || activeCategoryIds.isEmpty()) { + throw new CustomException(CategoryErrorCode.CATEGORY_REQUIRED); + } + } } From 467d403bb84d85df59e42bf58bc667a92d36c6f8 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sun, 8 Mar 2026 17:24:00 +0900 Subject: [PATCH 06/14] =?UTF-8?q?[refactor]=20PutMapping=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festival/controller/organizer/FestivalController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java b/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java index 84d09248..5daeabc1 100644 --- a/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java +++ b/src/main/java/com/amp/domain/festival/controller/organizer/FestivalController.java @@ -52,7 +52,7 @@ public ResponseEntity> getFestivalDetail( @Operation(summary = "공연 수정") @ApiErrorCodes(SwaggerResponseDescription.FAIL_TO_UPDATE_FESTIVAL) - @PatchMapping(value = "/{festivalId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + @PutMapping(value = "/{festivalId}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> updateFestival( @PathVariable Long festivalId, @ModelAttribute @Valid FestivalUpdateRequest request) { From d14068e7f09c737f8ff6cb55c5df4c5bef099ed6 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sun, 8 Mar 2026 17:46:37 +0900 Subject: [PATCH 07/14] =?UTF-8?q?[refactor]=20=EC=BB=A4=EB=B0=8B=20?= =?UTF-8?q?=ED=9B=84=20=EC=9D=B4=EC=A0=84=EC=9D=B4=EB=AF=B8=EC=A7=80=20s3?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EC=BB=A4=EB=B0=8B=EA=B3=BC?= =?UTF-8?q?=20=EB=A1=A4=EB=B0=B1=20=ED=9B=84=20=EC=83=88=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20s3=20=EC=82=AD=EC=A0=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/organizer/FestivalService.java | 29 ++++++++++++------- .../java/com/amp/global/s3/S3ErrorCode.java | 1 + 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index f38624d3..1094ec77 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -20,7 +20,6 @@ import com.amp.domain.congestion.service.StageService; import com.amp.domain.user.entity.Organizer; import com.amp.domain.user.entity.User; -import com.amp.global.annotation.LogExecutionTime; import com.amp.global.common.CommonErrorCode; import com.amp.global.exception.CustomException; import com.amp.global.s3.S3ErrorCode; @@ -32,6 +31,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; @@ -188,17 +189,23 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ if (request.mainImage() != null && !request.mainImage().isEmpty()) { String oldKey = s3Service.extractKey(festival.getMainImageUrl()); - String newKey = null; - try { - newKey = uploadImage(request.mainImage()); - s3Service.delete(oldKey); - festival.updateMainImage(s3Service.getPublicUrl(newKey)); - } catch (CustomException e) { - if (newKey != null) { - s3Service.delete(newKey); + String newKey = uploadImage(request.mainImage()); + + festival.updateMainImage(s3Service.getPublicUrl(newKey)); + + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + s3Service.delete(oldKey); } - throw e; - } + + @Override + public void afterCompletion(int status) { + if (status == STATUS_ROLLED_BACK) { + s3Service.delete(newKey); + } + } + }); } LocalDate startDate = calculateDate(festival.getSchedules(), FestivalSchedule::getFestivalDate, true); diff --git a/src/main/java/com/amp/global/s3/S3ErrorCode.java b/src/main/java/com/amp/global/s3/S3ErrorCode.java index 366f85f4..42521034 100644 --- a/src/main/java/com/amp/global/s3/S3ErrorCode.java +++ b/src/main/java/com/amp/global/s3/S3ErrorCode.java @@ -13,6 +13,7 @@ public enum S3ErrorCode implements ErrorCode { INVALID_IMAGE_IMAGE(HttpStatus.BAD_REQUEST, "S3", "001", "이미지 파일만 업로드 가능합니다."), FILE_NAME_NOT_FOUND(HttpStatus.BAD_REQUEST, "S3", "002", "파일명이 없습니다."), INVALID_DIRECTORY_ROUTE(HttpStatus.BAD_REQUEST, "S3", "003", "잘못된 디렉토리 경로입니다."), + INVALID_IMAGE_URL(HttpStatus.BAD_REQUEST, "S3", "004", "유효하지 않은 이미지 URL입니다."), // 500 Internal Server Error S3_UPLOAD_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "S3", "001", "S3 업로드를 실패하였습니다."), From 551030cbfe2b8b6ca9e30f66bb2d5fa0c6317649 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sun, 8 Mar 2026 17:47:05 +0900 Subject: [PATCH 08/14] =?UTF-8?q?[refactor]=20=EC=9E=98=EB=AA=BB=EB=90=9C?= =?UTF-8?q?=20=ED=82=A4=EA=B0=80=20=EC=A0=84=EB=8B=AC=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EA=B2=83=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - url이 baseUrl로 시작하지 않으면 예외 --- src/main/java/com/amp/global/s3/S3Service.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/amp/global/s3/S3Service.java b/src/main/java/com/amp/global/s3/S3Service.java index 6aa15446..8b517394 100644 --- a/src/main/java/com/amp/global/s3/S3Service.java +++ b/src/main/java/com/amp/global/s3/S3Service.java @@ -88,6 +88,9 @@ public String extractKey(String publicUrl) { s3Properties.getBucket(), s3Properties.getRegion() ); - return publicUrl.replace(baseUrl, ""); + if (!publicUrl.startsWith(baseUrl)) { + throw new CustomException(S3ErrorCode.INVALID_IMAGE_URL); + } + return publicUrl.substring(baseUrl.length()); } } From 8d9091673d2baff318ba8fd5f56b31e1d9bbe98d Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Sun, 8 Mar 2026 17:58:24 +0900 Subject: [PATCH 09/14] =?UTF-8?q?[refactor]=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=BD=9C=EB=B0=B1=EC=97=90=EC=84=9C=20S3=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../festival/service/organizer/FestivalService.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java index 1094ec77..4fc101b3 100644 --- a/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java +++ b/src/main/java/com/amp/domain/festival/service/organizer/FestivalService.java @@ -196,13 +196,21 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { @Override public void afterCommit() { - s3Service.delete(oldKey); + try { + s3Service.delete(oldKey); + } catch (Exception e) { + log.error("[S3] 커밋 후 구 이미지 삭제 실패 — key: {}, 원인: {}", oldKey, e.getMessage()); + } } @Override public void afterCompletion(int status) { if (status == STATUS_ROLLED_BACK) { - s3Service.delete(newKey); + try { + s3Service.delete(newKey); + } catch (Exception e) { + log.error("[S3] 롤백 후 새 이미지 삭제 실패 — key: {}, 원인: {}", newKey, e.getMessage()); + } } } }); From 6a765a97a652eaf299f1f6e57df782172a338730 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Wed, 11 Mar 2026 00:37:48 +0900 Subject: [PATCH 10/14] =?UTF-8?q?[refactor]=20dday=20->=20dDay=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B9=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amp/domain/festival/dto/response/FestivalInfoResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/amp/domain/festival/dto/response/FestivalInfoResponse.java b/src/main/java/com/amp/domain/festival/dto/response/FestivalInfoResponse.java index e7a9b6df..9ff316d0 100644 --- a/src/main/java/com/amp/domain/festival/dto/response/FestivalInfoResponse.java +++ b/src/main/java/com/amp/domain/festival/dto/response/FestivalInfoResponse.java @@ -13,7 +13,7 @@ public record FestivalInfoResponse( String location, String period, Boolean isWishlist, - Long dday, + Long dDay, List activeCategories) { public static FestivalInfoResponse from(Festival festival, Boolean isWishlist) { return new FestivalInfoResponse( From 0fc62cee940a22a95875f1b06e8a936f29207a06 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Wed, 11 Mar 2026 00:45:33 +0900 Subject: [PATCH 11/14] =?UTF-8?q?[refactor]=20NPE=20=EA=B2=80=EC=A6=9D=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/amp/global/s3/S3Service.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/amp/global/s3/S3Service.java b/src/main/java/com/amp/global/s3/S3Service.java index 8b517394..8cbae736 100644 --- a/src/main/java/com/amp/global/s3/S3Service.java +++ b/src/main/java/com/amp/global/s3/S3Service.java @@ -88,7 +88,7 @@ public String extractKey(String publicUrl) { s3Properties.getBucket(), s3Properties.getRegion() ); - if (!publicUrl.startsWith(baseUrl)) { + if (publicUrl == null || !publicUrl.startsWith(baseUrl)) { throw new CustomException(S3ErrorCode.INVALID_IMAGE_URL); } return publicUrl.substring(baseUrl.length()); From 3c16e1cb2a7df8587f89ada8245766f8f7aaf042 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Wed, 11 Mar 2026 00:53:03 +0900 Subject: [PATCH 12/14] =?UTF-8?q?[fix]=20Hibernate=20=EC=BA=90=EC=8B=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=9D=B8=ED=95=B4=20=EA=B4=80=EA=B0=9D=20=EB=A7=88?= =?UTF-8?q?=EC=9D=B4=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8B=89=EB=84=A4?= =?UTF-8?q?=EC=9E=84=20null=20=EB=B0=98=ED=99=98=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../amp/domain/audience/controller/AudienceController.java | 3 +-- .../java/com/amp/domain/audience/service/AudienceService.java | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/amp/domain/audience/controller/AudienceController.java b/src/main/java/com/amp/domain/audience/controller/AudienceController.java index 911ca21a..91bbb0e9 100644 --- a/src/main/java/com/amp/domain/audience/controller/AudienceController.java +++ b/src/main/java/com/amp/domain/audience/controller/AudienceController.java @@ -43,8 +43,7 @@ public class AudienceController { @PreAuthorize("hasRole('AUDIENCE')") public ResponseEntity> getMyPage( @AuthenticationPrincipal CustomUserPrincipal principal) { - Long userId = principal.getUserId(); - AudienceMyPageResponse response = audienceService.getMyPage(userId); + AudienceMyPageResponse response = audienceService.getMyPage(principal.getEmail()); return ResponseEntity .status(SuccessStatus.USER_PROFILE_RETRIEVED.getHttpStatus()) .body(BaseResponse.of(SuccessStatus.USER_PROFILE_RETRIEVED, response)); diff --git a/src/main/java/com/amp/domain/audience/service/AudienceService.java b/src/main/java/com/amp/domain/audience/service/AudienceService.java index 6349a127..b4696c12 100644 --- a/src/main/java/com/amp/domain/audience/service/AudienceService.java +++ b/src/main/java/com/amp/domain/audience/service/AudienceService.java @@ -17,8 +17,8 @@ public class AudienceService { private final AudienceRepository audienceRepository; - public AudienceMyPageResponse getMyPage(Long userId) { - Audience audience = audienceRepository.findById(userId) + public AudienceMyPageResponse getMyPage(String email) { + Audience audience = audienceRepository.findByEmail(email) .orElseThrow(() -> new CustomException(USER_NOT_FOUND)); return AudienceMyPageResponse.from(audience); } From 199e082e2649823f647c4dcd742e9d8f7cc9e630 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Wed, 11 Mar 2026 01:00:35 +0900 Subject: [PATCH 13/14] =?UTF-8?q?[refactor]=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20db=20=EC=82=BD=EC=9E=85=20=EC=8B=9C,=20=EC=88=9C?= =?UTF-8?q?=EC=84=9C=20=EC=A0=95=EB=A0=AC=ED=95=B4=EC=84=9C=20=EC=82=BD?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/amp/domain/festival/entity/Festival.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/amp/domain/festival/entity/Festival.java b/src/main/java/com/amp/domain/festival/entity/Festival.java index 7e21ddba..b2b55624 100644 --- a/src/main/java/com/amp/domain/festival/entity/Festival.java +++ b/src/main/java/com/amp/domain/festival/entity/Festival.java @@ -60,6 +60,7 @@ public class Festival extends BaseTimeEntity { private List stages = new ArrayList<>(); @OneToMany(mappedBy = "festival", cascade = CascadeType.ALL, orphanRemoval = true) + @OrderBy("category.id ASC") private List festivalCategories = new ArrayList<>(); @Column(name = "deleted_at") From 1d2191fc4a2539369140271510c3041de53e5229 Mon Sep 17 00:00:00 2001 From: Chae-Yu Date: Wed, 11 Mar 2026 01:05:40 +0900 Subject: [PATCH 14/14] =?UTF-8?q?[fix]=20=ED=8E=98=EC=8A=A4=ED=8B=B0?= =?UTF-8?q?=EB=B2=8C=20=EC=8B=9C=EA=B0=84=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=EB=A1=9C=20=EC=96=BC=EB=A6=AC=20=EB=A6=AC?= =?UTF-8?q?=ED=84=B4=20=EB=AC=B8=EC=A0=9C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../congestion/service/CongestionCalculateServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/com/amp/domain/congestion/service/CongestionCalculateServiceTest.java b/src/test/java/com/amp/domain/congestion/service/CongestionCalculateServiceTest.java index 667e3bad..4e060992 100644 --- a/src/test/java/com/amp/domain/congestion/service/CongestionCalculateServiceTest.java +++ b/src/test/java/com/amp/domain/congestion/service/CongestionCalculateServiceTest.java @@ -69,7 +69,7 @@ void calculateWeightedAverageTest() { .willReturn(Optional.of(FestivalSchedule.builder() .festival(festival) .festivalDate(LocalDate.now()) - .festivalTime(LocalTime.of(20, 0)) + .festivalTime(LocalTime.of(0, 0)) .build())); given(stageRepository.findById(stageId)).willReturn(Optional.of(stage)); given(reportRepository.findRecentReports(eq(stageId), any())).willReturn(List.of(report1, report2));