-
Notifications
You must be signed in to change notification settings - Fork 4
[feat] 여행 정보 community 게시물 및 댓글 관련 서비스 구현 #104
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
22dd81f
cc94e56
886d5d3
11e80a3
11efdec
55e52d0
7dc1498
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,7 @@ | |
| import com.arom.with_travel.global.entity.BaseEntity; | ||
| import jakarta.persistence.*; | ||
| import jakarta.validation.constraints.NotNull; | ||
| import lombok.AccessLevel; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.*; | ||
| import org.hibernate.annotations.SQLDelete; | ||
| import org.hibernate.annotations.SQLRestriction; | ||
|
|
||
|
|
@@ -18,35 +16,57 @@ | |
| @Getter | ||
| @Entity | ||
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
| @Builder | ||
| @AllArgsConstructor | ||
| @SQLDelete(sql = "UPDATE community SET is_deleted = true, deleted_at = now() where id = ?") | ||
| @SQLRestriction("is_deleted is FALSE") | ||
| public class Community extends BaseEntity { | ||
|
|
||
| @Id | ||
| @GeneratedValue(strategy = GenerationType.IDENTITY) | ||
| private Long id; | ||
| @NotNull | ||
|
|
||
| @NotNull @Column(length = 120) | ||
| private String title; | ||
|
|
||
| @NotNull | ||
| @NotNull @Lob | ||
| private String content; | ||
|
|
||
| @NotNull | ||
| private String continent; | ||
|
|
||
| @NotNull | ||
| private String country; | ||
|
|
||
| @NotNull | ||
| private String city; | ||
| @NotNull private String continent; | ||
| @NotNull private String country; | ||
| @NotNull private String city; | ||
|
|
||
| @ManyToOne(fetch = FetchType.LAZY) | ||
| @JoinColumn(name = "member_id") | ||
| @JoinColumn(name = "member_id", nullable = false) | ||
| private Member member; | ||
|
|
||
| @OneToMany(mappedBy = "community") | ||
| private List<CommunityReply> communityReplies = new ArrayList<>(); | ||
|
|
||
| @OneToMany(mappedBy = "community") | ||
| private List<Image> images = new ArrayList<>(); | ||
|
|
||
| @Column(nullable = false) | ||
| private long viewCount = 0L; | ||
|
|
||
| public static Community create(Member writer, String title, String content, | ||
| String continent, String country, String city) { | ||
| Community c = Community.builder() | ||
| .member(writer) | ||
| .title(title) | ||
| .content(content) | ||
| .continent(continent) | ||
| .country(country) | ||
| .city(city) | ||
| .build(); | ||
| return c; | ||
| } | ||
|
|
||
| public void update(String title, String content, String continent, String country, String city) { | ||
| if (title != null) this.title = title; | ||
| if (content != null) this.content = content; | ||
| if (continent != null) this.continent = continent; | ||
| if (country != null) this.country = country; | ||
| if (city != null) this.city = city; | ||
| } | ||
|
Comment on lines
+65
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. null 여부를 확인하지 말고 그냥 값을 초기화하면 될것 같습니다. 게시글 수정 시 수정된 부분만 받는것이 아닌 전체 수정으로 받을 것 같기 때문입니다.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 네 |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package com.arom.with_travel.domain.community; | ||
|
|
||
| import org.springframework.data.jpa.domain.Specification; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| public class CommunitySpecs { | ||
| private CommunitySpecs() {} | ||
|
|
||
|
Comment on lines
+6
to
+8
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 어떤 목적으로 만든 클래스인가요?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JPA Specification 모아 둔 유틸 클래스인데 대륙/국가/도시/검색 키워드 조건을 동적으로 조합해서 커뮤니티 게시글 검색할 때 사용하려고 만든 거예요 |
||
| public static Specification<Community> continentEq(String continent) { | ||
| return (root, q, cb) -> StringUtils.hasText(continent) ? cb.equal(root.get("continent"), continent) : null; | ||
| } | ||
|
|
||
| public static Specification<Community> countryEq(String country) { | ||
| return (root, q, cb) -> StringUtils.hasText(country) ? cb.equal(root.get("country"), country) : null; | ||
| } | ||
|
|
||
| public static Specification<Community> cityEq(String city) { | ||
| return (root, q, cb) -> StringUtils.hasText(city) ? cb.equal(root.get("city"), city) : null; | ||
| } | ||
|
|
||
| public static Specification<Community> keywordLike(String qword) { | ||
| return (root, q, cb) -> { | ||
| if (!StringUtils.hasText(qword)) return null; | ||
| String like = "%" + qword.trim() + "%"; | ||
| return cb.or( | ||
| cb.like(root.get("title"), like), | ||
| cb.like(root.get("content"), like) | ||
| ); | ||
| }; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| package com.arom.with_travel.domain.community.controller; | ||
|
|
||
| import com.arom.with_travel.domain.community.dto.CommunityCreateRequest; | ||
| import com.arom.with_travel.domain.community.dto.CommunityDetailResponse; | ||
| import com.arom.with_travel.domain.community.dto.CommunityListItemResponse; | ||
| import com.arom.with_travel.domain.community.dto.CommunityUpdateRequest; | ||
| import com.arom.with_travel.domain.community.service.CommunityService; | ||
| import com.arom.with_travel.global.security.domain.PrincipalDetails; | ||
| import com.arom.with_travel.global.utils.SecurityUtils; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.PageRequest; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.domain.Sort; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/v1/communities") | ||
| public class CommunityController { | ||
|
|
||
| private final CommunityService communityService; | ||
|
|
||
| @PostMapping | ||
| public Long create(@AuthenticationPrincipal PrincipalDetails principal, | ||
| @RequestBody @Valid CommunityCreateRequest req) { | ||
| String me = principal.getAuthenticatedMember().getEmail(); | ||
| return communityService.create(me, req); | ||
| } | ||
|
|
||
| @GetMapping("/{id}") | ||
| public CommunityDetailResponse detail(@PathVariable Long id) { | ||
| return communityService.readAndIncreaseView(id); | ||
| } | ||
|
|
||
| @GetMapping | ||
| public Page<CommunityListItemResponse> list( | ||
| @RequestParam(required = false) String continent, | ||
| @RequestParam(required = false) String country, | ||
| @RequestParam(required = false) String city, | ||
| @RequestParam(required = false, name = "q") String keyword, | ||
| @RequestParam(defaultValue = "0") int page, | ||
| @RequestParam(defaultValue = "20") int size) { | ||
|
|
||
| Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt")); | ||
| return communityService.search(continent, country, city, keyword, pageable); | ||
| } | ||
|
|
||
| @PatchMapping("/{id}") | ||
| public void update(@PathVariable Long id, @RequestBody @Valid CommunityUpdateRequest req) { | ||
| Long me = SecurityUtils.currentMemberIdOrThrow(); | ||
| communityService.update(me, id, req); | ||
| } | ||
|
|
||
| @DeleteMapping("/{id}") | ||
| public void delete(@PathVariable Long id) { | ||
| Long me = SecurityUtils.currentMemberIdOrThrow(); | ||
| communityService.delete(me, id); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| package com.arom.with_travel.domain.community.dto; | ||
|
|
||
| import jakarta.validation.constraints.NotEmpty; | ||
| import jakarta.validation.constraints.Size; | ||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CommunityCreateRequest { | ||
| @NotEmpty @Size(max = 120) String title; | ||
| @NotEmpty String content; | ||
| @NotEmpty String continent; | ||
| @NotEmpty String country; | ||
| @NotEmpty String city; | ||
| private List<ImageCreate> images; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public static class ImageCreate { | ||
| private String imageName; | ||
| private String imageUrl; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.arom.with_travel.domain.community.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CommunityDetailResponse { | ||
| Long id; | ||
| String title; | ||
| String content; | ||
| String continent; | ||
| String country; | ||
| String city; | ||
| Long writerId; | ||
| String writerNickname; | ||
| long viewCount; | ||
| List<String> imageUrls; | ||
| String createdAt; | ||
| String updatedAt; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.arom.with_travel.domain.community.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CommunityListItemResponse { | ||
| Long id; | ||
| String title; | ||
| String snippet; | ||
| String continent; | ||
| String country; | ||
| String city; | ||
| Long writerId; | ||
| String writerNickname; | ||
| long viewCount; | ||
| String createdAt; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package com.arom.with_travel.domain.community.dto; | ||
|
|
||
| import lombok.AllArgsConstructor; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public class CommunityUpdateRequest { | ||
| private String title; | ||
| private String content; | ||
| private String continent; | ||
| private String country; | ||
| private String city; | ||
| private List<ImageUpdate> images; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| @AllArgsConstructor | ||
| public static class ImageUpdate { | ||
| private String imageName; | ||
| private String imageUrl; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.arom.with_travel.domain.community.repository; | ||
|
|
||
| import com.arom.with_travel.domain.community.Community; | ||
| import org.springframework.data.domain.Page; | ||
| import org.springframework.data.domain.Pageable; | ||
| import org.springframework.data.jpa.domain.Specification; | ||
| import org.springframework.data.jpa.repository.*; | ||
|
|
||
| public interface CommunityRepository extends JpaRepository<Community, Long>, | ||
| JpaSpecificationExecutor<Community> { | ||
|
|
||
| @EntityGraph(attributePaths = {"member", "images"}) | ||
| @Query("select c from Community c where c.id = :id") | ||
| Community findDetailById(Long id); | ||
|
|
||
| @Modifying(clearAutomatically = true, flushAutomatically = true) | ||
| @Query("update Community c set c.viewCount = c.viewCount + 1 where c.id = :id") | ||
| int increaseViewCount(Long id); | ||
|
|
||
| default Page<Community> search(Specification<Community> spec, Pageable pageable) { | ||
| return this.findAll(spec, pageable); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
연관관계 양방향 매핑도 추가해주세요