Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 34 additions & 7 deletions src/main/java/com/arom/with_travel/domain/community/Community.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.arom.with_travel.domain.community;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import com.arom.with_travel.domain.community_reply.CommunityReply;
import com.arom.with_travel.domain.image.Image;
import com.arom.with_travel.domain.member.Member;
Expand All @@ -14,16 +15,22 @@
import java.util.List;

@Getter
@Setter
@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")
@SQLDelete(sql = "UPDATE community SET is_deleted = true, deleted_at = now() WHERE id = ?")
@SQLRestriction("is_deleted = FALSE")
@Table(indexes = {
@Index(name = "idx_community_tag", columnList = "tag"),
@Index(name = "idx_community_created_at", columnList = "created_at"),
@Index(name = "idx_community_like_count", columnList = "like_count"),
@Index(name = "idx_community_view_count", columnList = "view_count")
})
public class Community extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@NotNull @Column(length = 120)
Expand All @@ -32,6 +39,10 @@ public class Community extends BaseEntity {
@NotNull @Lob
private String content;

@Enumerated(EnumType.STRING)
@Column(length = 20, nullable = false)
private CommunityTag tag;

@NotNull private String continent;
@NotNull private String country;
@NotNull private String city;
Expand All @@ -43,18 +54,27 @@ public class Community extends BaseEntity {
@OneToMany(mappedBy = "community", orphanRemoval = true)
private List<CommunityReply> communityReplies = new ArrayList<>();

@OneToMany(mappedBy = "community")
@OneToMany(mappedBy = "community", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<Image> images = new ArrayList<>();

@Column(nullable = false)
@Column(name = "view_count", nullable = false)
private long viewCount = 0L;

@Column(name = "like_count", nullable = false)
private long likeCount = 0L;

@Column(name = "reply_count", nullable = false)
private long replyCount = 0L;

public static Community create(Member writer, String title, String content,
CommunityTag tag,
String continent, String country, String city) {
Community c = Community.builder()
.member(writer)
.title(title)
.content(content)
.tag(tag)
.continent(continent)
.country(country)
.city(city)
Expand All @@ -80,9 +100,16 @@ public void addReply(CommunityReply reply) {
}
}

public void update(String title, String content, String continent, String country, String city) {
public void addImage(Image image) {
images.add(image);
image.attachToCommunity(this);
}

public void update(String title, String content, CommunityTag tag,
String continent, String country, String city) {
this.title = title;
this.content = content;
this.tag = tag;
this.continent = continent;
this.country = country;
this.city = city;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.arom.with_travel.domain.community;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;

public class CommunitySpecs {
private CommunitySpecs() {}

public static Specification<Community> tagEq(CommunityTag tag) {
return (root, q, cb) -> (tag != null) ? cb.equal(root.get("tag"), tag) : null;
}

public static Specification<Community> continentEq(String continent) {
return (root, q, cb) -> StringUtils.hasText(continent) ? cb.equal(root.get("continent"), continent) : null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,24 @@
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.enums.CommunityTag;
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 lombok.extern.slf4j.Slf4j;
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.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/communities")
Expand All @@ -24,8 +30,10 @@ public class CommunityController {
private final CommunityService communityService;

@PostMapping
public Long create(@AuthenticationPrincipal PrincipalDetails principal,
@RequestBody @Valid CommunityCreateRequest req) {
public Long create(
@AuthenticationPrincipal PrincipalDetails principal,
@RequestBody @Valid CommunityCreateRequest req
) {
String me = principal.getAuthenticatedMember().getEmail();
return communityService.create(me, req);
}
Expand All @@ -41,22 +49,63 @@ public Page<CommunityListItemResponse> list(
@RequestParam(required = false) String country,
@RequestParam(required = false) String city,
@RequestParam(required = false, name = "q") String keyword,
@RequestParam(required = false, name = "tag") CommunityTag tag,
@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);
return communityService.search(continent, country, city, keyword, tag, pageable);
}

@GetMapping("/top-liked")
public List<CommunityListItemResponse> topLiked() {
return communityService.topLiked();
}

@PostMapping("/{id}/like")
public Map<String, Object> toggleLike(
@PathVariable Long id,
@AuthenticationPrincipal PrincipalDetails principal
) {
Long me = principal.getAuthenticatedMember().getMemberId();
boolean liked = communityService.toggleLike(id, me);
return Map.of("liked", liked);
}

@PatchMapping("/{id}")
public void update(@PathVariable Long id, @RequestBody @Valid CommunityUpdateRequest req) {
Long me = SecurityUtils.currentMemberIdOrThrow();
communityService.update(me, id, req);
public CommunityDetailResponse update(
@PathVariable Long id,
@RequestBody @Valid CommunityUpdateRequest req,
@AuthenticationPrincipal PrincipalDetails principal
) {
Long me = principal.getAuthenticatedMember().getMemberId();
return communityService.update(me, id, req);
}

@DeleteMapping("/{id}")
public void delete(@PathVariable Long id) {
Long me = SecurityUtils.currentMemberIdOrThrow();
public ResponseEntity<Void> delete(
@PathVariable Long id,
@AuthenticationPrincipal PrincipalDetails principal
) {
Long me = principal.getAuthenticatedMember().getMemberId();
communityService.delete(me, id);
return ResponseEntity.noContent().build();
}

@GetMapping("/tags/{tag}")
public Page<CommunityListItemResponse> listByTag(
@PathVariable CommunityTag tag,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size
) {
int limitedSize = Math.min(Math.max(size, 1), 50);

Pageable pageable = PageRequest.of(
page,
limitedSize,
Sort.by(Sort.Direction.DESC, "createdAt")
);
return communityService.listByTag(tag, pageable);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.arom.with_travel.domain.community.dto;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
Expand All @@ -17,6 +18,7 @@ public class CommunityCreateRequest {
@NotEmpty String continent;
@NotEmpty String country;
@NotEmpty String city;
private CommunityTag tag;
private List<ImageCreate> images;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.arom.with_travel.domain.community.dto;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -13,12 +14,15 @@ public class CommunityDetailResponse {
Long id;
String title;
String content;
private CommunityTag tag;
String continent;
String country;
String city;
Long writerId;
String writerNickname;
long viewCount;
long likeCount;
long replyCount;
List<String> imageUrls;
String createdAt;
String updatedAt;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.arom.with_travel.domain.community.dto;

import com.arom.with_travel.domain.community.Community;
import com.arom.with_travel.domain.image.Image;

public class CommunityDetailResponseMapper {

public static CommunityDetailResponse from(Community c) {
return new CommunityDetailResponse(
c.getId(),
c.getTitle(),
c.getContent(),
c.getTag(),
c.getContinent(),
c.getCountry(),
c.getCity(),
c.getMember().getId(),
c.getMember().getNickname(),
c.getViewCount(),
c.getLikeCount(),
c.getReplyCount(),
c.getImages().stream().map(Image::getImageUrl).toList(),
c.getCreatedAt().toString(),
c.getUpdatedAt().toString()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.arom.with_travel.domain.community.dto;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -11,11 +12,14 @@ public class CommunityListItemResponse {
Long id;
String title;
String snippet;
CommunityTag tag;
String continent;
String country;
String city;
Long writerId;
String writerNickname;
long viewCount;
long likeCount;
long replyCount;
String createdAt;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.arom.with_travel.domain.community.dto;

import com.arom.with_travel.domain.community.enums.CommunityTag;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -15,6 +16,7 @@ public class CommunityUpdateRequest {
private String continent;
private String country;
private String city;
private CommunityTag tag;
private List<ImageUpdate> images;

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.arom.with_travel.domain.community.enums;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonValue;

import java.util.Arrays;

public enum CommunityTag {
RESTAURANT("맛집추천"),
CAFE("카페탐방"),
INFO("정보공유"),
TIPS("꿀팁");

private final String labelKo;

CommunityTag(String labelKo) {
this.labelKo = labelKo;
}

public String getLabelKo() {
return labelKo;
}

@JsonCreator
public static CommunityTag from(String v) {
if (v == null) return null;
String key = v.trim();
String upper = key.toUpperCase();

return Arrays.stream(values())
.filter(e -> e.name().equalsIgnoreCase(upper) || e.labelKo.equalsIgnoreCase(key))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unknown tag: " + v));
}

@JsonValue
public String toValue() {
return this.name();
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.arom.with_travel.domain.community.repository;

import com.arom.with_travel.domain.community.Community;
import com.arom.with_travel.domain.community.enums.CommunityTag;
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.*;

import java.util.List;

public interface CommunityRepository extends JpaRepository<Community, Long>,
JpaSpecificationExecutor<Community> {

Expand All @@ -17,7 +20,15 @@ public interface CommunityRepository extends JpaRepository<Community, Long>,
@Query("update Community c set c.viewCount = c.viewCount + 1 where c.id = :id")
int increaseViewCount(Long id);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update Community c set c.likeCount = c.likeCount + :delta where c.id = :id")
int addLikeCount(Long id, long delta);

Page<Community> findByTagOrderByCreatedAtDesc(CommunityTag tag, Pageable pageable);

List<Community> findTop2ByOrderByLikeCountDescIdDesc();

default Page<Community> search(Specification<Community> spec, Pageable pageable) {
return this.findAll(spec, pageable);
}
}
}
Loading