Skip to content

Commit

Permalink
[Feat/#237] Implement Random Posts and Infinite Scrolling API for Sir…
Browse files Browse the repository at this point in the history
…en (#249)

* [Feat/#237] Implement Random Unresolved Posts API

* [Feat/#237] Implement Infinite Scrolling for Siren Main Page API

* [Feat/#237] Change Siren Page Size

* [Feat/#237] Refactor Package Structure

* [Feat/#237] Update Variable Name
  • Loading branch information
ahnsugyeong authored May 8, 2024
1 parent fd56869 commit c30688b
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ public interface SirenQueryService {

Siren getSirenByBoardId(Long boardId);

List<Siren> getRandomUnresolvedSirenList();

}
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,9 @@ public Siren getSirenByBoardId(Long boardId) {
.orElseThrow(() -> new SirenHandler(ErrorStatus.BOARD_NOT_FOUND));
}

@Override
public List<Siren> getRandomUnresolvedSirenList() {
return sirenRepository.findRandomUnresolvedSirens();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@

import com.example.waggle.domain.board.persistence.entity.Siren;
import com.example.waggle.domain.board.presentation.dto.siren.SirenFilterParam;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

public interface SirenQueryRepository {

Page<Siren> findSirensByFilter(SirenFilterParam filterParam, Pageable pageable);

List<Siren> findRandomUnresolvedSirens();

}
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
package com.example.waggle.domain.board.persistence.dao.siren.querydsl;

import static com.example.waggle.domain.board.persistence.entity.QSiren.siren;
import static com.example.waggle.domain.recommend.persistence.entity.QRecommend.recommend;

import com.example.waggle.domain.board.persistence.entity.ResolutionStatus;
import com.example.waggle.domain.board.persistence.entity.Siren;
import com.example.waggle.domain.board.presentation.dto.siren.SirenFilterParam;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

import java.util.List;

import static com.example.waggle.domain.board.persistence.entity.QSiren.siren;
import static com.example.waggle.domain.recommend.persistence.entity.QRecommend.recommend;

@Repository
@RequiredArgsConstructor
public class SirenQueryRepositoryImpl implements SirenQueryRepository {
Expand All @@ -39,11 +40,17 @@ public Page<Siren> findSirensByFilter(SirenFilterParam filterParam, Pageable pag
return new PageImpl<>(sirenList, pageable, count);
}

@Override
public List<Siren> findRandomUnresolvedSirens() {
return query.selectFrom(siren)
.where(siren.status.eq(ResolutionStatus.UNRESOLVED))
.orderBy(Expressions.numberTemplate(Double.class, "function('rand')").asc())
.limit(4)
.fetch();
}

private OrderSpecifier[] createOrderFilter(SirenFilterParam filterParam) {
switch (filterParam) {
case latest -> {
return new OrderSpecifier[]{siren.createdDate.desc()};
}
case recommend -> {
return new OrderSpecifier[]{
recommend.count().desc(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.example.waggle.domain.board.presentation.controller;

import static com.example.waggle.global.util.PageUtil.SIREN_SIZE;

import com.example.waggle.domain.board.application.siren.SirenCacheService;
import com.example.waggle.domain.board.application.siren.SirenCommandService;
import com.example.waggle.domain.board.application.siren.SirenQueryService;
Expand All @@ -8,20 +10,22 @@
import com.example.waggle.domain.board.presentation.converter.SirenConverter;
import com.example.waggle.domain.board.presentation.dto.siren.SirenFilterParam;
import com.example.waggle.domain.board.presentation.dto.siren.SirenRequest;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.RepresentativeSirenDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenSummaryListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenDetailDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenPagedSummaryListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenSummaryDto;
import com.example.waggle.domain.member.persistence.entity.Member;
import com.example.waggle.domain.recommend.application.query.RecommendQueryService;
import com.example.waggle.exception.payload.code.ErrorStatus;
import com.example.waggle.exception.payload.dto.ApiResponseDto;
import com.example.waggle.global.annotation.api.ApiErrorCodeExample;
import com.example.waggle.global.annotation.auth.AuthUser;
import com.example.waggle.exception.payload.dto.ApiResponseDto;
import com.example.waggle.exception.payload.code.ErrorStatus;
import com.example.waggle.global.util.MediaUtil;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
Expand All @@ -30,12 +34,15 @@
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

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

import static com.example.waggle.global.util.PageUtil.SIREN_SIZE;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RequiredArgsConstructor
Expand Down Expand Up @@ -92,17 +99,28 @@ public ApiResponseDto<Long> convertStatus(@PathVariable("sirenId") Long sirenId,
return ApiResponseDto.onSuccess(sirenId);
}

@Operation(summary = "사이렌 삭제 🔑", description = "특정 사이렌을 삭제합니다.게시글과 관련된 댓글, 대댓글, 미디어 등을 모두 삭제합니다.")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@DeleteMapping("/{sirenId}")
public ApiResponseDto<Boolean> deleteSiren(@PathVariable("sirenId") Long sirenId,
@AuthUser Member member) {
sirenCommandService.deleteSiren(sirenId, member);
return ApiResponseDto.onSuccess(Boolean.TRUE);
}


@Operation(summary = "전체 사이렌 목록 조회", description = "전체 사이렌 목록을 조회합니다.")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@GetMapping
public ApiResponseDto<SirenListDto> getAllSiren(
public ApiResponseDto<SirenPagedSummaryListDto> getAllSiren(
@RequestParam(name = "currentPage", defaultValue = "0") int currentPage) {
Pageable pageable = PageRequest.of(currentPage, SIREN_SIZE, latestSorting);
Pageable pageable = PageRequest.of(currentPage, 8, latestSorting);
Page<Siren> pagedSirenList = sirenQueryService.getPagedSirenList(pageable);
SirenListDto listDto = SirenConverter.toSirenListDto(pagedSirenList);
SirenPagedSummaryListDto listDto = SirenConverter.toSirenPageDto(pagedSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
}
Expand All @@ -112,12 +130,12 @@ public ApiResponseDto<SirenListDto> getAllSiren(
ErrorStatus._INTERNAL_SERVER_ERROR
})
@GetMapping("/filter")
public ApiResponseDto<SirenListDto> getSirensByFilter(
public ApiResponseDto<SirenPagedSummaryListDto> getSirensByFilter(
@RequestParam(name = "filterParam") SirenFilterParam filterParam,
@RequestParam(name = "currentPage", defaultValue = "0") int currentPage) {
Pageable pageable = PageRequest.of(currentPage, SIREN_SIZE);
Page<Siren> pagedSirenList = sirenQueryService.getPagedSirenListByFilter(filterParam, pageable);
SirenListDto listDto = SirenConverter.toSirenListDto(pagedSirenList);
SirenPagedSummaryListDto listDto = SirenConverter.toSirenPageDto(pagedSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
}
Expand All @@ -127,22 +145,22 @@ public ApiResponseDto<SirenListDto> getSirensByFilter(
ErrorStatus._INTERNAL_SERVER_ERROR
})
@GetMapping("/category")
public ApiResponseDto<SirenListDto> getSirensByCategory(
public ApiResponseDto<SirenPagedSummaryListDto> getSirensByCategory(
@RequestParam(name = "category") SirenCategory category,
@RequestParam(name = "currentPage", defaultValue = "0") int currentPage) {
Pageable pageable = PageRequest.of(currentPage, SIREN_SIZE, latestSorting);
Page<Siren> pagedSirenList = sirenQueryService.getPagedSirenListByCategory(category, pageable);
SirenListDto listDto = SirenConverter.toSirenListDto(pagedSirenList);
SirenPagedSummaryListDto listDto = SirenConverter.toSirenPageDto(pagedSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
}

@Operation(summary = "대표 사이렌 조회", description = "대표 사이렌을 조회합니다. 미해결 인기순으로 정렬하고, 상단 3개의 사이렌을 반환합니다.")
@ApiErrorCodeExample({ErrorStatus._INTERNAL_SERVER_ERROR})
@GetMapping("/representative")
public ApiResponseDto<RepresentativeSirenDto> getRepresentativeSirenList() {
public ApiResponseDto<SirenSummaryListDto> getRepresentativeSirenList() {
List<Siren> representativeSirenList = sirenQueryService.getRepresentativeSirenList();
RepresentativeSirenDto listDto = SirenConverter.toRepresentativeSirenDto(
SirenSummaryListDto listDto = SirenConverter.toSirenSummaryListDto(
representativeSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
Expand All @@ -153,11 +171,11 @@ public ApiResponseDto<RepresentativeSirenDto> getRepresentativeSirenList() {
ErrorStatus._INTERNAL_SERVER_ERROR
})
@GetMapping("/member/{userUrl}")
public ApiResponseDto<SirenListDto> getSirenListByUsername(@PathVariable("userUrl") String userUrl,
@RequestParam(name = "currentPage", defaultValue = "0") int currentPage) {
public ApiResponseDto<SirenPagedSummaryListDto> getSirenListByUsername(@PathVariable("userUrl") String userUrl,
@RequestParam(name = "currentPage", defaultValue = "0") int currentPage) {
Pageable pageable = PageRequest.of(currentPage, SIREN_SIZE, latestSorting);
Page<Siren> pagedSirenList = sirenQueryService.getPagedSirenListByUserUrl(userUrl, pageable);
SirenListDto listDto = SirenConverter.toSirenListDto(pagedSirenList);
SirenPagedSummaryListDto listDto = SirenConverter.toSirenPageDto(pagedSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
}
Expand All @@ -175,17 +193,18 @@ public ApiResponseDto<SirenDetailDto> getSirenByBoardId(@PathVariable("sirenId")
return ApiResponseDto.onSuccess(detailDto);
}

@Operation(summary = "사이렌 삭제 🔑", description = "특정 사이렌을 삭제합니다.게시글과 관련된 댓글, 대댓글, 미디어 등을 모두 삭제합니다.")
@ApiErrorCodeExample({
ErrorStatus._INTERNAL_SERVER_ERROR
})
@DeleteMapping("/{sirenId}")
public ApiResponseDto<Boolean> deleteSiren(@PathVariable("sirenId") Long sirenId,
@AuthUser Member member) {
sirenCommandService.deleteSiren(sirenId, member);
return ApiResponseDto.onSuccess(Boolean.TRUE);
@Operation(summary = "랜덤 사이렌 조회", description = "임의의 사이렌을 조회합니다. 미해결 사이렌 중 임의로 3개의 사이렌을 반환합니다.")
@ApiErrorCodeExample({ErrorStatus._INTERNAL_SERVER_ERROR})
@GetMapping("/random")
public ApiResponseDto<SirenSummaryListDto> getUnresolvedRandomSirenList() {
List<Siren> randomUnresolvedSirenList = sirenQueryService.getRandomUnresolvedSirenList();
SirenSummaryListDto listDto = SirenConverter.toSirenSummaryListDto(
randomUnresolvedSirenList);
setRecommendCntInList(listDto.getSirenList());
return ApiResponseDto.onSuccess(listDto);
}


private void setRecommendCntInList(List<SirenSummaryDto> sirenList) {
sirenList
.forEach(siren ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.PageRequest;
Expand Down Expand Up @@ -47,7 +48,7 @@ public class StoryApiController {
ErrorStatus._INTERNAL_SERVER_ERROR
})
@PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ApiResponseDto<Long> createStory(@RequestPart("createStoryRequest") StoryRequest createStoryRequest,
public ApiResponseDto<Long> createStory(@RequestPart("createStoryRequest") @Valid StoryRequest createStoryRequest,
@AuthUser Member member) {
List<String> removedPrefixMedia = createStoryRequest.getMediaList().stream()
.map(media -> MediaUtil.removePrefix(media)).collect(Collectors.toList());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
package com.example.waggle.domain.board.presentation.converter;

import com.example.waggle.domain.board.persistence.entity.Siren;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.RepresentativeSirenDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenSummaryListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenDetailDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenPagedSummaryListDto;
import com.example.waggle.domain.board.presentation.dto.siren.SirenResponse.SirenSummaryDto;
import com.example.waggle.global.util.MediaUtil;
import com.example.waggle.domain.member.presentation.converter.MemberConverter;
import com.example.waggle.global.util.PageUtil;
import org.springframework.data.domain.Page;

import java.util.List;
Expand All @@ -26,14 +27,12 @@ public static SirenSummaryDto toSirenSummaryDto(Siren siren) {
.build();
}

public static SirenListDto toSirenListDto(Page<Siren> pagedSiren) {
public static SirenPagedSummaryListDto toSirenPageDto(Page<Siren> pagedSiren) {
List<SirenSummaryDto> collect = pagedSiren.stream()
.map(SirenConverter::toSirenSummaryDto).collect(Collectors.toList());
return SirenListDto.builder()
return SirenPagedSummaryListDto.builder()
.sirenList(collect)
.isFirst(pagedSiren.isFirst())
.isLast(pagedSiren.isLast())
.sirenCount(pagedSiren.getTotalElements())
.nextPageParam(PageUtil.countNextPage(pagedSiren))
.build();
}

Expand All @@ -57,10 +56,10 @@ public static SirenDetailDto toSirenDetailDto(Siren siren) {
.build();
}

public static RepresentativeSirenDto toRepresentativeSirenDto(List<Siren> sirenList) {
public static SirenSummaryListDto toSirenSummaryListDto(List<Siren> sirenList) {
List<SirenSummaryDto> collect = sirenList.stream()
.map(SirenConverter::toSirenSummaryDto).collect(Collectors.toList());
return RepresentativeSirenDto.builder()
return SirenSummaryListDto.builder()
.sirenList(collect)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,17 @@ public static class SirenDetailDto {
@NoArgsConstructor
@AllArgsConstructor
@Schema
public static class SirenListDto {
public static class SirenPagedSummaryListDto {
private List<SirenSummaryDto> sirenList;
private long sirenCount;
private Boolean isFirst;
private Boolean isLast;
private int nextPageParam;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema
public static class RepresentativeSirenDto {
public static class SirenSummaryListDto {
private List<SirenSummaryDto> sirenList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ public class StoryRequest {
@Size(max = 500)
private String content;
private List<String> hashtagList;

@Size(min = 1, message = "미디어는 최소 1개 이상 필요합니다.")
private List<String> mediaList;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.example.waggle.global.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.lang.reflect.Type;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter;
import org.springframework.stereotype.Component;

@Component
public class MultipartJackson2HttpMessageConverter extends AbstractJackson2HttpMessageConverter {

/**
* "Content-Type: multipart/form-data" 헤더를 지원하는 HTTP 요청 변환기
*/
public MultipartJackson2HttpMessageConverter(ObjectMapper objectMapper) {
super(objectMapper, MediaType.APPLICATION_OCTET_STREAM);
}

@Override
public boolean canWrite(Class<?> clazz, MediaType mediaType) {
return false;
}

@Override
public boolean canWrite(Type type, Class<?> clazz, MediaType mediaType) {
return false;
}

@Override
protected boolean canWrite(MediaType mediaType) {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.example.waggle.global.config.spring;
package com.example.waggle.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand Down
Loading

0 comments on commit c30688b

Please sign in to comment.