-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat/#213] 공연 이미지 수정/삭제 API #215
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
d5cbd8e
e591967
2df06cd
ed1a21c
cf78eef
467d403
d14068e
551030c
8d90916
6a765a9
0fc62ce
3c16e1c
199e082
1d2191f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +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 java.util.List; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| public record FestivalUpdateRequest( | ||
| @NotBlank(message = "공연명은 필수입니다.") String title, | ||
| @NotBlank(message = "공연 장소는 필수입니다.") String location, | ||
| @Valid @NotEmpty(message = "공연 일시는 필수입니다.") List<ScheduleRequest> schedules, | ||
| @Valid @NotEmpty(message = "1개 이상의 무대/부스 정보는 필수입니다.") List<StageRequest> stages, | ||
| @NotEmpty(message = "최소 1개의 카테고리를 선택해야 합니다.") List<Long> activeCategoryIds | ||
| MultipartFile mainImage, | ||
| @NotBlank(message = "공연 일시는 필수입니다.") String schedules, | ||
| @NotBlank(message = "1개 이상의 무대/부스 정보는 필수입니다.") String stages, | ||
| @NotBlank(message = "1개 이상의 카테고리 선택은 필수입니다.") String activeCategoryIds | ||
chaeyuuu marked this conversation as resolved.
Show resolved
Hide resolved
Comment on lines
+9
to
+12
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Valid 가 빠지면서 내부 필드 값에 대한 검증이 더이상 진행되지 않는 걸로 보이는데 상관없는건가요??
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컨트롤러 파라미터에 FestivalUpdateRequest에 한 번에 붙여놨습니다. 현재 해당 클래스의 dto에는 중첩객체가 없고 단순 타입만 있어서 생략했습니다. 필요하다면 추가하겠습니다~!! |
||
| ) { | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -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; | ||||||||||||||||||||||||
|
|
@@ -63,41 +64,30 @@ 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<ScheduleRequest> schedules = parseJson( | ||||||||||||||||||||||||
| request.schedules(), | ||||||||||||||||||||||||
| new TypeReference<List<ScheduleRequest>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| FestivalErrorCode.INVALID_SCHEDULE_FORMAT | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| List<StageRequest> stages = parseJson( | ||||||||||||||||||||||||
| request.stages(), | ||||||||||||||||||||||||
| new TypeReference<List<StageRequest>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| FestivalErrorCode.INVALID_STAGE_FORMAT | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| List<Long> activeCategoryIds = parseJson( | ||||||||||||||||||||||||
| request.activeCategoryIds(), | ||||||||||||||||||||||||
| normalizeJsonArray(request.activeCategoryIds()), | ||||||||||||||||||||||||
| new TypeReference<List<Long>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||
|
|
@@ -170,11 +160,53 @@ public FestivalUpdateResponse updateFestival(Long festivalId, FestivalUpdateRequ | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| validateOrganizer(festival, user); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| List<ScheduleRequest> schedules = parseJson( | ||||||||||||||||||||||||
| request.schedules(), | ||||||||||||||||||||||||
| new TypeReference<List<ScheduleRequest>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| FestivalErrorCode.INVALID_SCHEDULE_FORMAT | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| List<StageRequest> stages = parseJson( | ||||||||||||||||||||||||
| request.stages(), | ||||||||||||||||||||||||
| new TypeReference<List<StageRequest>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| FestivalErrorCode.INVALID_STAGE_FORMAT | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
| List<Long> activeCategoryIds = parseJson( | ||||||||||||||||||||||||
| normalizeJsonArray(request.activeCategoryIds()), | ||||||||||||||||||||||||
| new TypeReference<List<Long>>() { | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
| FestivalErrorCode.INVALID_CATEGORY_FORMAT | ||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| checkNullField(schedules, stages, activeCategoryIds); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| 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()); | ||||||||||||||||||||||||
| String newKey = uploadImage(request.mainImage()); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| festival.updateMainImage(s3Service.getPublicUrl(newKey)); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { | ||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||
| public void afterCommit() { | ||||||||||||||||||||||||
| s3Service.delete(oldKey); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @Override | ||||||||||||||||||||||||
| public void afterCompletion(int status) { | ||||||||||||||||||||||||
| if (status == STATUS_ROLLED_BACK) { | ||||||||||||||||||||||||
| s3Service.delete(newKey); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| LocalDate startDate = calculateDate(festival.getSchedules(), FestivalSchedule::getFestivalDate, true); | ||||||||||||||||||||||||
| LocalDate endDate = calculateDate(festival.getSchedules(), FestivalSchedule::getFestivalDate, false); | ||||||||||||||||||||||||
|
|
@@ -197,8 +229,6 @@ public void deleteFestival(Long festivalId) { | |||||||||||||||||||||||
| festivalScheduleRepository.softDeleteByFestivalId(festivalId); | ||||||||||||||||||||||||
| stageRepository.softDeleteByFestivalId(festivalId); | ||||||||||||||||||||||||
| festivalCategoryRepository.softDeleteByFestivalId(festivalId); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| festivalRepository.softDeleteById(festivalId); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
|
|
@@ -218,7 +248,6 @@ private <T> LocalTime calculateTime(List<T> schedules, Function<T, LocalTime> ti | |||||||||||||||||||||||
| .orElseThrow(() -> new CustomException(FestivalErrorCode.INVALID_SCHEDULE_FORMAT)); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| @LogExecutionTime("이미지 업로드") | ||||||||||||||||||||||||
| private String uploadImage(MultipartFile image) { | ||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| return s3Service.upload(image, "festivals"); | ||||||||||||||||||||||||
|
|
@@ -227,10 +256,17 @@ private String uploadImage(MultipartFile image) { | |||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private String normalizeJsonArray(String value) { | ||||||||||||||||||||||||
| if (value == null) return null; | ||||||||||||||||||||||||
| String trimmed = value.trim(); | ||||||||||||||||||||||||
| return trimmed.startsWith("[") ? trimmed : "[" + trimmed + "]"; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
+267
to
+271
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Swagger의 배열 전송 방식 처리
한 가지 고려사항: 빈 문자열( 선택적 개선안 private String normalizeJsonArray(String value) {
if (value == null) return null;
String trimmed = value.trim();
+ if (trimmed.isEmpty()) return null;
return trimmed.startsWith("[") ? trimmed : "[" + trimmed + "]";
}현재 구현도 정상 동작하므로 선택적 개선입니다. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private <T> T parseJson(String json, TypeReference<T> typeReference, FestivalErrorCode errorCode) { | ||||||||||||||||||||||||
| if (json == null || json.trim().isEmpty()) { | ||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| return objectMapper.readValue(json, typeReference); | ||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||
|
|
@@ -243,10 +279,21 @@ 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); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| private static void checkNullField(List<ScheduleRequest> schedules, List<StageRequest> stages, List<Long> 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); | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.