Skip to content

Commit

Permalink
feat: 페스티벌 수정 사항 요청 api 구현 (#146)
Browse files Browse the repository at this point in the history
  • Loading branch information
punchdrunkard authored Sep 3, 2024
1 parent c452387 commit d229461
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
Expand All @@ -31,13 +32,15 @@
import com.odiga.fiesta.common.error.exception.CustomException;
import com.odiga.fiesta.festival.dto.request.FestivalCreateRequest;
import com.odiga.fiesta.festival.dto.request.FestivalFilterRequest;
import com.odiga.fiesta.festival.dto.request.CreateFestivalModificationRequest;
import com.odiga.fiesta.festival.dto.response.FestivalAndLocation;
import com.odiga.fiesta.festival.dto.response.FestivalBasic;
import com.odiga.fiesta.festival.dto.response.FestivalBookmarkResponse;
import com.odiga.fiesta.festival.dto.response.FestivalCreateResponse;
import com.odiga.fiesta.festival.dto.response.FestivalDetailResponse;
import com.odiga.fiesta.festival.dto.response.FestivalInfo;
import com.odiga.fiesta.festival.dto.response.FestivalInfoWithBookmark;
import com.odiga.fiesta.festival.dto.response.FestivalModificationResponse;
import com.odiga.fiesta.festival.dto.response.FestivalMonthlyResponse;
import com.odiga.fiesta.festival.dto.response.RecommendFestivalResponse;
import com.odiga.fiesta.festival.service.FestivalBookmarkService;
Expand Down Expand Up @@ -255,6 +258,23 @@ public ResponseEntity<BasicResponse<RecommendFestivalResponse>> getRecommendFest
return ResponseEntity.ok(BasicResponse.ok("유형별 추천 페스티벌 조회 성공", recommendFestivals));
}

@Operation(summary = "페스티벌 수정 사항 요청", description = "페스티벌 수정 사항을 요청을 생성합니다.")
@PostMapping("/{festivalId}/request")
public ResponseEntity<BasicResponse<FestivalModificationResponse>> createFestivalRequest(
@AuthUser User user,
@PathVariable Long festivalId,
@RequestBody @Valid CreateFestivalModificationRequest request
) {
checkLogin(user);

FestivalModificationResponse response = festivalService.createFestivalRequest(
isNull(user) ? null : user, festivalId, request);

return ResponseEntity.created(
URI.create("/api/v1/festivals/" + festivalId))
.body(BasicResponse.created("페스티벌 수정 사항 요청 성공", response));
}

private void validateFestivalDay(int year, int month, int day) {
YearMonth yearMonth = YearMonth.of(year, month);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.odiga.fiesta.festival.domain;

import static jakarta.persistence.GenerationType.*;

import com.odiga.fiesta.common.domain.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor
@Table(name = "festival_modification_request")
public class FestivalModificationRequest extends BaseEntity {

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "festival_modification_id")
private Long id;

@Column(name = "festival_id", nullable = false)
private Long festivalId;

@Column(name = "user_id", nullable = false)
private Long userId;

@Column(name = "content", length = 500, nullable = false)
private String content;

@Column(name = "is_pending", nullable = false)
private boolean isPending;

@Builder
public FestivalModificationRequest(Long id, Long festivalId, Long userId,
String content, boolean isPending) {
this.id = id;
this.festivalId = festivalId;
this.userId = userId;
this.content = content;
this.isPending = isPending;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.odiga.fiesta.festival.dto.request;

import jakarta.validation.constraints.NotBlank;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class CreateFestivalModificationRequest {

@NotBlank
private String content;

@Builder
public CreateFestivalModificationRequest(String content) {
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.odiga.fiesta.festival.dto.response;

import java.time.LocalDateTime;

import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@Getter
public class FestivalModificationResponse {

private Long festivalId;
private Long requestId;
private boolean isPending;
private LocalDateTime createdAt;

@Builder
public FestivalModificationResponse(Long festivalId, Long requestId, boolean isPending, LocalDateTime createdAt) {
this.festivalId = festivalId;
this.requestId = requestId;
this.isPending = isPending;
this.createdAt = createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.odiga.fiesta.festival.repository;

import org.springframework.data.jpa.repository.JpaRepository;

import com.odiga.fiesta.festival.domain.FestivalModificationRequest;

public interface FestivalModificationRequestRepository extends JpaRepository<FestivalModificationRequest, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.odiga.fiesta.festival.domain.Festival;
import com.odiga.fiesta.festival.domain.FestivalCategory;
import com.odiga.fiesta.festival.domain.FestivalImage;
import com.odiga.fiesta.festival.domain.FestivalModificationRequest;
import com.odiga.fiesta.festival.domain.FestivalMood;
import com.odiga.fiesta.festival.dto.projection.FestivalData;
import com.odiga.fiesta.festival.dto.projection.FestivalDetailData;
Expand All @@ -42,6 +43,7 @@
import com.odiga.fiesta.festival.dto.request.FestivalCreateRequest;
import com.odiga.fiesta.festival.dto.request.FestivalFilterCondition;
import com.odiga.fiesta.festival.dto.request.FestivalFilterRequest;
import com.odiga.fiesta.festival.dto.request.CreateFestivalModificationRequest;
import com.odiga.fiesta.festival.dto.response.CategoryResponse;
import com.odiga.fiesta.festival.dto.response.DailyFestivalContents;
import com.odiga.fiesta.festival.dto.response.FestivalAndLocation;
Expand All @@ -50,11 +52,13 @@
import com.odiga.fiesta.festival.dto.response.FestivalImageResponse;
import com.odiga.fiesta.festival.dto.response.FestivalInfo;
import com.odiga.fiesta.festival.dto.response.FestivalInfoWithBookmark;
import com.odiga.fiesta.festival.dto.response.FestivalModificationResponse;
import com.odiga.fiesta.festival.dto.response.FestivalMonthlyResponse;
import com.odiga.fiesta.festival.dto.response.MoodResponse;
import com.odiga.fiesta.festival.dto.response.RecommendFestivalResponse;
import com.odiga.fiesta.festival.repository.FestivalCategoryRepository;
import com.odiga.fiesta.festival.repository.FestivalImageRepository;
import com.odiga.fiesta.festival.repository.FestivalModificationRequestRepository;
import com.odiga.fiesta.festival.repository.FestivalMoodRepository;
import com.odiga.fiesta.festival.repository.FestivalRepository;
import com.odiga.fiesta.festival.repository.FestivalUserTypeRepository;
Expand Down Expand Up @@ -90,6 +94,7 @@ public class FestivalService {
private final FestivalImageRepository festivalImageRepository;
private final FestivalCategoryRepository festivalCategoryRepository;
private final FestivalMoodRepository festivalMoodRepository;
private final FestivalModificationRequestRepository festivalModificationRequestRepository;

private final UserTypeService userTypeService;

Expand Down Expand Up @@ -316,6 +321,29 @@ public RecommendFestivalResponse getRecommendFestivals(User user, Long size) {

}

@Transactional
public FestivalModificationResponse createFestivalRequest(User user, Long festivalId,
CreateFestivalModificationRequest request) {
validateUserId(user.getId());
validateFestival(festivalId);

FestivalModificationRequest festivalModificationRequest = FestivalModificationRequest.builder()
.festivalId(festivalId)
.userId(user.getId())
.content(request.getContent())
.isPending(true)
.build();

festivalModificationRequestRepository.save(festivalModificationRequest);

return FestivalModificationResponse.builder()
.festivalId(festivalId)
.requestId(festivalModificationRequest.getId())
.isPending(true)
.createdAt(festivalModificationRequest.getCreatedAt())
.build();
}

// 필터링을 위해, request list 내부의 중복 제거
private FestivalFilterCondition getFestivalFilterCondition(FestivalFilterRequest festivalFilterRequest) {
Optional.ofNullable(festivalFilterRequest.getMonths())
Expand Down Expand Up @@ -437,7 +465,6 @@ private void validateFestival(Long festivalId) {
.orElseThrow(() -> new CustomException(FESTIVAL_NOT_FOUND));

if (festival.isPending()) {
// TODO 이후에 권한으로 처리
throw new CustomException(FESTIVAL_IS_PENDING);
}
}
Expand All @@ -447,7 +474,9 @@ private void validateUserId(Long userId) {
throw new CustomException(UNAUTHENTICATED_USER);
}

userRepository.findById(userId).orElseThrow(() -> new CustomException(USER_NOT_FOUND));
if (!userRepository.existsById(userId)) {
throw new CustomException(USER_NOT_FOUND);
}
}

private void validateFileCount(List<MultipartFile> files) {
Expand All @@ -461,4 +490,5 @@ private void validateFileExtension(List<MultipartFile> files) {
files.forEach(fileUtils::validateImageExtension);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
CREATE TABLE festival_modification_request
(
festival_modification_id BIGINT NOT NULL PRIMARY KEY,
festival_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content VARCHAR(500) NOT NULL,
is_pending BIT(1) DEFAULT 1 NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
Expand All @@ -16,9 +17,14 @@
import org.springframework.http.MediaType;
import org.springframework.security.core.context.SecurityContextHolder;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.odiga.fiesta.ControllerTestSupport;
import com.odiga.fiesta.auth.domain.AuthUser;
import com.odiga.fiesta.festival.domain.Festival;
import com.odiga.fiesta.festival.dto.request.CreateFestivalModificationRequest;
import com.odiga.fiesta.festival.dto.response.FestivalInfoWithBookmark;
import com.odiga.fiesta.festival.dto.response.FestivalMonthlyResponse;
import com.odiga.fiesta.festival.repository.FestivalRepository;
import com.odiga.fiesta.festival.service.FestivalService;

class FestivalControllerTest extends ControllerTestSupport {
Expand Down Expand Up @@ -127,7 +133,7 @@ void getFestivalsByQuery_QueryIsBlank() throws Exception {

@DisplayName("다가오는 페스티벌 조회 -로그인하지 않는 경우 에러가 발생한다.")
@Test
void test() throws Exception {
void getUpcomingFestival_NotLoggedIn() throws Exception {
// given
SecurityContextHolder.clearContext();

Expand All @@ -138,4 +144,37 @@ void test() throws Exception {
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.message").value(NOT_LOGGED_IN.getMessage()));
}

@DisplayName("페스티벌 수정 사항 요청 - 내용이 비어있으면 에러가 발생한다.")
@Test
void createFestivalRequest_EmptyContent() throws Exception {
// given
CreateFestivalModificationRequest request = CreateFestivalModificationRequest.builder()
.content("")
.build();

// when // then
mockMvc.perform(post("/api/v1/festivals/{festivalId}/request", 1L)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isBadRequest());
}

@DisplayName("페스티벌 수정 사항 요청 - 로그인 하지 않는 경우 에러가 발생한다.")
@Test
void createFestivalRequest_NotLoggedIn() throws Exception {
// given
SecurityContextHolder.clearContext();

CreateFestivalModificationRequest request = CreateFestivalModificationRequest.builder()
.content("리뷰 내용")
.build();

// when // then
mockMvc.perform(post("/api/v1/festivals/{festivalId}/request", 1L)
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isUnauthorized())
.andExpect(jsonPath("$.message").value(NOT_LOGGED_IN.getMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import com.odiga.fiesta.festival.dto.response.FestivalBasic;
import com.odiga.fiesta.festival.repository.FestivalCategoryRepository;
import com.odiga.fiesta.festival.repository.FestivalImageRepository;
import com.odiga.fiesta.festival.repository.FestivalModificationRequestRepository;
import com.odiga.fiesta.festival.repository.FestivalMoodRepository;
import com.odiga.fiesta.festival.repository.FestivalRepository;
import com.odiga.fiesta.festival.repository.FestivalUserTypeRepository;
Expand Down Expand Up @@ -68,6 +69,9 @@ class FestivalServiceMockTest extends MockTestSupport {
@Mock
private FestivalMoodRepository festivalMoodRepository;

@Mock
private FestivalModificationRequestRepository festivalModificationRequestRepository;

@Mock
private FileUtils fileUtils;

Expand Down Expand Up @@ -209,7 +213,7 @@ class FestivalCreationTest {
@Test
void createFestival_Success() {
// given
given(userRepository.findById(1L)).willReturn(Optional.of(user));
given(userRepository.existsById(1L)).willReturn(true);
given(userTypeService.getTopNUserTypes(List.of(1L, 2L), List.of(1L, 2L), 2))
.willReturn(List.of(userType1, userType2));
given(festivalRepository.save(any())).willReturn(festival);
Expand All @@ -225,7 +229,7 @@ void createFestival_Success() {
@Test
void createFestival_SuccessWithoutImages() {
// given
given(userRepository.findById(1L)).willReturn(Optional.of(user));
given(userRepository.existsById(1L)).willReturn(true);
given(userTypeService.getTopNUserTypes(List.of(1L, 2L), List.of(1L, 2L), 2))
.willReturn(List.of(userType1, userType2));
given(festivalRepository.save(any())).willReturn(festival);
Expand Down Expand Up @@ -261,7 +265,7 @@ void createFestival_RemoveDuplicatedKeywords() {
.tip("팁")
.build();

given(userRepository.findById(1L)).willReturn(Optional.of(user));
given(userRepository.existsById(1L)).willReturn(true);
given(userTypeService.getTopNUserTypes(List.of(1L, 2L), List.of(1L, 2L), 2))
.willReturn(List.of(userType1, userType2));
given(festivalRepository.save(any())).willReturn(festival);
Expand Down Expand Up @@ -320,7 +324,7 @@ void createFestival_ImageCountExceeded() {
"test4".getBytes())
);

given(userRepository.findById(1L)).willReturn(Optional.of(user));
given(userRepository.existsById(1L)).willReturn(true);

// when // then
CustomException exception = assertThrows(CustomException.class, () -> {
Expand Down
Loading

0 comments on commit d229461

Please sign in to comment.