Skip to content

Commit

Permalink
Merge pull request #74 from Team-GAJI/feature/#67-user-studyroom/GAJI-98
Browse files Browse the repository at this point in the history
✨[feature] #67 유저 스터디룸 목록 조회 API
  • Loading branch information
karryred authored Aug 12, 2024
2 parents da2ed64 + fec04d2 commit ccc7810
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 7 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'

implementation 'mysql:mysql-connector-java:8.0.33'

//QueryDSL 의존성 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

tasks.named('test') {
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/gaji/service/config/QueryDSLConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gaji.service.config;

import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class QueryDSLConfig{

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
1 change: 0 additions & 1 deletion src/main/java/gaji/service/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,4 @@ private Info apiInfo() {
.description("GAJI API 명세서입니다.")
.version("1.0.0");
}

}
17 changes: 17 additions & 0 deletions src/main/java/gaji/service/domain/common/annotation/CheckPage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package gaji.service.domain.common.annotation;

import gaji.service.domain.common.validation.PageNumberValidator;
import jakarta.validation.Payload;
import jakarta.validation.Constraint;

import java.lang.annotation.*;

@Documented
@Constraint(validatedBy = PageNumberValidator.class)
@Target( { ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface CheckPage {
String message() default "유효하지 않은 페이지 숫자 입니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package gaji.service.domain.common.validation;

import gaji.service.domain.common.annotation.CheckPage;
import gaji.service.global.exception.code.status.GlobalErrorStatus;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;
import org.springframework.stereotype.Component;

@Component
public class PageNumberValidator implements ConstraintValidator<CheckPage, Integer> {

@Override
public void initialize(CheckPage constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}

@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
boolean isValid = value>=0;

if (!isValid){
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate(GlobalErrorStatus._INTERNAL_PAGE_ERROR.toString())
.addConstraintViolation();
}

return isValid;
}
}
5 changes: 5 additions & 0 deletions src/main/java/gaji/service/domain/enums/RoomTypeEnum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package gaji.service.domain.enums;

public enum RoomTypeEnum {
ONGOING, ENDED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package gaji.service.domain.room.repository;

import com.querydsl.core.Tuple;
import gaji.service.domain.user.entity.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import java.time.LocalDate;

public interface RoomCustomRepository {
public Slice<Tuple> findAllOngoingRoomsByUser(User user, LocalDate cursorDate, Long cursorId, Pageable pageable);
public Slice<Tuple> findAllEndedRoomsByUser(User user, LocalDate cursorDate, Long cursorId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package gaji.service.domain.room.repository;

import com.querydsl.core.Tuple;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import gaji.service.domain.room.entity.QRoom;
import gaji.service.domain.studyMate.QStudyMate;
import gaji.service.domain.user.entity.User;
import lombok.AllArgsConstructor;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;

@Repository
@AllArgsConstructor
public class RoomCustomRepositoryImpl implements RoomCustomRepository {
private final JPAQueryFactory jpaQueryFactory;

private final QRoom room = QRoom.room;
private final QStudyMate studyMate = QStudyMate.studyMate;

public Slice<Tuple> findAllOngoingRoomsByUser(User user, LocalDate cursorDate, Long cursorId, Pageable pageable) {
List<Long> userRoomIds = jpaQueryFactory
.select(studyMate.room.id)
.from(studyMate)
.where(studyMate.user.eq(user))
.fetch();

List<Tuple> ongoingRooms = jpaQueryFactory.select(room.id, room.name, room.description, room.thumbnailUrl, room.studyStartDay)
.from(room)
.where(room.id.in(userRoomIds)
.and(room.studyEndDay.after(getCurrentDay()))
.and(getCursorCondition(cursorDate, cursorId)))
.orderBy(room.studyStartDay.desc(), room.id.asc())
.limit(pageable.getPageSize()+1) // size보다 1개 더 가져와서 다음 페이지 여부 확인
.fetch();

return checkLastPage(pageable, ongoingRooms);
}


public Slice<Tuple> findAllEndedRoomsByUser(User user, LocalDate cursorDate, Long cursorId, Pageable pageable) {
LocalDate now = LocalDate.now();

BooleanExpression cursorCondition = (room.studyStartDay.eq(cursorDate).and(room.id.gt(cursorId)))
.or(room.studyStartDay.lt(cursorDate));

List<Long> userRoomIds = jpaQueryFactory
.select(studyMate.room.id)
.from(studyMate)
.where(studyMate.user.eq(user))
.fetch();

List<Tuple> ongoingRooms = jpaQueryFactory.select(room.id, room.name, room.description, room.thumbnailUrl, room.studyStartDay)
.from(room)
.where(room.id.in(userRoomIds)
.and(room.studyEndDay.before(getCurrentDay()))
.and(getCursorCondition(cursorDate, cursorId)))
.orderBy(room.studyStartDay.desc(), room.id.asc())
.limit(pageable.getPageSize()+1) // size보다 1개 더 가져와서 다음 페이지 여부 확인
.fetch();

return checkLastPage(pageable, ongoingRooms);
}


private Slice<Tuple> checkLastPage(Pageable pageable, List<Tuple> roomList) {
boolean hasNext = false;

if (roomList.size() > pageable.getPageSize()) {
hasNext = true;
roomList.remove(pageable.getPageSize()); // 더 가져왔을 시, 삭제
}
return new SliceImpl<Tuple>(roomList, pageable, hasNext);
}

private BooleanExpression getCursorCondition(LocalDate cursorDate, Long cursorId) {
return (room.studyStartDay.eq(cursorDate).and(room.id.gt(cursorId)))
.or(room.studyStartDay.lt(cursorDate));
}

private LocalDate getCurrentDay() {
return LocalDate.now();
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package gaji.service.domain.user.converter;

import gaji.service.domain.enums.UserActive;
import com.querydsl.core.Tuple;
import gaji.service.domain.room.entity.QRoom;
import gaji.service.domain.user.entity.User;
import gaji.service.domain.user.web.dto.UserResponseDTO;
import org.springframework.data.domain.Slice;

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

public class UserConverter {
public static UserResponseDTO.CancleResultDTO toCancleResultDTO(User user) {
Expand All @@ -19,4 +24,24 @@ public static UserResponseDTO.UpdateNicknameResultDTO toUpdateNicknameResultDTO(
.build();
}

public static UserResponseDTO.GetRoomDTO toGetRoomDTO(Tuple tuple) {
return UserResponseDTO.GetRoomDTO.builder()
.roomId(tuple.get(QRoom.room.id))
.name(tuple.get(QRoom.room.name))
.description(tuple.get(QRoom.room.description))
.thumbnail_url(tuple.get(QRoom.room.thumbnailUrl))
.studyStartDay(tuple.get(QRoom.room.studyStartDay))
.build();
}

public static UserResponseDTO.GetRoomListDTO toGetRoomListDTO(Slice<Tuple> roomList) {
List<UserResponseDTO.GetRoomDTO> getRoomDTOList = roomList.stream()
.map(UserConverter::toGetRoomDTO)
.collect(Collectors.toList());

return UserResponseDTO.GetRoomListDTO.builder()
.roomList(getRoomDTOList)
.hasNext(roomList.hasNext())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
package gaji.service.domain.user.service;


import com.querydsl.core.Tuple;
import gaji.service.domain.enums.RoomTypeEnum;
import gaji.service.domain.user.entity.User;
import org.springframework.data.domain.Slice;

import java.time.LocalDate;

public interface UserQueryService {

boolean existUserById(Long userId);
User findUserById(Long userId);
Slice<Tuple> getUserRoomList(Long userId, LocalDate cursorDate, Long cursorId, RoomTypeEnum type, int size);

}
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package gaji.service.domain.user.service;

import com.querydsl.core.Tuple;
import gaji.service.domain.enums.RoomTypeEnum;
import gaji.service.domain.room.repository.RoomCustomRepository;
import gaji.service.domain.user.code.UserErrorStatus;
import gaji.service.domain.user.entity.User;
import gaji.service.domain.user.repository.UserRepository;
import gaji.service.global.exception.RestApiException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserQueryServiceImpl implements UserQueryService {

private final UserRepository userRepository;
private final RoomCustomRepository roomCustomRepository;

@Override
public boolean existUserById(Long userId) {
Expand All @@ -26,4 +33,26 @@ public User findUserById(Long userId) {
return userRepository.findById(userId)
.orElseThrow(() -> new RestApiException(UserErrorStatus._USER_NOT_FOUND));
}

@Override
public Slice<Tuple> getUserRoomList(Long userId, LocalDate cursorDate, Long cursorId, RoomTypeEnum type, int size) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RestApiException(UserErrorStatus._USER_NOT_FOUND));

cursorDate = cursorDate == null ? LocalDate.now() : cursorDate;
cursorId = cursorId == null ? 0 : cursorId;

PageRequest pageRequest = PageRequest.of(0, size);

Slice<Tuple> roomList;

if(type == RoomTypeEnum.ONGOING) {
roomList = roomCustomRepository.findAllOngoingRoomsByUser(user, cursorDate, cursorId, pageRequest);
}
else{
roomList = roomCustomRepository.findAllEndedRoomsByUser(user, cursorDate, cursorId, pageRequest);
}

return roomList;
}
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package gaji.service.domain.user.web.controller;

import gaji.service.domain.enums.PostTypeEnum;
import gaji.service.domain.post.entity.Post;
import gaji.service.domain.user.code.UserErrorStatus;
import com.querydsl.core.Tuple;
import gaji.service.domain.enums.RoomTypeEnum;
import gaji.service.domain.user.converter.UserConverter;
import gaji.service.domain.user.entity.User;
import gaji.service.domain.user.service.UserCommandService;
import gaji.service.domain.user.service.UserQueryService;
import gaji.service.domain.user.web.dto.UserRequestDTO;
import gaji.service.domain.user.web.dto.UserResponseDTO;
import gaji.service.global.base.BaseResponse;
import gaji.service.global.exception.RestApiException;
import gaji.service.jwt.service.TokenProviderService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.time.LocalDate;

@RestController
@RequestMapping("/api/users")
Expand Down Expand Up @@ -45,5 +44,27 @@ public BaseResponse<UserResponseDTO.UpdateNicknameResultDTO> updateNickname(@Req
User user = userCommandService.updateUserNickname(userIdFromToken, userIdFromPathVariable, request);
return BaseResponse.onSuccess(UserConverter.toUpdateNicknameResultDTO(user));
}

@GetMapping("/rooms")
public BaseResponse<UserResponseDTO.GetRoomListDTO> getMyRoomList(@RequestHeader("Authorization") String authorizationHeader,
@RequestParam(value = "cursorDate",required = false) LocalDate cursorDate,
@RequestParam(value = "cursorId",required = false) Long cursorId,
@RequestParam("type") RoomTypeEnum type,
@RequestParam(defaultValue = "10") int size) {
Long userId = tokenProviderService.getUserIdFromToken(authorizationHeader);
Slice<Tuple> userRoomList = userQueryService.getUserRoomList(userId, cursorDate, cursorId, type, size);
return BaseResponse.onSuccess(UserConverter.toGetRoomListDTO(userRoomList));
}

@GetMapping("/rooms/{userId}")
public BaseResponse<UserResponseDTO.GetRoomListDTO> getUserRoomList(@PathVariable Long userId,
@RequestParam(value = "cursorDate",required = false) LocalDate cursorDate,
@RequestParam(value = "cursorId",required = false) Long cursorId,
@RequestParam("type") RoomTypeEnum type,
@RequestParam(defaultValue = "10") int size) {
Slice<Tuple> userRoomList = userQueryService.getUserRoomList(userId, cursorDate, cursorId, type, size);
return BaseResponse.onSuccess(UserConverter.toGetRoomListDTO(userRoomList));
}

}

Loading

0 comments on commit ccc7810

Please sign in to comment.