Skip to content

Commit

Permalink
Merge pull request #436 from Zelusik/feature/#419-report-place-info
Browse files Browse the repository at this point in the history
장소 정보 수정 제안하기(신고) API 구현
  • Loading branch information
jiholee0 authored Feb 26, 2024
2 parents 20e6169 + d8080d9 commit 005cbca
Show file tree
Hide file tree
Showing 14 changed files with 771 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.zelusik.eatery.domain.report_place.api;

import com.zelusik.eatery.domain.report_place.dto.ReportPlaceDto;
import com.zelusik.eatery.domain.report_place.dto.request.ReportPlaceRequest;
import com.zelusik.eatery.domain.report_place.dto.response.ReportPlaceResponse;
import com.zelusik.eatery.domain.report_place.service.ReportPlaceCommandService;
import com.zelusik.eatery.domain.report_place.service.ReportPlaceQueryService;
import com.zelusik.eatery.global.auth.UserPrincipal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.net.URI;

import static com.zelusik.eatery.global.common.constant.EateryConstants.API_MINOR_VERSION_HEADER_NAME;

@Tag(name = "장소 신고 관련 API")
@RequiredArgsConstructor
@RequestMapping("/api/v1/reports/places")
@RestController
public class ReportPlaceControllerV1 {

private final ReportPlaceCommandService reportPlaceCommandService;
private final ReportPlaceQueryService reportPlaceQueryService;

@Operation(summary = "장소 신고(정보 수정 제안)",
description = "<p><strong>Latest version: v1.1</strong>" +
"<p>특정 장소를 신고합니다.</p>" +
"<ul><li><code>placeId</code> : 신고하는 장소 id</li>" +
"<li><code>reasonOption</code> : 신고하는 이유 선택(POSITION, TIME, CLOSED_DAYS, NUMBER, SNS, ETC 중 택 1) </li>" +
"<li><code>reasonDetail</code> : 신고하는 상세 이유 </li></ul>",
security = @SecurityRequirement(name = "access-token")
)
@PostMapping(headers = API_MINOR_VERSION_HEADER_NAME + "=1")
public ResponseEntity<ReportPlaceResponse> reportPlaceV1(
@AuthenticationPrincipal UserPrincipal userPrincipal,
@RequestBody @Valid ReportPlaceRequest reportPlaceRequest) {
ReportPlaceDto reportPlaceDto = reportPlaceCommandService.reportPlace(userPrincipal.getMemberId(), reportPlaceRequest);
return ResponseEntity
.created(URI.create("/api/v1/reports/places/" + reportPlaceDto.getId()))
.body(ReportPlaceResponse.from(reportPlaceDto));
}

@Operation(summary = "장소 신고 내역(정보 수정 제안) 단건 조회",
description = "<p><strong>Latest version: v1.1</strong>" +
"<p>전달받은 <code>reportPlaceId</code>에 해당하는 장소 신고 내역을 조회합니다.",
security = @SecurityRequirement(name = "access-token")
)
@GetMapping(value = "/{reportPlaceId}", headers = API_MINOR_VERSION_HEADER_NAME + "=1")
public ReportPlaceResponse findReportPlaceByIdV1(
@Parameter(
description = "PK of reportPlace",
example = "3"
) @PathVariable Long reportPlaceId
) {
ReportPlaceDto reportPlaceDto = reportPlaceQueryService.getDtoById(reportPlaceId);
return ReportPlaceResponse.from(reportPlaceDto);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.zelusik.eatery.domain.report_place.dto;

import com.zelusik.eatery.domain.place.dto.PlaceDto;
import com.zelusik.eatery.domain.report_place.entity.ReportPlace;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class ReportPlaceDto {

private Long id;
private Long reporterId;
private PlaceDto place;
private ReportPlaceReasonOption reasonOption;
private String reasonDetail;

public static ReportPlaceDto from(ReportPlace entity) {
return new ReportPlaceDto(
entity.getId(),
entity.getReporter().getId(),
PlaceDto.from(entity.getPlace()),
entity.getReasonOption(),
entity.getReasonDetail()
);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.zelusik.eatery.domain.report_place.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum ReportPlaceReasonOption {
POSITION("음식점의 위치"),
TIME("운영 시간"),
CLOSED_DAYS("휴무일 정보"),
NUMBER("전화번호"),
SNS("sns 정보"),
ETC("기타");

private final String fullSentence;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.zelusik.eatery.domain.report_place.dto.request;

import com.zelusik.eatery.domain.report_place.dto.ReportPlaceReasonOption;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class ReportPlaceRequest {
@Schema(description = "신고하고자 하는 장소의 id(PK)", example = "2")
@NotNull
private Long placeId;

@Schema(description = "신고 이유 선택(POSITION, TIME, CLOSED_DAYS, NUMBER, SNS, ETC 중 택 1)", example = "NUMBER")
@NotNull
private ReportPlaceReasonOption reasonOption;

@Schema(description = "신고 이유 상세", example = "전화번호가 ~~로 변경되었습니다.")
@NotNull
private String reasonDetail;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.zelusik.eatery.domain.report_place.dto.response;

import com.zelusik.eatery.domain.report_place.dto.ReportPlaceDto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class ReportPlaceResponse {

@Schema(description = "장소 신고의 id(PK)", example = "19")
private Long id;

@Schema(description = "장소를 신고한 회원의 id(PK)", example = "15")
private Long reporterId;

@Schema(description = "신고한 장소의 id(PK)", example = "2")
private Long placeId;

@Schema(description = "신고 이유 옵션 전체 문장", example = "전화번호")
private String reasonOption;

@Schema(description = "신고 이유 상세", example = "전화번호가 ~~로 변경되었습니다.")
private String reasonDetail;

public static ReportPlaceResponse from(ReportPlaceDto reportPlaceDto) {
return new ReportPlaceResponse(
reportPlaceDto.getId(),
reportPlaceDto.getReporterId(),
reportPlaceDto.getPlace().getId(),
reportPlaceDto.getReasonOption().getFullSentence(),
reportPlaceDto.getReasonDetail()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.zelusik.eatery.domain.report_place.entity;

import com.zelusik.eatery.domain.member.entity.Member;
import com.zelusik.eatery.domain.place.entity.Place;
import com.zelusik.eatery.domain.report_place.dto.ReportPlaceReasonOption;
import com.zelusik.eatery.global.common.entity.BaseTimeEntity;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class ReportPlace extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "report_place_id")
private Long id;

@JoinColumn(name = "reporter_id")
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
private Member reporter;

@JoinColumn(name = "place_id")
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
private Place place;

@NotNull
@Enumerated(EnumType.STRING)
private ReportPlaceReasonOption reasonOption;

@NotNull
private String reasonDetail;

public ReportPlace(Long id, Member reporter, Place place, ReportPlaceReasonOption reasonOption, String reasonDetail, LocalDateTime createdAt, LocalDateTime updatedAt) {
super(createdAt, updatedAt);
this.id = id;
this.reporter = reporter;
this.place = place;
this.reasonOption = reasonOption;
this.reasonDetail = reasonDetail;
}

public static ReportPlace create(Member reporter, Place place, ReportPlaceReasonOption reasonOption, String reasonDetail) {
return create(null, reporter, place, reasonOption, reasonDetail, null, null);
}

public static ReportPlace create(Long id, Member reporter, Place place, ReportPlaceReasonOption reasonOption, String reasonDetail, LocalDateTime createdAt, LocalDateTime updatedAt) {
return new ReportPlace(id, reporter, place, reasonOption, reasonDetail, createdAt, updatedAt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.zelusik.eatery.domain.report_place.exception;

import com.zelusik.eatery.global.common.exception.NotFoundException;
import com.zelusik.eatery.global.exception.constant.CustomExceptionType;

public class ReportPlaceNotFoundByIdException extends NotFoundException {

public ReportPlaceNotFoundByIdException(Long id) {
super(CustomExceptionType.REPORT_PLACE_NOT_FOUND, "PK = " + id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.zelusik.eatery.domain.report_place.repository;

import com.zelusik.eatery.domain.report_place.entity.ReportPlace;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ReportPlaceRepository extends JpaRepository<ReportPlace, Long> {

@EntityGraph(attributePaths = {"reporter", "place"})
@Override
Optional<ReportPlace> findById(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.zelusik.eatery.domain.report_place.service;

import com.zelusik.eatery.domain.member.entity.Member;
import com.zelusik.eatery.domain.member.service.MemberQueryService;
import com.zelusik.eatery.domain.place.entity.Place;
import com.zelusik.eatery.domain.place.service.PlaceQueryService;
import com.zelusik.eatery.domain.report_place.dto.ReportPlaceDto;
import com.zelusik.eatery.domain.report_place.dto.request.ReportPlaceRequest;
import com.zelusik.eatery.domain.report_place.entity.ReportPlace;
import com.zelusik.eatery.domain.report_place.repository.ReportPlaceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional
@Service
public class ReportPlaceCommandService {
private final MemberQueryService memberQueryService;
private final PlaceQueryService placeQueryService;
private final ReportPlaceRepository reportPlaceRepository;

/**
* 장소를 신고한다.
*
* @param reporterId login member Id
* @param body 장소 신고 요청 request 객체
* @return 장소 신고 dto
*/
public ReportPlaceDto reportPlace(Long reporterId, ReportPlaceRequest body) {
Member reporter = memberQueryService.getById(reporterId);
Place place = placeQueryService.getById(body.getPlaceId());

ReportPlace newReportPlace = ReportPlace.create(reporter, place, body.getReasonOption(), body.getReasonDetail());
ReportPlace reportPlace = reportPlaceRepository.save(newReportPlace);
return ReportPlaceDto.from(reportPlace);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.zelusik.eatery.domain.report_place.service;

import com.zelusik.eatery.domain.report_place.dto.ReportPlaceDto;
import com.zelusik.eatery.domain.report_place.entity.ReportPlace;
import com.zelusik.eatery.domain.report_place.exception.ReportPlaceNotFoundByIdException;
import com.zelusik.eatery.domain.report_place.repository.ReportPlaceRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Transactional(readOnly = true)
@Service
public class ReportPlaceQueryService {

private final ReportPlaceRepository reportPlaceRepository;

/**
* reportPlaceId에 해당하는 장소 신고 내역을 조회한다.
*
* @param id 조회하고자 하는 장소 신고 내역의 PK
* @return 조회된 장소 신고 내역의 dto
*/
public ReportPlaceDto getDtoById(Long id) {
ReportPlace reportPlace = reportPlaceRepository.findById(id)
.orElseThrow(() -> new ReportPlaceNotFoundByIdException(id));
return ReportPlaceDto.from(reportPlace);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import com.zelusik.eatery.domain.bookmark.entity.Bookmark;
import com.zelusik.eatery.domain.member.entity.Member;
import com.zelusik.eatery.domain.place.entity.Place;
import com.zelusik.eatery.domain.review.entity.Review;
import com.zelusik.eatery.domain.report_place.entity.ReportPlace;
import com.zelusik.eatery.domain.report_review.entity.ReportReview;
import com.zelusik.eatery.domain.review.entity.Review;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -20,6 +21,8 @@
* <li>2XXX: 회원({@link Member}) 관련 예외</li>
* <li>3000 ~ 3499: 장소 관련 예외</li>
* <li>3500 ~ 3999: 리뷰 관련 예외</li>
* <li>4000 ~ 4099: 리뷰 신고 관련 예외</li>
* <li>4100 ~ 4199: 장소 신고 관련 예외</li>
* <li>4300 ~ 4599: 북마크 관련 예외</li>
* <li>1XXXX: Kakao server 관련 예외</li>
* <li>2XXXX: Apple server/login 관련 예외</li>
Expand Down Expand Up @@ -85,6 +88,11 @@ public enum CustomExceptionType {
*/
REPORT_REVIEW_NOT_FOUND(4000, "리뷰 신고 내역을 찾을 수 없습니다."),

/**
* 장소 신고{@link ReportPlace}) 관련 예외
*/
REPORT_PLACE_NOT_FOUND(4100, "장소 신고 내역을 찾을 수 없습니다."),

/**
* 북마크({@link Bookmark} 관련 예외
*/
Expand Down
Loading

0 comments on commit 005cbca

Please sign in to comment.