Skip to content

Commit cfabe69

Browse files
iamseojinLEEDONGH00NCopilot
authored
[feat] 여행 정보 community 게시물 및 댓글 관련 서비스 구현 (with-travel#104)
* [feat] 여행 정보 community 게시글 업로드 관련 전반적인 기능 구현 * [feat] 여행 정보 community 게시물 JPA Specification 기반 검색 기능 추가 * [feat] 여행 정보 community 댓글 업로드 및 조회 관련 기능 구현 * [feat] 여행정보 커뮤니티/댓글 관련 에러 코드 추가 * [feat] SecurityUtils 유틸 추가 (현재 로그인한 사용자 ID 조회) * Update src/main/java/com/arom/with_travel/domain/community/dto/CommunityDetailResponse.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/main/java/com/arom/with_travel/domain/community/service/CommunityService.java Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: LEEDONGHOON <123933574+LEEDONGH00N@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 4b1c9e3 commit cfabe69

21 files changed

Lines changed: 710 additions & 19 deletions

src/main/java/com/arom/with_travel/domain/community/Community.java

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@
66
import com.arom.with_travel.global.entity.BaseEntity;
77
import jakarta.persistence.*;
88
import jakarta.validation.constraints.NotNull;
9-
import lombok.AccessLevel;
10-
import lombok.Getter;
11-
import lombok.NoArgsConstructor;
9+
import lombok.*;
1210
import org.hibernate.annotations.SQLDelete;
1311
import org.hibernate.annotations.SQLRestriction;
1412

@@ -18,35 +16,57 @@
1816
@Getter
1917
@Entity
2018
@NoArgsConstructor(access = AccessLevel.PROTECTED)
19+
@Builder
20+
@AllArgsConstructor
2121
@SQLDelete(sql = "UPDATE community SET is_deleted = true, deleted_at = now() where id = ?")
2222
@SQLRestriction("is_deleted is FALSE")
2323
public class Community extends BaseEntity {
2424

2525
@Id
2626
@GeneratedValue(strategy = GenerationType.IDENTITY)
2727
private Long id;
28-
@NotNull
28+
29+
@NotNull @Column(length = 120)
2930
private String title;
3031

31-
@NotNull
32+
@NotNull @Lob
3233
private String content;
3334

34-
@NotNull
35-
private String continent;
36-
37-
@NotNull
38-
private String country;
39-
40-
@NotNull
41-
private String city;
35+
@NotNull private String continent;
36+
@NotNull private String country;
37+
@NotNull private String city;
4238

4339
@ManyToOne(fetch = FetchType.LAZY)
44-
@JoinColumn(name = "member_id")
40+
@JoinColumn(name = "member_id", nullable = false)
4541
private Member member;
4642

4743
@OneToMany(mappedBy = "community")
4844
private List<CommunityReply> communityReplies = new ArrayList<>();
4945

5046
@OneToMany(mappedBy = "community")
5147
private List<Image> images = new ArrayList<>();
48+
49+
@Column(nullable = false)
50+
private long viewCount = 0L;
51+
52+
public static Community create(Member writer, String title, String content,
53+
String continent, String country, String city) {
54+
Community c = Community.builder()
55+
.member(writer)
56+
.title(title)
57+
.content(content)
58+
.continent(continent)
59+
.country(country)
60+
.city(city)
61+
.build();
62+
return c;
63+
}
64+
65+
public void update(String title, String content, String continent, String country, String city) {
66+
if (title != null) this.title = title;
67+
if (content != null) this.content = content;
68+
if (continent != null) this.continent = continent;
69+
if (country != null) this.country = country;
70+
if (city != null) this.city = city;
71+
}
5272
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.arom.with_travel.domain.community;
2+
3+
import org.springframework.data.jpa.domain.Specification;
4+
import org.springframework.util.StringUtils;
5+
6+
public class CommunitySpecs {
7+
private CommunitySpecs() {}
8+
9+
public static Specification<Community> continentEq(String continent) {
10+
return (root, q, cb) -> StringUtils.hasText(continent) ? cb.equal(root.get("continent"), continent) : null;
11+
}
12+
13+
public static Specification<Community> countryEq(String country) {
14+
return (root, q, cb) -> StringUtils.hasText(country) ? cb.equal(root.get("country"), country) : null;
15+
}
16+
17+
public static Specification<Community> cityEq(String city) {
18+
return (root, q, cb) -> StringUtils.hasText(city) ? cb.equal(root.get("city"), city) : null;
19+
}
20+
21+
public static Specification<Community> keywordLike(String qword) {
22+
return (root, q, cb) -> {
23+
if (!StringUtils.hasText(qword)) return null;
24+
String like = "%" + qword.trim() + "%";
25+
return cb.or(
26+
cb.like(root.get("title"), like),
27+
cb.like(root.get("content"), like)
28+
);
29+
};
30+
}
31+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.arom.with_travel.domain.community.controller;
2+
3+
import com.arom.with_travel.domain.community.dto.CommunityCreateRequest;
4+
import com.arom.with_travel.domain.community.dto.CommunityDetailResponse;
5+
import com.arom.with_travel.domain.community.dto.CommunityListItemResponse;
6+
import com.arom.with_travel.domain.community.dto.CommunityUpdateRequest;
7+
import com.arom.with_travel.domain.community.service.CommunityService;
8+
import com.arom.with_travel.global.security.domain.PrincipalDetails;
9+
import com.arom.with_travel.global.utils.SecurityUtils;
10+
import jakarta.validation.Valid;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.data.domain.Page;
13+
import org.springframework.data.domain.PageRequest;
14+
import org.springframework.data.domain.Pageable;
15+
import org.springframework.data.domain.Sort;
16+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@RestController
20+
@RequiredArgsConstructor
21+
@RequestMapping("/api/v1/communities")
22+
public class CommunityController {
23+
24+
private final CommunityService communityService;
25+
26+
@PostMapping
27+
public Long create(@AuthenticationPrincipal PrincipalDetails principal,
28+
@RequestBody @Valid CommunityCreateRequest req) {
29+
String me = principal.getAuthenticatedMember().getEmail();
30+
return communityService.create(me, req);
31+
}
32+
33+
@GetMapping("/{id}")
34+
public CommunityDetailResponse detail(@PathVariable Long id) {
35+
return communityService.readAndIncreaseView(id);
36+
}
37+
38+
@GetMapping
39+
public Page<CommunityListItemResponse> list(
40+
@RequestParam(required = false) String continent,
41+
@RequestParam(required = false) String country,
42+
@RequestParam(required = false) String city,
43+
@RequestParam(required = false, name = "q") String keyword,
44+
@RequestParam(defaultValue = "0") int page,
45+
@RequestParam(defaultValue = "20") int size) {
46+
47+
Pageable pageable = PageRequest.of(page, size, Sort.by(Sort.Direction.DESC, "createdAt"));
48+
return communityService.search(continent, country, city, keyword, pageable);
49+
}
50+
51+
@PatchMapping("/{id}")
52+
public void update(@PathVariable Long id, @RequestBody @Valid CommunityUpdateRequest req) {
53+
Long me = SecurityUtils.currentMemberIdOrThrow();
54+
communityService.update(me, id, req);
55+
}
56+
57+
@DeleteMapping("/{id}")
58+
public void delete(@PathVariable Long id) {
59+
Long me = SecurityUtils.currentMemberIdOrThrow();
60+
communityService.delete(me, id);
61+
}
62+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.arom.with_travel.domain.community.dto;
2+
3+
import jakarta.validation.constraints.NotEmpty;
4+
import jakarta.validation.constraints.Size;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.util.List;
10+
11+
@Getter
12+
@NoArgsConstructor
13+
@AllArgsConstructor
14+
public class CommunityCreateRequest {
15+
@NotEmpty @Size(max = 120) String title;
16+
@NotEmpty String content;
17+
@NotEmpty String continent;
18+
@NotEmpty String country;
19+
@NotEmpty String city;
20+
private List<ImageCreate> images;
21+
22+
@Getter
23+
@NoArgsConstructor
24+
@AllArgsConstructor
25+
public static class ImageCreate {
26+
private String imageName;
27+
private String imageUrl;
28+
}
29+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.arom.with_travel.domain.community.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
7+
import java.util.List;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class CommunityDetailResponse {
13+
Long id;
14+
String title;
15+
String content;
16+
String continent;
17+
String country;
18+
String city;
19+
Long writerId;
20+
String writerNickname;
21+
long viewCount;
22+
List<String> imageUrls;
23+
String createdAt;
24+
String updatedAt;
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.arom.with_travel.domain.community.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
7+
@Getter
8+
@NoArgsConstructor
9+
@AllArgsConstructor
10+
public class CommunityListItemResponse {
11+
Long id;
12+
String title;
13+
String snippet;
14+
String continent;
15+
String country;
16+
String city;
17+
Long writerId;
18+
String writerNickname;
19+
long viewCount;
20+
String createdAt;
21+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.arom.with_travel.domain.community.dto;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
import lombok.NoArgsConstructor;
6+
7+
import java.util.List;
8+
9+
@Getter
10+
@NoArgsConstructor
11+
@AllArgsConstructor
12+
public class CommunityUpdateRequest {
13+
private String title;
14+
private String content;
15+
private String continent;
16+
private String country;
17+
private String city;
18+
private List<ImageUpdate> images;
19+
20+
@Getter
21+
@NoArgsConstructor
22+
@AllArgsConstructor
23+
public static class ImageUpdate {
24+
private String imageName;
25+
private String imageUrl;
26+
}
27+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.arom.with_travel.domain.community.repository;
2+
3+
import com.arom.with_travel.domain.community.Community;
4+
import org.springframework.data.domain.Page;
5+
import org.springframework.data.domain.Pageable;
6+
import org.springframework.data.jpa.domain.Specification;
7+
import org.springframework.data.jpa.repository.*;
8+
9+
public interface CommunityRepository extends JpaRepository<Community, Long>,
10+
JpaSpecificationExecutor<Community> {
11+
12+
@EntityGraph(attributePaths = {"member", "images"})
13+
@Query("select c from Community c where c.id = :id")
14+
Community findDetailById(Long id);
15+
16+
@Modifying(clearAutomatically = true, flushAutomatically = true)
17+
@Query("update Community c set c.viewCount = c.viewCount + 1 where c.id = :id")
18+
int increaseViewCount(Long id);
19+
20+
default Page<Community> search(Specification<Community> spec, Pageable pageable) {
21+
return this.findAll(spec, pageable);
22+
}
23+
}

0 commit comments

Comments
 (0)