From 308f28703657b727605c465d9f039b0d5e7b5619 Mon Sep 17 00:00:00 2001 From: Yeongjin Noh <129354455+nohy6630@users.noreply.github.com> Date: Fri, 17 Nov 2023 19:48:04 +0900 Subject: [PATCH 01/12] =?UTF-8?q?Feat:=20=EC=9E=AC=EB=82=9C=EC=83=81?= =?UTF-8?q?=ED=99=A9=20API=20=EA=B5=AC=ED=98=84=20(#72)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat(#55): 커스텀 예외 추가 * Feat(#55): 재난상황 커뮤니티 API 구현 * Feat(#55): 홈 api에서 상위 인기순 대화 3개 가져오도록 구현 * Feat(#55): 인기순, 최신순 정렬로 재난상황 대화 가져오기 구현 --- .../controller/ConversationController.java | 74 ++++++++++ .../CreateChildConversationRequest.java | 15 ++ .../request/CreateConversationRequest.java | 20 +++ .../dto/response/GetConversationResponse.java | 60 ++++++++ .../conversation/entity/Conversation.java | 89 ++++++++++++ .../repository/ConversationRepository.java | 24 ++++ .../service/ConversationService.java | 132 ++++++++++++++++++ .../controller/DisasterController.java | 21 +++ .../dto/response/SituationDetailResponse.java | 20 +++ .../dto/response/SituationHomeResponse.java | 21 +++ .../dto/response/SituationResponse.java | 58 ++++++++ .../domain/disaster/entity/Disaster.java | 5 + .../repository/DisasterRepository.java | 2 +- .../disaster/service/DisasterService.java | 74 +++++++++- .../domain/like/entity/ConversationLike.java | 39 ++++++ .../ConversationLikeRepository.java | 15 ++ .../backend/domain/member/entity/Member.java | 4 + .../BadRequestConversationSortException.java | 11 ++ .../context/CustomExceptionContext.java | 5 + .../NotFoundConversationException.java | 12 ++ .../notfound/NotFoundDisasterException.java | 11 ++ 21 files changed, 710 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/numberone/backend/domain/conversation/controller/ConversationController.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateChildConversationRequest.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateConversationRequest.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/dto/response/GetConversationResponse.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/entity/Conversation.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/repository/ConversationRepository.java create mode 100644 src/main/java/com/numberone/backend/domain/conversation/service/ConversationService.java create mode 100644 src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationDetailResponse.java create mode 100644 src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationHomeResponse.java create mode 100644 src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationResponse.java create mode 100644 src/main/java/com/numberone/backend/domain/like/entity/ConversationLike.java create mode 100644 src/main/java/com/numberone/backend/domain/like/repository/ConversationLikeRepository.java create mode 100644 src/main/java/com/numberone/backend/exception/badrequest/BadRequestConversationSortException.java create mode 100644 src/main/java/com/numberone/backend/exception/notfound/NotFoundConversationException.java create mode 100644 src/main/java/com/numberone/backend/exception/notfound/NotFoundDisasterException.java diff --git a/src/main/java/com/numberone/backend/domain/conversation/controller/ConversationController.java b/src/main/java/com/numberone/backend/domain/conversation/controller/ConversationController.java new file mode 100644 index 00000000..3ae92f17 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/controller/ConversationController.java @@ -0,0 +1,74 @@ +package com.numberone.backend.domain.conversation.controller; + +import com.numberone.backend.domain.conversation.dto.request.CreateChildConversationRequest; +import com.numberone.backend.domain.conversation.dto.request.CreateConversationRequest; +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import com.numberone.backend.domain.conversation.service.ConversationService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "conversations", description = "대화(재난상황 댓글) 관련 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/conversations") +public class ConversationController { + private final ConversationService conversationService; + + @Operation(summary = "(대댓글x) 대화 생성하기", description = """ + 대화 내용을 body에 담아 전달해주세요. + + 어떤 재난상황에 대한 대화인지 재난상황 id를 body에 같이 담아 전달해주세요. + """) + @PostMapping() + public void createConversation(Authentication authentication, @RequestBody @Valid CreateConversationRequest createConversationRequest) { + conversationService.createConversation(authentication.getName(), createConversationRequest); + } + + @Operation(summary = "대댓글 대화 생성하기", description = """ + 대댓글 대화 내용을 body에 담아 전달해주세요. + + 어떤 대화(댓글)의 대댓글인지 대화 id를 파라미터로 전달해주세요. + """) + @PostMapping("/{conversationId}") + public void createChildConversation(Authentication authentication, @RequestBody @Valid CreateChildConversationRequest createConversationRequest, @PathVariable Long conversationId){ + conversationService.createChildConversation(authentication.getName(), createConversationRequest, conversationId); + } + + @Operation(summary = "(대댓글도 포함)대화 삭제하기", description = """ + 삭제할 대화 id를 파라미터로 전달해주세요. + """) + @DeleteMapping("/{conversationId}") + public void delete(@PathVariable Long conversationId) { + conversationService.delete(conversationId); + } + +// 만들었는데 필요없는 api인것 같아서 일단 주석처리 +// @Operation(summary = "대화 조회하기", description = """ +// 조회할 대화 id를 파라미터로 전달해주세요. +// """) +// @GetMapping("/{conversationId}") +// public ResponseEntity get(Authentication authentication, @PathVariable Long conversationId) { +// return ResponseEntity.ok(conversationService.get(authentication.getName(), conversationId)); +// } + + @Operation(summary = "대화 좋아요 등록하기", description = """ + 사용자가 대화의 좋아요를 등록할 때 대화 id를 파라미터로 전달해주세요. + """) + @PostMapping("/like/{conversationId}") + public void increaseLike(Authentication authentication, @PathVariable Long conversationId) { + conversationService.increaseLike(authentication.getName(), conversationId); + } + + @Operation(summary = "대화 좋아요 취소하기", description = """ + 사용자가 대화의 좋아요를 취소할 때 대화 id를 파라미터로 전달해주세요. + """) + @DeleteMapping("/like/{conversationId}") + public void decreaseLike(Authentication authentication, @PathVariable Long conversationId) { + conversationService.decreaseLike(authentication.getName(), conversationId); + } +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateChildConversationRequest.java b/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateChildConversationRequest.java new file mode 100644 index 00000000..0ab1177f --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateChildConversationRequest.java @@ -0,0 +1,15 @@ +package com.numberone.backend.domain.conversation.dto.request; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CreateChildConversationRequest { + @NotNull(message = "댓글 내용은 null일 수 없습니다.") + @Schema(defaultValue = "강남역 잠겼나요?") + private String content; +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateConversationRequest.java b/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateConversationRequest.java new file mode 100644 index 00000000..9f07e657 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/dto/request/CreateConversationRequest.java @@ -0,0 +1,20 @@ +package com.numberone.backend.domain.conversation.dto.request; + + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class CreateConversationRequest { + @NotNull(message = "댓글 내용은 null일 수 없습니다.") + @Schema(defaultValue = "강남역 잠겼나요?") + private String content; + + @NotNull(message = "재난 상황 id는 null일 수 없습니다.") + @Schema(defaultValue = "2") + private Long disasterId; +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/dto/response/GetConversationResponse.java b/src/main/java/com/numberone/backend/domain/conversation/dto/response/GetConversationResponse.java new file mode 100644 index 00000000..2ca2f387 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/dto/response/GetConversationResponse.java @@ -0,0 +1,60 @@ +package com.numberone.backend.domain.conversation.dto.response; + +import com.numberone.backend.domain.conversation.entity.Conversation; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class GetConversationResponse { + @Schema(defaultValue = "67") + private Long conversationId; + + @Schema(defaultValue = "생존전문가 ・ 5분전") + private String info; + + @Schema(defaultValue = "강남역 잠겼나요?") + private String content; + + @Schema(defaultValue = "2") + private Long like; + + @Schema(defaultValue = "true") + private Boolean isLiked; + + @Schema(defaultValue = "true") + private Boolean isEditable; + + private List childs; + + private static String makeInfo(String nickname, LocalDateTime createdAt) { + String info = nickname + " ・ "; + Duration duration = Duration.between(createdAt, LocalDateTime.now()); + if (duration.toSeconds() < 60) + info += duration.toSeconds() + "초전"; + else if (duration.toMinutes() < 60) + info += duration.toMinutes() + "분전"; + else + info += duration.toHours() + "시간전"; + return info; + } + + public static GetConversationResponse of(Conversation conversation, Boolean isLiked, Boolean isEditable, List childs) { + return GetConversationResponse.builder() + .conversationId(conversation.getId()) + .like(conversation.getLikeCnt()) + .info(makeInfo(conversation.getMember().getNickName(), conversation.getCreatedAt())) + .content(conversation.getContent()) + .isLiked(isLiked) + .isEditable(isEditable) + .childs(childs) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/entity/Conversation.java b/src/main/java/com/numberone/backend/domain/conversation/entity/Conversation.java new file mode 100644 index 00000000..df521199 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/entity/Conversation.java @@ -0,0 +1,89 @@ +package com.numberone.backend.domain.conversation.entity; + +import com.numberone.backend.config.basetime.BaseTimeEntity; +import com.numberone.backend.domain.disaster.entity.Disaster; +import com.numberone.backend.domain.like.entity.ConversationLike; +import com.numberone.backend.domain.member.entity.Member; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Conversation extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Comment("내용") + private String content; + + @Comment("댓글 depth {0: 댓글, 1: 대댓글}") + private Integer depth; + + @Comment("좋아요 갯수") + private Long likeCnt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "disaster_id") + private Disaster disaster; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "conversation_id") + private Conversation parent; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) + private List conversations = new ArrayList<>(); + + @OneToMany(mappedBy = "conversation", cascade = CascadeType.ALL) + private List likes = new ArrayList<>(); + + @Builder + public Conversation(String content, Long likeCnt, Integer depth, Member member, Disaster disaster, Conversation parent) { + this.content = content; + this.depth = depth; + this.member = member; + this.disaster = disaster; + this.parent = parent; + this.likeCnt = likeCnt; + } + + public static Conversation of(String content, Member member, Disaster disaster) { + return Conversation.builder() + .content(content) + .member(member) + .disaster(disaster) + .depth(0) + .likeCnt(0L) + .build(); + } + + public static Conversation childOf(String content, Member member, Conversation parent) { + return Conversation.builder() + .content(content) + .member(member) + .parent(parent) + .depth(1) + .likeCnt(0L) + .build(); + } + + public void increaseLike() { + likeCnt++; + } + + public void decreaseLike() { + likeCnt--; + } +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/repository/ConversationRepository.java b/src/main/java/com/numberone/backend/domain/conversation/repository/ConversationRepository.java new file mode 100644 index 00000000..a2b48fa6 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/repository/ConversationRepository.java @@ -0,0 +1,24 @@ +package com.numberone.backend.domain.conversation.repository; + + +import com.numberone.backend.domain.conversation.entity.Conversation; +import com.numberone.backend.domain.disaster.entity.Disaster; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ConversationRepository extends JpaRepository { + + long countByDisaster(Disaster disaster); + + long countByParent(Conversation parent); + + List findAllByDisasterOrderByLikeCntDesc(Disaster disaster, Pageable pageable); + + List findAllByDisasterOrderByLikeCntDesc(Disaster disaster); + + List findAllByDisasterOrderByCreatedAtDesc(Disaster disaster); + + List findAllByParentOrderByLikeCntDesc(Conversation parent); +} diff --git a/src/main/java/com/numberone/backend/domain/conversation/service/ConversationService.java b/src/main/java/com/numberone/backend/domain/conversation/service/ConversationService.java new file mode 100644 index 00000000..d59035b9 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/conversation/service/ConversationService.java @@ -0,0 +1,132 @@ +package com.numberone.backend.domain.conversation.service; + +import com.numberone.backend.domain.conversation.dto.request.CreateChildConversationRequest; +import com.numberone.backend.domain.conversation.dto.request.CreateConversationRequest; +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import com.numberone.backend.domain.conversation.entity.Conversation; +import com.numberone.backend.domain.conversation.repository.ConversationRepository; +import com.numberone.backend.domain.disaster.entity.Disaster; +import com.numberone.backend.domain.disaster.repository.DisasterRepository; +import com.numberone.backend.domain.like.entity.ConversationLike; +import com.numberone.backend.domain.like.repository.ConversationLikeRepository; +import com.numberone.backend.domain.member.entity.Member; +import com.numberone.backend.domain.member.service.MemberService; +import com.numberone.backend.exception.conflict.AlreadyLikedException; +import com.numberone.backend.exception.conflict.AlreadyUnLikedException; +import com.numberone.backend.exception.notfound.NotFoundConversationException; +import com.numberone.backend.exception.notfound.NotFoundDisasterException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class ConversationService { + private final ConversationRepository conversationRepository; + private final MemberService memberService; + private final DisasterRepository disasterRepository; + private final ConversationLikeRepository conversationLikeRepository; + + @Transactional + public void createConversation(String email, CreateConversationRequest createConversationRequest) { + Member member = memberService.findByEmail(email); + Disaster disaster = disasterRepository.findById(createConversationRequest.getDisasterId()) + .orElseThrow(NotFoundDisasterException::new); + Conversation conversation = Conversation.of( + createConversationRequest.getContent(), + member, + disaster + ); + conversationRepository.save(conversation); + } + + @Transactional + public void createChildConversation(String email, CreateChildConversationRequest createConversationRequest, Long conversationId) { + Member member = memberService.findByEmail(email); + Conversation parent = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + Conversation conversation = Conversation.childOf( + createConversationRequest.getContent(), + member, + parent + ); + conversationRepository.save(conversation); + } + + @Transactional + public void delete(Long conversationId) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + conversationRepository.delete(conversation); + } + + private boolean checkLike(Member member, Conversation conversation) { + for (ConversationLike conversationLike : conversation.getLikes()) { + if (conversationLike.getMember().equals(member)) { + return true; + } + } + return false; + } + + public GetConversationResponse get(String email, Long conversationId) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + Member member = memberService.findByEmail(email); + List childs = new ArrayList<>(); + List childConversations = conversationRepository.findAllByParentOrderByLikeCntDesc(conversation); + for (Conversation child : childConversations) { + childs.add(GetConversationResponse.of( + child, + checkLike(member, child), + member.equals(child.getMember()), + new ArrayList<>() + )); + } + return GetConversationResponse.of( + conversation, + checkLike(member, conversation), + member.equals(conversation.getMember()), + childs); + } + + public GetConversationResponse getExceptChild(String email, Long conversationId) { + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + Member member = memberService.findByEmail(email); + return GetConversationResponse.of( + conversation, + checkLike(member, conversation), + member.equals(conversation.getMember()), + new ArrayList<>()); + } + + @Transactional + public void increaseLike(String email, Long conversationId) { + Member member = memberService.findByEmail(email); + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + conversationLikeRepository.findByMemberAndConversation(member, conversation) + .ifPresent((o) -> { + throw new AlreadyLikedException(); + }); + ConversationLike conversationLike = ConversationLike.of(member, conversation); + conversationLikeRepository.save(conversationLike); + conversation.increaseLike(); + } + + @Transactional + public void decreaseLike(String email, Long conversationId) { + Member member = memberService.findByEmail(email); + Conversation conversation = conversationRepository.findById(conversationId) + .orElseThrow(NotFoundConversationException::new); + ConversationLike conversationLike = conversationLikeRepository.findByMemberAndConversation(member, conversation) + .orElseThrow(AlreadyUnLikedException::new); + conversationLikeRepository.delete(conversationLike); + conversation.decreaseLike(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/disaster/controller/DisasterController.java b/src/main/java/com/numberone/backend/domain/disaster/controller/DisasterController.java index a83e132b..f3e18890 100644 --- a/src/main/java/com/numberone/backend/domain/disaster/controller/DisasterController.java +++ b/src/main/java/com/numberone/backend/domain/disaster/controller/DisasterController.java @@ -2,12 +2,15 @@ import com.numberone.backend.domain.disaster.dto.request.LatestDisasterRequest; import com.numberone.backend.domain.disaster.dto.response.LatestDisasterResponse; +import com.numberone.backend.domain.disaster.dto.response.SituationDetailResponse; +import com.numberone.backend.domain.disaster.dto.response.SituationHomeResponse; import com.numberone.backend.domain.disaster.service.DisasterService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; @Tag(name = "disasters", description = "재난문자 관련 API") @@ -29,4 +32,22 @@ public class DisasterController { public ResponseEntity getLatestDisaster(@Valid @RequestBody LatestDisasterRequest latestDisasterRequest) { return ResponseEntity.ok(disasterService.getLatestDisaster(latestDisasterRequest)); } + + @Operation(summary = "재난상황 커뮤니티 데이터 가져오기", description = """ + 재난상황 페이지에서 필요한 재난목록과 그와 관련된 대화(댓글)들을 가져옵니다. + """) + @GetMapping("/situation") + public ResponseEntity getSituationHome(Authentication authentication){ + return ResponseEntity.ok(disasterService.getSituationHome(authentication.getName())); + } + + @Operation(summary = "해당 재난과 관련된 모든 커뮤니티 대화 가져오기", description = """ + 정렬기준(최신순: time, 인기순: popularity) 과 재난상황 id를 파라미터로 전달해주세요. + + 커뮤니티-재난상황-댓글더보기 페이지에서 사용하는 API입니다. + """) + @GetMapping("/{sort}/{disasterId}") + public ResponseEntity getSituationDetail(Authentication authentication, @PathVariable Long disasterId, @PathVariable String sort){ + return ResponseEntity.ok(disasterService.getSituationDetail(authentication.getName(), disasterId, sort)); + } } diff --git a/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationDetailResponse.java b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationDetailResponse.java new file mode 100644 index 00000000..862c04aa --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationDetailResponse.java @@ -0,0 +1,20 @@ +package com.numberone.backend.domain.disaster.dto.response; + +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import lombok.*; + +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class SituationDetailResponse { + private List conversations; + + public static SituationDetailResponse of(List conversations) { + return SituationDetailResponse.builder() + .conversations(conversations) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationHomeResponse.java b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationHomeResponse.java new file mode 100644 index 00000000..c66702cd --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationHomeResponse.java @@ -0,0 +1,21 @@ +package com.numberone.backend.domain.disaster.dto.response; + +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class SituationHomeResponse { + private List situations = new ArrayList<>(); + + public static SituationHomeResponse of(List situations) { + return SituationHomeResponse.builder() + .situations(situations) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationResponse.java b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationResponse.java new file mode 100644 index 00000000..bf792afd --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/disaster/dto/response/SituationResponse.java @@ -0,0 +1,58 @@ +package com.numberone.backend.domain.disaster.dto.response; + +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import com.numberone.backend.domain.disaster.entity.Disaster; +import com.numberone.backend.domain.disaster.util.DisasterType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.*; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Locale; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class SituationResponse { + @Schema(defaultValue = "32") + private Long disasterId; + + @Schema(defaultValue = "화재") + private String disasterType; + + @Schema(defaultValue = "서울특별시 강남구 동작동 화재 발생") + private String title; + + @Schema(defaultValue = "많은 비가 예상되므로 반지하주택, 지하주차장, 지하차도, 지하공간 등 침수가 우려되니 사전점검 등 산사태 위험지역 주민은 이상 징후 시 대피 바랍니다.") + private String msg; + + @Schema(defaultValue = "서울특별시 강남구 ・ 오후 2시 46분") + private String info; + + @Schema(defaultValue = "6") + private Long conversationCnt; + + private List conversations; + + + public static SituationResponse of(Disaster disaster, List conversations, Long conversationCnt) { + String category, time; + if (disaster.getDisasterType() == DisasterType.OTHERS) + category = "상황"; + else + category = disaster.getDisasterType().getDescription(); + time = disaster.getGeneratedAt().format(DateTimeFormatter.ofPattern("a h시 m분", Locale.KOREAN)); + return SituationResponse.builder() + .disasterId(disaster.getId()) + .disasterType(disaster.getDisasterType().getDescription()) + .title(disaster.getLocation() + " " + category + " 발생") + .msg(disaster.getMsg()) + .info(disaster.getLocation() + " ・ " + time) + .conversations(conversations) + .conversationCnt(conversationCnt) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/disaster/entity/Disaster.java b/src/main/java/com/numberone/backend/domain/disaster/entity/Disaster.java index a6c49c1f..80604acb 100644 --- a/src/main/java/com/numberone/backend/domain/disaster/entity/Disaster.java +++ b/src/main/java/com/numberone/backend/domain/disaster/entity/Disaster.java @@ -1,11 +1,13 @@ package com.numberone.backend.domain.disaster.entity; +import com.numberone.backend.domain.conversation.entity.Conversation; import com.numberone.backend.domain.disaster.util.DisasterType; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.Comment; import java.time.LocalDateTime; +import java.util.List; @Entity @Getter @@ -32,6 +34,9 @@ public class Disaster { @Comment("재난 발생 시각") private LocalDateTime generatedAt; + @OneToMany(mappedBy = "disaster", cascade = CascadeType.ALL) + private List conversations; + @Builder public Disaster(DisasterType disasterType, String location, String msg, Long disasterNum, LocalDateTime generatedAt) { this.disasterType = disasterType; diff --git a/src/main/java/com/numberone/backend/domain/disaster/repository/DisasterRepository.java b/src/main/java/com/numberone/backend/domain/disaster/repository/DisasterRepository.java index bf5d8a2e..8bcddecf 100644 --- a/src/main/java/com/numberone/backend/domain/disaster/repository/DisasterRepository.java +++ b/src/main/java/com/numberone/backend/domain/disaster/repository/DisasterRepository.java @@ -16,5 +16,5 @@ public interface DisasterRepository extends JpaRepository { "where :address like concat(d.location,'%') " + "and d.generatedAt > :time " + "order by d.generatedAt desc") - List findDisastersInAddressAfterTime(String address, LocalDateTime time, Pageable pageable); + List findDisastersInAddressAfterTime(String address, LocalDateTime time); } diff --git a/src/main/java/com/numberone/backend/domain/disaster/service/DisasterService.java b/src/main/java/com/numberone/backend/domain/disaster/service/DisasterService.java index 862b1241..a9ace5de 100644 --- a/src/main/java/com/numberone/backend/domain/disaster/service/DisasterService.java +++ b/src/main/java/com/numberone/backend/domain/disaster/service/DisasterService.java @@ -1,10 +1,24 @@ package com.numberone.backend.domain.disaster.service; +import com.numberone.backend.domain.conversation.dto.response.GetConversationResponse; +import com.numberone.backend.domain.conversation.entity.Conversation; +import com.numberone.backend.domain.conversation.repository.ConversationRepository; +import com.numberone.backend.domain.conversation.service.ConversationService; import com.numberone.backend.domain.disaster.dto.request.LatestDisasterRequest; import com.numberone.backend.domain.disaster.dto.request.SaveDisasterRequest; import com.numberone.backend.domain.disaster.dto.response.LatestDisasterResponse; +import com.numberone.backend.domain.disaster.dto.response.SituationDetailResponse; +import com.numberone.backend.domain.disaster.dto.response.SituationHomeResponse; +import com.numberone.backend.domain.disaster.dto.response.SituationResponse; import com.numberone.backend.domain.disaster.entity.Disaster; import com.numberone.backend.domain.disaster.repository.DisasterRepository; +import com.numberone.backend.domain.disaster.util.DisasterType; +import com.numberone.backend.domain.member.entity.Member; +import com.numberone.backend.domain.member.service.MemberService; +import com.numberone.backend.domain.notificationdisaster.entity.NotificationDisaster; +import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; +import com.numberone.backend.exception.badrequest.BadRequestConversationSortException; +import com.numberone.backend.exception.notfound.NotFoundDisasterException; import com.numberone.backend.util.LocationProvider; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -12,9 +26,13 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.awt.print.Pageable; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; @Service @RequiredArgsConstructor @@ -23,11 +41,14 @@ public class DisasterService { private final DisasterRepository disasterRepository; private final LocationProvider locationProvider; + private final MemberService memberService; + private final ConversationService conversationService; + private final ConversationRepository conversationRepository; public LatestDisasterResponse getLatestDisaster(LatestDisasterRequest latestDisasterRequest) { String address = locationProvider.pos2address(latestDisasterRequest.getLatitude(), latestDisasterRequest.getLongitude()); LocalDateTime time = LocalDateTime.now().minusDays(1); - List disasters = disasterRepository.findDisastersInAddressAfterTime(address, time, PageRequest.of(0, 1)); + List disasters = disasterRepository.findDisastersInAddressAfterTime(address, time); if (!disasters.isEmpty()) return LatestDisasterResponse.of(disasters.get(0)); else @@ -47,4 +68,55 @@ public void save(SaveDisasterRequest saveDisasterRequest) { ); disasterRepository.save(disaster); } + + private boolean isValidDisasterType(DisasterType disasterType, List notificationDisasters) { + for (NotificationDisaster notificationDisaster : notificationDisasters) { + if (disasterType.equals(notificationDisaster.getDisasterType())) + return true; + } + return false; + } + + public SituationHomeResponse getSituationHome(String email) { + Set disasters = new HashSet<>(); + Member member = memberService.findByEmail(email); + LocalDateTime time = LocalDateTime.now().minusDays(1); + for (NotificationRegion notificationRegion : member.getNotificationRegions()) { + disasters.addAll(disasterRepository.findDisastersInAddressAfterTime(notificationRegion.getLocation(), time)); + } + disasters.removeIf(disaster -> !isValidDisasterType(disaster.getDisasterType(), member.getNotificationDisasters())); + + + List situationResponses = new ArrayList<>(); + for (Disaster disaster : disasters) { + Long conversationCnt=0L; + List conversationResponses = new ArrayList<>(); + conversationCnt+=conversationRepository.countByDisaster(disaster); + List conversations = conversationRepository.findAllByDisasterOrderByLikeCntDesc(disaster, PageRequest.of(0,3)); + for (Conversation conversation : conversations) { + conversationResponses.add(conversationService.getExceptChild(email, conversation.getId())); + conversationCnt+=conversationRepository.countByParent(conversation); + } + situationResponses.add(SituationResponse.of(disaster, conversationResponses, conversationCnt)); + } + + return SituationHomeResponse.of(situationResponses); + } + + public SituationDetailResponse getSituationDetail(String email, Long disasterId, String sort) { + Disaster disaster = disasterRepository.findById(disasterId) + .orElseThrow(NotFoundDisasterException::new); + List conversationResponses = new ArrayList<>(); + List conversations; + if(sort.equals("popularity")) + conversations = conversationRepository.findAllByDisasterOrderByLikeCntDesc(disaster); + else if(sort.equals("time")) + conversations = conversationRepository.findAllByDisasterOrderByCreatedAtDesc(disaster); + else + throw new BadRequestConversationSortException(); + for (Conversation conversation : conversations) { + conversationResponses.add(conversationService.get(email, conversation.getId())); + } + return SituationDetailResponse.of(conversationResponses); + } } diff --git a/src/main/java/com/numberone/backend/domain/like/entity/ConversationLike.java b/src/main/java/com/numberone/backend/domain/like/entity/ConversationLike.java new file mode 100644 index 00000000..7eddeb68 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/like/entity/ConversationLike.java @@ -0,0 +1,39 @@ +package com.numberone.backend.domain.like.entity; + +import com.numberone.backend.domain.conversation.entity.Conversation; +import com.numberone.backend.domain.member.entity.Member; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ConversationLike { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "conversation_id") + private Conversation conversation; + + @Builder + public ConversationLike(Member member, Conversation conversation) { + this.member = member; + this.conversation = conversation; + } + + public static ConversationLike of(Member member, Conversation conversation) { + return ConversationLike.builder() + .member(member) + .conversation(conversation) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/like/repository/ConversationLikeRepository.java b/src/main/java/com/numberone/backend/domain/like/repository/ConversationLikeRepository.java new file mode 100644 index 00000000..81f0bf81 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/like/repository/ConversationLikeRepository.java @@ -0,0 +1,15 @@ +package com.numberone.backend.domain.like.repository; + +import com.numberone.backend.domain.conversation.entity.Conversation; +import com.numberone.backend.domain.like.entity.ConversationLike; +import com.numberone.backend.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; + +public interface ConversationLikeRepository extends JpaRepository { + Optional findByMemberAndConversation(Member member, Conversation conversation); + + long countByConversation(Conversation conversation); +} diff --git a/src/main/java/com/numberone/backend/domain/member/entity/Member.java b/src/main/java/com/numberone/backend/domain/member/entity/Member.java index 53d9ee4a..67de046c 100644 --- a/src/main/java/com/numberone/backend/domain/member/entity/Member.java +++ b/src/main/java/com/numberone/backend/domain/member/entity/Member.java @@ -3,6 +3,7 @@ import com.numberone.backend.config.basetime.BaseTimeEntity; import com.numberone.backend.domain.like.entity.ArticleLike; import com.numberone.backend.domain.like.entity.CommentLike; +import com.numberone.backend.domain.like.entity.ConversationLike; import jakarta.persistence.*; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -64,6 +65,9 @@ public class Member extends BaseTimeEntity { @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) private List notificationRegions = new ArrayList<>(); + @OneToMany(mappedBy = "member", cascade = CascadeType.ALL) + private List conversationLikes = new ArrayList<>(); + @Builder public Member(String email, String nickName, String realName, Integer heartCnt, String fcmToken) { this.email = email; diff --git a/src/main/java/com/numberone/backend/exception/badrequest/BadRequestConversationSortException.java b/src/main/java/com/numberone/backend/exception/badrequest/BadRequestConversationSortException.java new file mode 100644 index 00000000..e86bba4b --- /dev/null +++ b/src/main/java/com/numberone/backend/exception/badrequest/BadRequestConversationSortException.java @@ -0,0 +1,11 @@ +package com.numberone.backend.exception.badrequest; + +import com.numberone.backend.exception.context.ExceptionContext; + +import static com.numberone.backend.exception.context.CustomExceptionContext.BAD_REQUEST_CONVERSATION_SORT; + +public class BadRequestConversationSortException extends BadRequestException{ + public BadRequestConversationSortException() { + super(BAD_REQUEST_CONVERSATION_SORT); + } +} diff --git a/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java b/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java index 13eca97d..ca8601b0 100644 --- a/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java +++ b/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java @@ -28,6 +28,7 @@ public enum CustomExceptionContext implements ExceptionContext { INVALID_DISASTER_TYPE("존재하지 않는 재난 유형입니다.",5000), NOT_FOUND_API("데이터 수집을 위한 API 요청이 실패했습니다.",5001), NOT_FOUND_CRAWLING("데이터 수집을 위한 크롤링이 실패했습니다.",5002), + NOT_FOUND_DISASTER("존재하지 않는 재난상황입니다.", 5003), // fcm 관련 예외 FIREBASE_INITIALIZATION_FAILED("Firebase Application 초기 설정에 실패하였습니다.", 6000), @@ -50,6 +51,10 @@ public enum CustomExceptionContext implements ExceptionContext { // like 관련 예외 ALREADY_LIKED_ERROR("이미 좋아요 처리된 엔티티입니다.", 11000), ALREADY_UNLIKED_ERROR("이미 좋아요 해제 처리된 엔티티입니다.", 11001), + + //conversation 관련 예외 + NOT_FOUND_CONVERSATION("해당 대화를 찾을 수 없습니다.", 12000), + BAD_REQUEST_CONVERSATION_SORT("정렬 기준 값을 올바르게 전달해주세요. (popularity 또는 time)",12001) ; private final String message; diff --git a/src/main/java/com/numberone/backend/exception/notfound/NotFoundConversationException.java b/src/main/java/com/numberone/backend/exception/notfound/NotFoundConversationException.java new file mode 100644 index 00000000..8ca37e93 --- /dev/null +++ b/src/main/java/com/numberone/backend/exception/notfound/NotFoundConversationException.java @@ -0,0 +1,12 @@ +package com.numberone.backend.exception.notfound; + +import com.numberone.backend.exception.context.ExceptionContext; + +import static com.numberone.backend.exception.context.CustomExceptionContext.NOT_FOUND_CONVERSATION; + +public class NotFoundConversationException extends NotFoundException { + + public NotFoundConversationException() { + super(NOT_FOUND_CONVERSATION); + } +} diff --git a/src/main/java/com/numberone/backend/exception/notfound/NotFoundDisasterException.java b/src/main/java/com/numberone/backend/exception/notfound/NotFoundDisasterException.java new file mode 100644 index 00000000..c58872f1 --- /dev/null +++ b/src/main/java/com/numberone/backend/exception/notfound/NotFoundDisasterException.java @@ -0,0 +1,11 @@ +package com.numberone.backend.exception.notfound; + +import com.numberone.backend.exception.context.ExceptionContext; + +import static com.numberone.backend.exception.context.CustomExceptionContext.NOT_FOUND_DISASTER; + +public class NotFoundDisasterException extends NotFoundException { + public NotFoundDisasterException() { + super(NOT_FOUND_DISASTER); + } +} From cd739154d16797e203cc46bd706da202a59815f5 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sun, 12 Nov 2023 14:40:20 +0900 Subject: [PATCH 02/12] Feat(#54): resolved rebase conflicts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Feat(#54): 커뮤니티 API 기초 구현 Feat(#54): 게시글 상세 조회 시 태그 포함 Feat(#54): todo 작성 Feat(#54): 게시글 생성 시 이미지 업로드 bug fix Feat(#54): 게시글 상세 조회 시 좋아요 여부와 작성자 프로필 사진 url 반환 Feat(#54): 게시글 상세 조회 시 좋아요 여부와 작성자 프로필 사진 url 반환 Feat(#54): 게시글 리스트 조회 시, 좋아요 누른 여부를 포함 Feat(#54): todo 주석 제거 및 더미데이터 제거 Feat(#54): 썸네일 이미지 관련 요구사항 적용 - 첫 이미지를 썸네일 이미지로 지정 Feat(#54): 게시물 카테고리 수정 - 일상, 교통, 치안, 기타 Hotfix: npe fix Feat(#54): rename field for resolve rebase conflict Docs(#54): update todo Fix(#54): 좋아요 버그 수정 Fix(#54): 스웨거 jwt 버그 수정 Feat(#54): 게시글/댓글 좋아요 api 구현 Feat(#54): 알림 저장 시 닉네임도 저장 Feat(#54): 좋아요 관련 예외 로직 Feat(#54): 푸시 알림 태그 구분 Feat(#54): 푸시 알림 발송 시 db 에 저장 Feat(#54): 게시글 좋아요 api (wip) Feat(#54): 게시글 좋아요 api (wip) Feat(#54): improvement community apis - 게시글 작성 시, 작성자의 주소 로깅 - 게시글에 댓글 등록 시 푸시 알림 전송 Feat(#54): improvement community apis - 게시글 작성 시, 작성자의 주소 로깅 - 게시글에 댓글 등록 시 푸시 알림 전송 Feat(#54): catch exception Docs(#54): todo 작성 Feat(#54): 게시글 수정하기 api 구현 및 게시글 삭제 http method 를 patch 로 변경 Feat(#54): 대댓글 계층형 조회 api 구현 Feat(#54): 대댓글 작성 api 구현 Feat(#54): 게시글에 댓글 작성하기 api 구현 Feat(#54): 게시글 조회 no offset paging api 구현 Feat(#54): removed test api for fcm Feat(#54): removed test api for fcm Feat(#54): 게시글 상세 조회 시, 제목 및 내용 포함 Feat(#54): 게시글 상세 조회 및 삭제 API 구현 Docs(#54): 게시글 작성 API Feat(#54): removed Qclass Feat(#54): 게시글 활성화 상태 필드 추가 Feat(#54): 게시글 작성 API 구현 Feat(#54): rename CommunityParticipant - to ArticleParticipant Feat(#54): 게시글, 댓글, 커뮤니티 참여자 레포지토리 구현 Feat(#54): relocate s3Provider Docs(#54): 프로필 사진 업로드 api - swagger api summary, description Feat(#54): 커뮤니티 엔티티 설계 - Article: 동네생활 게시글 - ArticleTag: 게시글 태그 - CommentEntity: 댓글 - CommunityParticipant: 게시글 참여자 Feat(#54): 회원 프로필 사진 업로드 API 구현 Feat(#54): 회원 엔티티 필드 추가 - 프로필 사진, 닉네임, 실명, fcm 토큰 Feat(#54): implement SecurityContextProvider - for parsing principal from jwt token Feat(#54): member extends baseTimeEntity --- .../domain/article/dto/response/GetArticleDetailResponse.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java index a695eb61..839ba71f 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java @@ -70,4 +70,5 @@ public static GetArticleDetailResponse of( .build(); } + } From 2f840510a155045b2e0632f3859b03b57b23dae4 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Thu, 16 Nov 2023 23:12:11 +0900 Subject: [PATCH 03/12] Feat(#54): fix invalid uri prefix --- .../java/com/numberone/backend/domain/member/entity/Member.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/numberone/backend/domain/member/entity/Member.java b/src/main/java/com/numberone/backend/domain/member/entity/Member.java index 67de046c..f54d3390 100644 --- a/src/main/java/com/numberone/backend/domain/member/entity/Member.java +++ b/src/main/java/com/numberone/backend/domain/member/entity/Member.java @@ -26,6 +26,7 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter +@RequestMapping("/api/members") @Table(name = "MEMBER") public class Member extends BaseTimeEntity { @Id From 4701c85b3708db446e70f43d982db6a714841cc7 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Thu, 16 Nov 2023 23:14:20 +0900 Subject: [PATCH 04/12] Feat(#54): fix invalid uri prefix --- .../java/com/numberone/backend/domain/member/entity/Member.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/numberone/backend/domain/member/entity/Member.java b/src/main/java/com/numberone/backend/domain/member/entity/Member.java index f54d3390..67de046c 100644 --- a/src/main/java/com/numberone/backend/domain/member/entity/Member.java +++ b/src/main/java/com/numberone/backend/domain/member/entity/Member.java @@ -26,7 +26,6 @@ @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@RequestMapping("/api/members") @Table(name = "MEMBER") public class Member extends BaseTimeEntity { @Id From 1ea2eac4215fc8524b3d515af67242282c9a2179 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 17 Nov 2023 01:24:47 +0900 Subject: [PATCH 05/12] =?UTF-8?q?Feat(#54):=20comment=20count=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20bug=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/article/dto/response/GetArticleDetailResponse.java | 1 - .../backend/domain/article/service/ArticleService.java | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java index 839ba71f..a695eb61 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java @@ -70,5 +70,4 @@ public static GetArticleDetailResponse of( .build(); } - } diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index 52de3c0d..bd7d7c74 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -194,6 +194,8 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest new CommentEntity(request.getContent(), article, member) ); + + articleParticipantRepository.save(new ArticleParticipant(article, member)); // 게시글 작성자에게 알림을 보낸다. fcmMessageProvider.sendFcm(member, ARTICLE_COMMENT_FCM_ALARM, NotificationTag.COMMUNITY); From fa9cbc79e1c9e348c1629d63ed0dd5f18be98efe Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Fri, 17 Nov 2023 01:47:18 +0900 Subject: [PATCH 06/12] =?UTF-8?q?Feat(#54):=20=EB=B2=A0=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EA=B2=8C=EC=8B=9C=EA=B8=80=20=EC=9E=91=EC=84=B1=EC=9E=90?= =?UTF-8?q?=EC=97=90=EA=B2=8C=20=ED=91=B8=EC=8B=9C=EC=95=8C=EB=9E=8C=20?= =?UTF-8?q?=EC=A0=84=EC=86=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/article/service/ArticleService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index bd7d7c74..52de3c0d 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -194,8 +194,6 @@ public CreateCommentResponse createComment(Long articleId, CreateCommentRequest new CommentEntity(request.getContent(), article, member) ); - - articleParticipantRepository.save(new ArticleParticipant(article, member)); // 게시글 작성자에게 알림을 보낸다. fcmMessageProvider.sendFcm(member, ARTICLE_COMMENT_FCM_ALARM, NotificationTag.COMMUNITY); From c28ade74317823cb39c2cb52db21e96973da4794 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:21:30 +0900 Subject: [PATCH 07/12] =?UTF-8?q?Feat(#54):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EC=A7=80=EC=97=AD=20=EA=B5=AC?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/service/ArticleService.java | 3 +++ .../member/controller/MemberController.java | 13 ++++++++++-- .../GetNotificationRegionResponse.java | 20 +++++++++++++++++++ .../backend/domain/member/entity/Member.java | 7 +------ .../domain/member/service/MemberService.java | 18 +++++++++++++---- 5 files changed, 49 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/numberone/backend/domain/member/dto/response/GetNotificationRegionResponse.java diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index 52de3c0d..b3422b2e 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -19,6 +19,8 @@ import com.numberone.backend.domain.member.entity.Member; import com.numberone.backend.domain.member.repository.MemberRepository; import com.numberone.backend.domain.notification.entity.NotificationTag; +import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; +import com.numberone.backend.domain.notificationregion.repository.NotificationRegionRepository; import com.numberone.backend.domain.token.util.SecurityContextProvider; import com.numberone.backend.exception.notfound.NotFoundArticleException; import com.numberone.backend.exception.notfound.NotFoundMemberException; @@ -53,6 +55,7 @@ public class ArticleService { private final ArticleImageRepository articleImageRepository; private final CommentRepository commentRepository; private final ArticleLikeRepository articleLikeRepository; + private final NotificationRegionRepository notificationRegionRepository; private final S3Provider s3Provider; private final LocationProvider locationProvider; private final FcmMessageProvider fcmMessageProvider; diff --git a/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java b/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java index 2344e859..bfa65dde 100644 --- a/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java +++ b/src/main/java/com/numberone/backend/domain/member/controller/MemberController.java @@ -2,6 +2,7 @@ import com.numberone.backend.domain.member.dto.request.OnboardingRequest; import com.numberone.backend.domain.member.dto.request.BuyHeartRequest; +import com.numberone.backend.domain.member.dto.response.GetNotificationRegionResponse; import com.numberone.backend.domain.member.dto.response.HeartCntResponse; import com.numberone.backend.domain.member.dto.response.UploadProfileImageResponse; import com.numberone.backend.domain.member.service.MemberService; @@ -36,7 +37,7 @@ public class MemberController { @Operation(summary = "회원 프로필 사진 업로드 API", description = """ 1. 반드시 access token 을 헤더에 포함하여 호출해주세요. (유저를 식별하기 위함입니다.) - + 2. 프로필 사진은 MultipartFile 으로 반드시 image 라는 이름으로 보내주세요 """) @PostMapping("/profile-image") @@ -67,7 +68,15 @@ public ResponseEntity getHeart(Authentication authentication) 온보딩에서 선택한 닉네임, 재난유형, 알림지역 데이터를 body에 담아 전달해주세요. """) @PostMapping("/onboarding") - public void initMemberData(Authentication authentication, @Valid @RequestBody OnboardingRequest onboardingRequest){ + public void initMemberData(Authentication authentication, @Valid @RequestBody OnboardingRequest onboardingRequest) { memberService.initMemberData(authentication.getName(), onboardingRequest); } + + @Operation(summary = "사용자가 온보딩 시 추가한 지역 리스트 가져오기", description = """ + 게시글 커뮤니티 지역 구분으로 사용할 수 있습니다. + """) + @GetMapping("/regions") + public ResponseEntity getNotificationRegions() { + return ResponseEntity.ok(memberService.getNotificationRegionLv2()); + } } diff --git a/src/main/java/com/numberone/backend/domain/member/dto/response/GetNotificationRegionResponse.java b/src/main/java/com/numberone/backend/domain/member/dto/response/GetNotificationRegionResponse.java new file mode 100644 index 00000000..bc92d550 --- /dev/null +++ b/src/main/java/com/numberone/backend/domain/member/dto/response/GetNotificationRegionResponse.java @@ -0,0 +1,20 @@ +package com.numberone.backend.domain.member.dto.response; + +import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; +import lombok.*; + +import java.util.List; + +@ToString +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class GetNotificationRegionResponse { + List regions; + public static GetNotificationRegionResponse of (List notificationRegions){ + return GetNotificationRegionResponse.builder() + .regions(notificationRegions.stream().map(NotificationRegion::getLv2).toList()) + .build(); + } +} diff --git a/src/main/java/com/numberone/backend/domain/member/entity/Member.java b/src/main/java/com/numberone/backend/domain/member/entity/Member.java index 67de046c..8ff08b0e 100644 --- a/src/main/java/com/numberone/backend/domain/member/entity/Member.java +++ b/src/main/java/com/numberone/backend/domain/member/entity/Member.java @@ -4,20 +4,15 @@ import com.numberone.backend.domain.like.entity.ArticleLike; import com.numberone.backend.domain.like.entity.CommentLike; import com.numberone.backend.domain.like.entity.ConversationLike; -import jakarta.persistence.*; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; import com.numberone.backend.domain.notificationdisaster.entity.NotificationDisaster; import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; import com.numberone.backend.domain.support.entity.Support; +import jakarta.persistence.*; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import org.hibernate.annotations.Comment; -import org.springframework.web.bind.annotation.RequestMapping; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/numberone/backend/domain/member/service/MemberService.java b/src/main/java/com/numberone/backend/domain/member/service/MemberService.java index 7107ebc6..f7f74b93 100644 --- a/src/main/java/com/numberone/backend/domain/member/service/MemberService.java +++ b/src/main/java/com/numberone/backend/domain/member/service/MemberService.java @@ -1,19 +1,20 @@ package com.numberone.backend.domain.member.service; -import com.numberone.backend.domain.member.dto.response.UploadProfileImageResponse; import com.numberone.backend.domain.disaster.util.DisasterType; +import com.numberone.backend.domain.member.dto.request.BuyHeartRequest; import com.numberone.backend.domain.member.dto.request.OnboardingAddress; import com.numberone.backend.domain.member.dto.request.OnboardingDisasterType; import com.numberone.backend.domain.member.dto.request.OnboardingRequest; -import com.numberone.backend.domain.member.dto.request.BuyHeartRequest; +import com.numberone.backend.domain.member.dto.response.GetNotificationRegionResponse; import com.numberone.backend.domain.member.dto.response.HeartCntResponse; +import com.numberone.backend.domain.member.dto.response.UploadProfileImageResponse; import com.numberone.backend.domain.member.entity.Member; import com.numberone.backend.domain.member.repository.MemberRepository; -import com.numberone.backend.domain.token.util.SecurityContextProvider; import com.numberone.backend.domain.notificationdisaster.entity.NotificationDisaster; import com.numberone.backend.domain.notificationdisaster.repository.NotificationDisasterRepository; import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; import com.numberone.backend.domain.notificationregion.repository.NotificationRegionRepository; +import com.numberone.backend.domain.token.util.SecurityContextProvider; import com.numberone.backend.exception.notfound.NotFoundMemberException; import com.numberone.backend.support.s3.S3Provider; import lombok.RequiredArgsConstructor; @@ -22,6 +23,8 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + @RequiredArgsConstructor @Service @Slf4j @@ -80,7 +83,7 @@ public HeartCntResponse getHeart(String email) { } @Transactional - public UploadProfileImageResponse uploadProfileImage(MultipartFile image){ + public UploadProfileImageResponse uploadProfileImage(MultipartFile image) { String email = SecurityContextProvider.getAuthenticatedUserEmail(); Member member = memberRepository.findByEmail(email) .orElseThrow(NotFoundMemberException::new); @@ -93,4 +96,11 @@ public UploadProfileImageResponse uploadProfileImage(MultipartFile image){ return UploadProfileImageResponse.of(imageUrl); } + public GetNotificationRegionResponse getNotificationRegionLv2() { + String principal = SecurityContextProvider.getAuthenticatedUserEmail(); + Member member = memberRepository.findByEmail(principal) + .orElseThrow(NotFoundMemberException::new); + return GetNotificationRegionResponse.of(member.getNotificationRegions()); + } + } From ea406aa83c77c5b513d23ff3e843fb44399a15ae Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:25:31 +0900 Subject: [PATCH 08/12] =?UTF-8?q?Feat(#54):=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C?= =?UTF-8?q?,=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=A7=80=EC=97=AD(=EA=B5=AC)?= =?UTF-8?q?=20=EC=97=90=20=EB=94=B0=EB=9D=BC=EC=84=9C=20=ED=95=84=ED=84=B0?= =?UTF-8?q?=EB=A7=81=EB=90=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/dto/response/ArticleSearchParameter.java | 1 + .../numberone/backend/domain/article/entity/Article.java | 9 +++++++++ .../repository/custom/ArticleRepositoryCustomImpl.java | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/ArticleSearchParameter.java b/src/main/java/com/numberone/backend/domain/article/dto/response/ArticleSearchParameter.java index 5eb8b6dd..66ce5453 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/ArticleSearchParameter.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/ArticleSearchParameter.java @@ -13,4 +13,5 @@ public class ArticleSearchParameter { private Long lastArticleId; private Double longitude; private Double latitude; + private String regionLv2; // 구 } diff --git a/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 92f0785f..7fe25cbf 100644 --- a/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -54,6 +54,15 @@ public class Article extends BaseTimeEntity { @Comment("게시글 작성 당시 주소") private String address; + @Comment("시/도") + private String lv1; + + @Comment("구/군") + private String lv2; + + @Comment("동/읍/면") + private String lv3; + @ColumnDefault("0") @Comment("게시글 좋아요 개수") private Integer likeCount; diff --git a/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java b/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java index f4160bad..41dc34df 100644 --- a/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java +++ b/src/main/java/com/numberone/backend/domain/article/repository/custom/ArticleRepositoryCustomImpl.java @@ -35,7 +35,8 @@ public Slice getArticlesNoOffSetPaging(ArticleSearchPara .where( ltArticleId(param.getLastArticleId()), checkTagCondition(param.getTag()), - article.articleStatus.eq(ArticleStatus.ACTIVATED) + article.articleStatus.eq(ArticleStatus.ACTIVATED), + article.lv2.eq(param.getRegionLv2()) ) .orderBy(article.id.desc()) .limit(pageable.getPageSize() + 1) From 99317a0e7e041b4bf64888b1933337dbfb5dbe96 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:39:25 +0900 Subject: [PATCH 09/12] =?UTF-8?q?Feat(#54):=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=8B=9C,=20=EB=8F=99=20?= =?UTF-8?q?=EC=9C=84=EC=B9=98=20=EC=A0=95=EB=B3=B4=EC=97=90=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=ED=95=9C=20=EA=B2=BD=EC=9A=B0=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=EB=A5=BC=20=EC=A0=80=EC=9E=A5=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/article/dto/request/UploadArticleRequest.java | 5 +++-- .../numberone/backend/domain/article/entity/Article.java | 7 +++++++ .../backend/domain/article/service/ArticleService.java | 7 +++++-- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java b/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java index 9bcea7c0..34650b0d 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/request/UploadArticleRequest.java @@ -23,8 +23,6 @@ public class UploadArticleRequest { @NotNull(message = """ 게시글의 태그를 하나 선택해주세요. - - LIFE(일상), FRAUD(사기), SAFETY(안전), REPORT(제보) """) private ArticleTag articleTag; // 게시글 태그 @@ -34,4 +32,7 @@ public class UploadArticleRequest { private Double longitude; private Double latitude; + @NotNull(message = "동 위치 정보 제공 동의는 null 일 수 없습니다.") + private boolean regionAgreementCheck; // 동 정보 제공 동의 + } diff --git a/src/main/java/com/numberone/backend/domain/article/entity/Article.java b/src/main/java/com/numberone/backend/domain/article/entity/Article.java index 7fe25cbf..c0bee56e 100644 --- a/src/main/java/com/numberone/backend/domain/article/entity/Article.java +++ b/src/main/java/com/numberone/backend/domain/article/entity/Article.java @@ -98,6 +98,13 @@ public void updateAddress(String address) { this.address = address; } + public void updateAddressDetail (String[] addressDetails) { + int length = addressDetails.length; + this.lv1 = length > 0 ? addressDetails[0] : ""; + this.lv2 = length > 1 ? addressDetails[1] : ""; + this.lv3 = length > 2 ? addressDetails[2] : ""; + } + public void increaseLikeCount() { this.likeCount++; } diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index b3422b2e..bbc85ff9 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -110,16 +110,19 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { // 4. 작성자 주소 설정 Double latitude = request.getLatitude(); Double longitude = request.getLongitude(); - if (latitude != null && longitude != null) { + if (latitude != null && longitude != null && request.isRegionAgreementCheck()) { // 주소가 null 이 아닌 경우에만 api 요청하여 update String address = locationProvider.pos2address(request.getLatitude(), request.getLongitude()); article.updateAddress(address); + if(!address.isEmpty()){ + String[] regionInfo = address.split(" "); + article.updateAddressDetail(regionInfo); + } } return UploadArticleResponse.of(article, imageUrls, thumbNailImageUrl); } - @Transactional public DeleteArticleResponse deleteArticle(Long articleId) { Article article = articleRepository.findById(articleId) From 6f3a4f04a03bf5cf69f2813b1b49707f2b535a65 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:40:44 +0900 Subject: [PATCH 10/12] =?UTF-8?q?Feat(#54):=20=EA=B2=8C=EC=8B=9C=EA=B8=80?= =?UTF-8?q?=20=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C=20?= =?UTF-8?q?=EB=8F=99=20=EC=A0=95=EB=B3=B4=EB=A5=BC=20=EC=A0=9C=EA=B3=B5?= =?UTF-8?q?=ED=95=A9=EB=8B=88=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/article/dto/response/GetArticleDetailResponse.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java index a695eb61..e95aa792 100644 --- a/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java +++ b/src/main/java/com/numberone/backend/domain/article/dto/response/GetArticleDetailResponse.java @@ -32,6 +32,7 @@ public class GetArticleDetailResponse { private String ownerName; private String ownerNickName; private String address; + private String regionLv2; private Long ownerMemberId; private String ownerProfileImageUrl; @@ -67,6 +68,8 @@ public static GetArticleDetailResponse of( .isLiked(memberLikedArticleList.contains(article.getId())) .articleTag(article.getArticleTag()) .commentCount(commentCount) + .regionLv2(Optional.ofNullable(article.getLv2()) + .orElse("")) .build(); } From 7099a1088807f76564fb2ec081f53c3968bfa8c2 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:51:52 +0900 Subject: [PATCH 11/12] =?UTF-8?q?Feat(#54):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EB=95=8C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=9C=20=EC=A7=80=EC=97=AD=EC=9D=B4=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=EC=97=90=EC=84=9C=EB=8A=94=20=EA=B8=80=EC=9D=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=97=86=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../article/service/ArticleService.java | 24 +++++++++++++++---- .../NotificationRegionRepository.java | 2 ++ .../UnauthorizedLocationException.java | 9 +++++++ .../context/CustomExceptionContext.java | 12 +++++----- 4 files changed, 36 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/numberone/backend/exception/conflict/UnauthorizedLocationException.java diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index bbc85ff9..3e75729f 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -22,6 +22,7 @@ import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; import com.numberone.backend.domain.notificationregion.repository.NotificationRegionRepository; import com.numberone.backend.domain.token.util.SecurityContextProvider; +import com.numberone.backend.exception.conflict.UnauthorizedLocationException; import com.numberone.backend.exception.notfound.NotFoundArticleException; import com.numberone.backend.exception.notfound.NotFoundMemberException; import com.numberone.backend.support.fcm.service.FcmMessageProvider; @@ -110,19 +111,32 @@ public UploadArticleResponse uploadArticle(UploadArticleRequest request) { // 4. 작성자 주소 설정 Double latitude = request.getLatitude(); Double longitude = request.getLongitude(); + String address = ""; if (latitude != null && longitude != null && request.isRegionAgreementCheck()) { // 주소가 null 이 아닌 경우에만 api 요청하여 update - String address = locationProvider.pos2address(request.getLatitude(), request.getLongitude()); + address = locationProvider.pos2address(request.getLatitude(), request.getLongitude()); article.updateAddress(address); - if(!address.isEmpty()){ - String[] regionInfo = address.split(" "); - article.updateAddressDetail(regionInfo); - } + } + + if (!address.isEmpty()) { + String[] regionInfo = address.split(" "); + article.updateAddressDetail(regionInfo); + validateLocation(owner, address); } return UploadArticleResponse.of(article, imageUrls, thumbNailImageUrl); } + public void validateLocation(Member member, String realLocation) { + List regionLv2List = member.getNotificationRegions() + .stream().map(NotificationRegion::getLv2).toList(); + String[] realRegions = realLocation.split(" "); + + if (realRegions.length > 1 && !regionLv2List.contains(realRegions[1])) { + throw new UnauthorizedLocationException(); + } + } + @Transactional public DeleteArticleResponse deleteArticle(Long articleId) { Article article = articleRepository.findById(articleId) diff --git a/src/main/java/com/numberone/backend/domain/notificationregion/repository/NotificationRegionRepository.java b/src/main/java/com/numberone/backend/domain/notificationregion/repository/NotificationRegionRepository.java index 6c9b2f09..a75d5cda 100644 --- a/src/main/java/com/numberone/backend/domain/notificationregion/repository/NotificationRegionRepository.java +++ b/src/main/java/com/numberone/backend/domain/notificationregion/repository/NotificationRegionRepository.java @@ -3,6 +3,8 @@ import com.numberone.backend.domain.notificationregion.entity.NotificationRegion; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + public interface NotificationRegionRepository extends JpaRepository { void deleteAllByMemberId(Long memberId); } diff --git a/src/main/java/com/numberone/backend/exception/conflict/UnauthorizedLocationException.java b/src/main/java/com/numberone/backend/exception/conflict/UnauthorizedLocationException.java new file mode 100644 index 00000000..6edf5d53 --- /dev/null +++ b/src/main/java/com/numberone/backend/exception/conflict/UnauthorizedLocationException.java @@ -0,0 +1,9 @@ +package com.numberone.backend.exception.conflict; + +import static com.numberone.backend.exception.context.CustomExceptionContext.UNAUTHORIZED_LOCATION_ERROR; + +public class UnauthorizedLocationException extends ConflictException { + public UnauthorizedLocationException() { + super(UNAUTHORIZED_LOCATION_ERROR); + } +} diff --git a/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java b/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java index ca8601b0..d369880a 100644 --- a/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java +++ b/src/main/java/com/numberone/backend/exception/context/CustomExceptionContext.java @@ -25,9 +25,9 @@ public enum CustomExceptionContext implements ExceptionContext { S3_MULTIPART_MISSING("Multipart 파일이 null 이거나 없습니다.", 4001), // DISASTER 관련 예외 - INVALID_DISASTER_TYPE("존재하지 않는 재난 유형입니다.",5000), - NOT_FOUND_API("데이터 수집을 위한 API 요청이 실패했습니다.",5001), - NOT_FOUND_CRAWLING("데이터 수집을 위한 크롤링이 실패했습니다.",5002), + INVALID_DISASTER_TYPE("존재하지 않는 재난 유형입니다.", 5000), + NOT_FOUND_API("데이터 수집을 위한 API 요청이 실패했습니다.", 5001), + NOT_FOUND_CRAWLING("데이터 수집을 위한 크롤링이 실패했습니다.", 5002), NOT_FOUND_DISASTER("존재하지 않는 재난상황입니다.", 5003), // fcm 관련 예외 @@ -37,13 +37,14 @@ public enum CustomExceptionContext implements ExceptionContext { //후원 페이지 관련 예외 NOT_FOUND_SUPPORT("존재하지 않는 후원 관계입니다.", 7000), NOT_FOUND_SPONSOR("존재하지 않는 후원입니다.", 7001), - BAD_REQUEST_HEART("후원을 하기에는 사용자의 마음 갯수가 부족합니다.",7002), + BAD_REQUEST_HEART("후원을 하기에는 사용자의 마음 갯수가 부족합니다.", 7002), // article 관련 예외 NOT_FOUND_ARTICLE("해당 게시글을 찾을 수 없습니다.", 8000), // article image 관련 예외 NOT_FOUND_ARTICLE_IMAGE("해당 이미지를 찾을 수 없습니다.", 9000), + UNAUTHORIZED_LOCATION_ERROR("사용자가 해당 요청을 처리할 수 없는 지역에 위치하고 있습니다.", 9001), // comment 관련 예외 NOT_FOUND_COMMENT("해당 댓글을 찾을 수 없습니다.", 10000), @@ -54,8 +55,7 @@ public enum CustomExceptionContext implements ExceptionContext { //conversation 관련 예외 NOT_FOUND_CONVERSATION("해당 대화를 찾을 수 없습니다.", 12000), - BAD_REQUEST_CONVERSATION_SORT("정렬 기준 값을 올바르게 전달해주세요. (popularity 또는 time)",12001) - ; + BAD_REQUEST_CONVERSATION_SORT("정렬 기준 값을 올바르게 전달해주세요. (popularity 또는 time)", 12001); private final String message; private final int code; From cda5e3c086a0ba5cf5a301f0ebcc052af389b038 Mon Sep 17 00:00:00 2001 From: Jaehyeon Date: Sat, 18 Nov 2023 00:56:09 +0900 Subject: [PATCH 12/12] =?UTF-8?q?Feat(#54):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=98=A8=EB=B3=B4=EB=94=A9=20=EB=95=8C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=ED=95=9C=20=EC=A7=80=EC=97=AD=EC=9D=B4=20=EC=95=84?= =?UTF-8?q?=EB=8B=8C=20=EC=BB=A4=EB=AE=A4=EB=8B=88=ED=8B=B0=20=EC=A7=80?= =?UTF-8?q?=EC=97=AD=EC=97=90=EC=84=9C=EB=8A=94=20=EA=B8=80=EC=9D=84=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1=ED=95=A0=20=EC=88=98=20=EC=97=86=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/domain/article/service/ArticleService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java index 3e75729f..ca6f8a7e 100644 --- a/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java +++ b/src/main/java/com/numberone/backend/domain/article/service/ArticleService.java @@ -132,7 +132,7 @@ public void validateLocation(Member member, String realLocation) { .stream().map(NotificationRegion::getLv2).toList(); String[] realRegions = realLocation.split(" "); - if (realRegions.length > 1 && !regionLv2List.contains(realRegions[1])) { + if (realRegions.length >= 1 && !regionLv2List.contains(realRegions[1])) { throw new UnauthorizedLocationException(); } }