Skip to content

Commit

Permalink
Merge pull request #87 from Lets-JUPJUP/feature/post-v2
Browse files Browse the repository at this point in the history
feat : 플로깅 게시글 필터링 구현
  • Loading branch information
chhaewxn authored Aug 7, 2024
2 parents 357467b + 96bac02 commit 39fbbfd
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 13 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.data:spring-data-jpa'

//springdoc
implementation 'org.springdoc:springdoc-openapi-ui:1.6.12'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
package efub.back.jupjup.domain.post.controller;

import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import efub.back.jupjup.domain.member.domain.Member;
import efub.back.jupjup.domain.post.domain.District;
import efub.back.jupjup.domain.post.dto.PostFilterDto;
import efub.back.jupjup.domain.post.dto.PostRequestDto;
import efub.back.jupjup.domain.post.service.PostService;
import efub.back.jupjup.domain.security.userInfo.AuthUser;
Expand Down Expand Up @@ -96,4 +102,22 @@ public ResponseEntity<StatusResponse> getSuccessfulRecruitmentPosts(@AuthUser Me
public ResponseEntity<StatusResponse> getCompletedPosts(@AuthUser Member member) {
return postService.getCompletedPosts(member);
}

// 필터링된 게시글 리스트 조회
@GetMapping("/filter")
public ResponseEntity<StatusResponse> getFilteredPosts(
@ModelAttribute PostFilterDto filterDto,
@AuthUser Member member,
@RequestParam(required = false) List<String> districts) {

if (districts != null && !districts.isEmpty()) {
List<District> districtList = districts.stream()
.map(District::from)
.collect(Collectors.toList());
filterDto.setDistricts(districtList);
}

filterDto.setUserGender(member.getGender());
return postService.getFilteredPosts(filterDto, member);
}
}
52 changes: 47 additions & 5 deletions src/main/java/efub/back/jupjup/domain/post/domain/District.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
package efub.back.jupjup.domain.post.domain;

import lombok.AllArgsConstructor;
import com.fasterxml.jackson.annotation.JsonCreator;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum District {
강남구, 강동구, 강북구, 강서구, 관악구, 광진구, 구로구, 금천구, 노원구, 도봉구,
동대문구, 동작구, 마포구, 서대문구, 서초구, 성동구, 성북구, 송파구, 양천구, 영등포구,
용산구, 은평구, 종로구, 중구, 중랑구
GANGNAM("강남구"),
GANGDONG("강동구"),
GANGBUK("강북구"),
GANGSEO("강서구"),
GWANAK("관악구"),
GWANGJIN("광진구"),
GURO("구로구"),
GEUMCHEON("금천구"),
NOWON("노원구"),
DOBONG("도봉구"),
DONGDAEMUN("동대문구"),
DONGJAK("동작구"),
MAPO("마포구"),
SEODAEMUN("서대문구"),
SEOCHO("서초구"),
SEONGDONG("성동구"),
SEONGBUK("성북구"),
SONGPA("송파구"),
YANGCHEON("양천구"),
YEONGDEUNGPO("영등포구"),
YONGSAN("용산구"),
EUNPYEONG("은평구"),
JONGNO("종로구"),
JUNG("중구"),
JUNGNANG("중랑구");

private final String koreanName;

District(String koreanName) {
this.koreanName = koreanName;
}

@JsonCreator
public static District from(String value) {
for (District district : District.values()) {
if (district.getKoreanName().equals(value) || district.name().equalsIgnoreCase(value)) {
return district;
}
}
throw new IllegalArgumentException("Invalid district: " + value);
}

@Override
public String toString() {
return this.koreanName;
}
}
22 changes: 22 additions & 0 deletions src/main/java/efub/back/jupjup/domain/post/dto/PostFilterDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package efub.back.jupjup.domain.post.dto;

import java.util.List;

import efub.back.jupjup.domain.member.domain.Gender;
import efub.back.jupjup.domain.post.domain.District;
import efub.back.jupjup.domain.post.domain.PostGender;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class PostFilterDto {
private PostGender postGender;
private Boolean withPet;
private List<District> districts;
private Boolean allAge; // '연령무관' 옵션
private Boolean allGender; // '성별무관' 옵션
private Boolean excludeClosedRecruitment;
private Gender userGender;
private Integer userAge;
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public Post toEntity(PostRequestDto dto, Member member) {
.maxMember(dto.getMaxMember())
.postGender(dto.getPostGender())
.withPet(dto.isWithPet())
.district(District.valueOf(dto.getDistrict()))
.district(District.from(dto.getDistrict()))
.route(dto.getRoute().stream()
.map(routeDto -> new Route(routeDto.getAddress(), routeDto.getLatitude(), routeDto.getLongitude()))
.collect(Collectors.toList()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,20 @@

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import efub.back.jupjup.domain.member.domain.Member;
import efub.back.jupjup.domain.post.domain.Post;
import efub.back.jupjup.domain.post.domain.PostGender;
import efub.back.jupjup.domain.post.dto.PostFilterDto;
import lombok.RequiredArgsConstructor;

public interface PostRepository extends JpaRepository<Post, Long> {

public interface PostRepository extends JpaRepository<Post, Long>, JpaSpecificationExecutor<Post> {
List<Post> findAllByPostGender(PostGender postGender, Sort sort);

List<Post> findAllByWithPet(boolean withPet, Sort sort);

long countByAuthor(Member author);

List<Post> findAllByAuthor(Member author, Sort sort);

List<Post> findByDueDateBetween(LocalDateTime startOfDay, LocalDateTime endOfDay);

List<Post> findAllByDueDateAfterOrderByCreatedAtDesc(LocalDateTime now);
List<Post> findAllByDueDateBeforeAndIsRecruitmentSuccessfulTrueOrderByCreatedAtDesc(LocalDateTime now);
List<Post> findAllByDueDateBeforeOrderByCreatedAtDesc(LocalDateTime now);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.stream.Collectors;

import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -17,11 +18,13 @@
import efub.back.jupjup.domain.post.domain.Post;
import efub.back.jupjup.domain.post.domain.PostGender;
import efub.back.jupjup.domain.post.domain.PostImage;
import efub.back.jupjup.domain.post.dto.PostFilterDto;
import efub.back.jupjup.domain.post.dto.PostRequestDto;
import efub.back.jupjup.domain.post.dto.PostResponseDto;
import efub.back.jupjup.domain.post.exception.PostNotFoundException;
import efub.back.jupjup.domain.post.repository.PostImageRepository;
import efub.back.jupjup.domain.post.repository.PostRepository;
import efub.back.jupjup.domain.post.specification.PostSpecification;
import efub.back.jupjup.domain.postjoin.domain.Postjoin;
import efub.back.jupjup.domain.postjoin.repository.PostjoinRepository;
import efub.back.jupjup.domain.score.repository.ScoreRepository;
Expand Down Expand Up @@ -321,4 +324,30 @@ public ResponseEntity<StatusResponse> getCompletedPosts(Member member) {
List<PostResponseDto> responseDtos = createPostResponseDtos(posts, member, now);
return ResponseEntity.ok(createStatusResponse(responseDtos));
}

@Transactional(readOnly = true)
public ResponseEntity<StatusResponse> getFilteredPosts(PostFilterDto filterDto, Member member) {
Specification<Post> spec = Specification.where(null);

if (filterDto.getAllGender() != null) {
spec = spec.and(PostSpecification.withGender(filterDto.getAllGender(), member.getGender()));
}
if (filterDto.getWithPet() != null) {
spec = spec.and(PostSpecification.withPet(filterDto.getWithPet()));
}
if (filterDto.getDistricts() != null && !filterDto.getDistricts().isEmpty()) {
spec = spec.and(PostSpecification.withDistricts(filterDto.getDistricts()));
}
if (filterDto.getAllAge() != null) {
spec = spec.and(PostSpecification.withAgeRange(filterDto.getAllAge(), member.getAge()));
}
if (filterDto.getExcludeClosedRecruitment() != null) {
spec = spec.and(PostSpecification.excludeClosedRecruitment(filterDto.getExcludeClosedRecruitment()));
}

List<Post> filteredPosts = postRepository.findAll(spec, Sort.by(Sort.Direction.DESC, "createdAt"));
List<PostResponseDto> responseDtos = createPostResponseDtos(filteredPosts, member, LocalDateTime.now());

return ResponseEntity.ok(createStatusResponse(responseDtos));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package efub.back.jupjup.domain.post.specification;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.data.jpa.domain.Specification;

import efub.back.jupjup.domain.member.domain.Gender;
import efub.back.jupjup.domain.post.domain.District;
import efub.back.jupjup.domain.post.domain.Post;
import efub.back.jupjup.domain.post.domain.PostGender;

public class PostSpecification {

public static Specification<Post> withGender(Boolean allGender, Gender userGender) {
return (root, query, criteriaBuilder) -> {
if (Boolean.TRUE.equals(allGender)) {
return null; // 모든 성별 포함
}
// allGender가 false인 경우, 사용자 성별과 ANY만 포함
return criteriaBuilder.or(
criteriaBuilder.equal(root.get("postGender"), PostGender.ANY),
criteriaBuilder.equal(root.get("postGender"), convertGenderToPostGender(userGender))
);
};
}

private static PostGender convertGenderToPostGender(Gender gender) {
switch (gender) {
case FEMALE:
return PostGender.FEMALE;
case MALE:
return PostGender.MALE;
default:
return PostGender.ANY;
}
}

public static Specification<Post> withPet(Boolean withPet) {
return (root, query, criteriaBuilder) ->
withPet == null ? null : criteriaBuilder.equal(root.get("withPet"), withPet);
}

public static Specification<Post> withDistricts(List<District> districts) {
return (root, query, criteriaBuilder) -> {
if (districts == null || districts.isEmpty()) {
return null;
}
return root.get("district").in(districts);
};
}

public static Specification<Post> withAgeRange(Boolean allAge, Integer userAge) {
return (root, query, criteriaBuilder) -> {
if (Boolean.TRUE.equals(allAge)) {
return null; // 모든 연령 포함
}
if (userAge != null) {
return criteriaBuilder.and(
criteriaBuilder.lessThanOrEqualTo(root.get("minAge"), userAge),
criteriaBuilder.greaterThanOrEqualTo(root.get("maxAge"), userAge)
);
}
return null;
};
}

public static Specification<Post> excludeClosedRecruitment(Boolean exclude) {
return (root, query, criteriaBuilder) ->
exclude == null || !exclude ? null :
criteriaBuilder.greaterThan(root.get("dueDate"), LocalDateTime.now());
}
}

0 comments on commit 39fbbfd

Please sign in to comment.