diff --git a/build.gradle b/build.gradle index a83743d..8f054dd 100644 --- a/build.gradle +++ b/build.gradle @@ -49,8 +49,18 @@ dependencies { //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + //Querydsl 추가 + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" } tasks.named('test') { useJUnitPlatform() } + +clean { + delete file('src/main/generated') +} diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/dto/response/ReadMainActivityRes.java b/src/main/java/com/meetup/teame/backend/domain/activity/dto/response/ReadMainActivityRes.java index 6e412da..73263c7 100644 --- a/src/main/java/com/meetup/teame/backend/domain/activity/dto/response/ReadMainActivityRes.java +++ b/src/main/java/com/meetup/teame/backend/domain/activity/dto/response/ReadMainActivityRes.java @@ -2,10 +2,9 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.meetup.teame.backend.domain.activity.entity.Activity; -import com.meetup.teame.backend.domain.activity.entity.Personality; +import com.meetup.teame.backend.domain.personality.Personality; import lombok.*; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/entity/Activity.java b/src/main/java/com/meetup/teame/backend/domain/activity/entity/Activity.java index 4a21bc1..d116bfc 100644 --- a/src/main/java/com/meetup/teame/backend/domain/activity/entity/Activity.java +++ b/src/main/java/com/meetup/teame/backend/domain/activity/entity/Activity.java @@ -1,16 +1,13 @@ package com.meetup.teame.backend.domain.activity.entity; import com.meetup.teame.backend.domain.like.entity.ActivityLike; -import com.meetup.teame.backend.domain.user.entity.User; +import com.meetup.teame.backend.domain.personality.Personality; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.Comment; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashSet; import java.util.List; -import java.util.Set; @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/entity/Personality.java b/src/main/java/com/meetup/teame/backend/domain/activity/entity/Personality.java deleted file mode 100644 index 9ff3bd7..0000000 --- a/src/main/java/com/meetup/teame/backend/domain/activity/entity/Personality.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.meetup.teame.backend.domain.activity.entity; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum Personality { - WINDLESS("잔잔한"), - ; - - private final String description; -} diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/repository/ActivityRepository.java b/src/main/java/com/meetup/teame/backend/domain/activity/repository/ActivityRepository.java index 5d1e5d0..338c761 100644 --- a/src/main/java/com/meetup/teame/backend/domain/activity/repository/ActivityRepository.java +++ b/src/main/java/com/meetup/teame/backend/domain/activity/repository/ActivityRepository.java @@ -1,7 +1,8 @@ package com.meetup.teame.backend.domain.activity.repository; import com.meetup.teame.backend.domain.activity.entity.Activity; +import com.meetup.teame.backend.domain.activity.repository.custom.ActivityRepositoryCustom; import org.springframework.data.jpa.repository.JpaRepository; -public interface ActivityRepository extends JpaRepository { +public interface ActivityRepository extends JpaRepository, ActivityRepositoryCustom { } diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryCustom.java b/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryCustom.java new file mode 100644 index 0000000..cb3a292 --- /dev/null +++ b/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryCustom.java @@ -0,0 +1,10 @@ +package com.meetup.teame.backend.domain.activity.repository.custom; + +import com.meetup.teame.backend.domain.activity.entity.Activity; +import com.meetup.teame.backend.domain.user.entity.User; + +import java.util.List; + +public interface ActivityRepositoryCustom { + List findActivitiesForUser(User user); +} diff --git a/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryImpl.java b/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryImpl.java new file mode 100644 index 0000000..9b0ff06 --- /dev/null +++ b/src/main/java/com/meetup/teame/backend/domain/activity/repository/custom/ActivityRepositoryImpl.java @@ -0,0 +1,25 @@ +package com.meetup.teame.backend.domain.activity.repository.custom; + +import com.meetup.teame.backend.domain.activity.entity.Activity; +import com.meetup.teame.backend.domain.activity.entity.QActivity; +import com.meetup.teame.backend.domain.user.entity.User; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import static com.meetup.teame.backend.domain.activity.entity.QActivity.activity; + +@RequiredArgsConstructor +public class ActivityRepositoryImpl implements ActivityRepositoryCustom { + private final JPAQueryFactory jpaQueryFactory; + + @Override + public List findActivitiesForUser(User user) { + return jpaQueryFactory + .select(activity) + .from(activity) + .where(activity.personalities.any().in(user.getPersonalities())) + .fetch(); + } +} diff --git a/src/main/java/com/meetup/teame/backend/domain/auth/config/SecurityConfig.java b/src/main/java/com/meetup/teame/backend/domain/auth/config/SecurityConfig.java index 9a08126..62567a0 100644 --- a/src/main/java/com/meetup/teame/backend/domain/auth/config/SecurityConfig.java +++ b/src/main/java/com/meetup/teame/backend/domain/auth/config/SecurityConfig.java @@ -18,7 +18,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .anyRequest().permitAll()) .cors(c -> c.configurationSource(request -> { CorsConfiguration config = new CorsConfiguration(); - config.setAllowedOrigins(List.of("http://localhost:3000")); + config.setAllowedOrigins(List.of("http://localhost:3000", "https://api.yeongjin.site", "https://ddoba.vercel.app")); config.setAllowedMethods(List.of("*")); config.setAllowCredentials(true); config.setAllowedHeaders(List.of("*")); diff --git a/src/main/java/com/meetup/teame/backend/domain/personality/Personality.java b/src/main/java/com/meetup/teame/backend/domain/personality/Personality.java new file mode 100644 index 0000000..d8baed0 --- /dev/null +++ b/src/main/java/com/meetup/teame/backend/domain/personality/Personality.java @@ -0,0 +1,30 @@ +package com.meetup.teame.backend.domain.personality; + +import com.meetup.teame.backend.global.exception.CustomException; +import com.meetup.teame.backend.global.exception.ExceptionContent; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@Getter +@RequiredArgsConstructor +public enum Personality { + WINDLESS("잔잔한"), + ACTIVE("활발한"), + PEACEFUL("평화로운"), + NATURE_FRIENDLY("자연친화적인"), + CREATIVE("창의적인"), + ACADEMIC("학문적인"), + ARTISTIC("예술적인"), + LEARNABLE("배울 수 있는"); + + private final String description; + + public static Personality des2enum(String description) { + return Arrays.stream(Personality.values()) + .filter(personality -> personality.getDescription().equals(description)) + .findFirst() + .orElseThrow(() -> new CustomException(ExceptionContent.NOT_FOUND_PERSONALITY)); + } +} diff --git a/src/main/java/com/meetup/teame/backend/domain/user/controller/UserController.java b/src/main/java/com/meetup/teame/backend/domain/user/controller/UserController.java index 03c3383..1456623 100644 --- a/src/main/java/com/meetup/teame/backend/domain/user/controller/UserController.java +++ b/src/main/java/com/meetup/teame/backend/domain/user/controller/UserController.java @@ -1,14 +1,14 @@ package com.meetup.teame.backend.domain.user.controller; +import com.meetup.teame.backend.domain.user.dto.request.OnboardingReq; import com.meetup.teame.backend.domain.user.dto.response.ReadMainRes; import com.meetup.teame.backend.domain.user.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequiredArgsConstructor @@ -19,9 +19,9 @@ public class UserController { @Operation(summary = "메인 페이지 조회", description = """ 현재 로그인한 유저의 메인 페이지를 조회합니다. - + 아직 로그인이 없어서 임시로 고정된 더미 유저의 데이터를 전달하는 식으로 구현되어 있습니다. - + 추후 로그인 적용 시에는 jwt토큰도 같이 전달해서 요청해주셔야 합니다. """) @GetMapping("/main") @@ -29,4 +29,22 @@ public ResponseEntity readMainPage() { return ResponseEntity .ok(userService.readMainPage()); } + + @Operation(summary = "온보딩 정보 등록", description = """ + 유저의 온보딩 정보를 등록합니다. + + 아직 로그인이 없어서 임시로 고정된 더미 유저의 온보딩 정보를 등록하는 식으로 구현되어 있습니다. + + 추후 로그인 적용 시에는 jwt토큰도 같이 전달해서 요청해주셔야 합니다. + + 현재 온보딩 정보로 입력 가능한 성격 유형 + ("잔잔한", "활발한", "평화로운", "자연친화적인", "창의적인", "학문적인", "예술적인", "배울 수 있는") + 위 유형들 중 최소 1개 이상을 선택해서 Request Body에 담아 전송해주세요. + """) + @PatchMapping("/onboarding") + public ResponseEntity setUserPersonality(@RequestBody @Valid OnboardingReq onboardingReq){ + userService.setUserPersonality(onboardingReq); + return ResponseEntity + .ok().build(); + } } diff --git a/src/main/java/com/meetup/teame/backend/domain/user/dto/request/OnboardingReq.java b/src/main/java/com/meetup/teame/backend/domain/user/dto/request/OnboardingReq.java new file mode 100644 index 0000000..037455f --- /dev/null +++ b/src/main/java/com/meetup/teame/backend/domain/user/dto/request/OnboardingReq.java @@ -0,0 +1,13 @@ +package com.meetup.teame.backend.domain.user.dto.request; + +import jakarta.validation.constraints.Size; +import lombok.*; + +import java.util.List; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +public class OnboardingReq { + @Size(min = 1, message = "at least one personality must be selected") + private List personalities; +} diff --git a/src/main/java/com/meetup/teame/backend/domain/user/entity/User.java b/src/main/java/com/meetup/teame/backend/domain/user/entity/User.java index 8f20fc8..a901f61 100644 --- a/src/main/java/com/meetup/teame/backend/domain/user/entity/User.java +++ b/src/main/java/com/meetup/teame/backend/domain/user/entity/User.java @@ -1,6 +1,6 @@ package com.meetup.teame.backend.domain.user.entity; -import com.meetup.teame.backend.domain.activity.entity.Activity; +import com.meetup.teame.backend.domain.personality.Personality; import com.meetup.teame.backend.domain.experience.entity.Experience; import com.meetup.teame.backend.domain.like.entity.ActivityLike; import jakarta.persistence.*; @@ -45,4 +45,12 @@ public class User { @OneToMany(mappedBy = "user", cascade = CascadeType.ALL) private List activityLikes; + + @ElementCollection + @Enumerated(EnumType.STRING) + private List personalities; + + public void setPersonalities(List personalities) { + this.personalities = personalities; + } } diff --git a/src/main/java/com/meetup/teame/backend/domain/user/service/UserService.java b/src/main/java/com/meetup/teame/backend/domain/user/service/UserService.java index 215ac06..47b414d 100644 --- a/src/main/java/com/meetup/teame/backend/domain/user/service/UserService.java +++ b/src/main/java/com/meetup/teame/backend/domain/user/service/UserService.java @@ -2,12 +2,19 @@ import com.meetup.teame.backend.domain.activity.repository.ActivityRepository; import com.meetup.teame.backend.domain.experience.repository.ExperienceRepository; +import com.meetup.teame.backend.domain.personality.Personality; +import com.meetup.teame.backend.domain.user.dto.request.OnboardingReq; import com.meetup.teame.backend.domain.user.dto.response.ReadMainRes; +import com.meetup.teame.backend.domain.user.entity.User; import com.meetup.teame.backend.domain.user.repository.UserRepository; +import com.meetup.teame.backend.global.exception.CustomException; +import com.meetup.teame.backend.global.exception.ExceptionContent; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.util.List; + @Service @RequiredArgsConstructor @Transactional(readOnly = true) @@ -17,10 +24,24 @@ public class UserService { private final ExperienceRepository experienceRepository; public ReadMainRes readMainPage() { + //todo 현재는 더미 유저지만 추후에는 SecurityContextHolder 정보를 조회해서 유저 정보를 가져와야 함 + User user = userRepository.findById(5L) + .orElseThrow(() -> new CustomException(ExceptionContent.NOT_FOUND_USER)); return ReadMainRes.of( - activityRepository.findAll(), + activityRepository.findActivitiesForUser(user), experienceRepository.findAll(), 2500 ); } + + @Transactional + public void setUserPersonality(OnboardingReq onboardingReq) { + //todo 현재는 더미 유저지만 추후에는 SecurityContextHolder 정보를 조회해서 유저 정보를 가져와야 함 + User user = userRepository.findById(5L) + .orElseThrow(() -> new CustomException(ExceptionContent.NOT_FOUND_USER)); + List personalities = onboardingReq.getPersonalities().stream() + .map(Personality::des2enum) + .toList(); + user.setPersonalities(personalities); + } } diff --git a/src/main/java/com/meetup/teame/backend/global/config/QueryDslConfig.java b/src/main/java/com/meetup/teame/backend/global/config/QueryDslConfig.java new file mode 100644 index 0000000..fb43901 --- /dev/null +++ b/src/main/java/com/meetup/teame/backend/global/config/QueryDslConfig.java @@ -0,0 +1,18 @@ +package com.meetup.teame.backend.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + @Autowired + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/meetup/teame/backend/global/exception/ExceptionContent.java b/src/main/java/com/meetup/teame/backend/global/exception/ExceptionContent.java index 481e13d..f63bc39 100644 --- a/src/main/java/com/meetup/teame/backend/global/exception/ExceptionContent.java +++ b/src/main/java/com/meetup/teame/backend/global/exception/ExceptionContent.java @@ -19,6 +19,7 @@ public enum ExceptionContent { BAD_REQUEST_SIGNUP(BAD_REQUEST, "회원가입에 실패했습니다. 이미 가입한 이메일입니다."), NOT_FOUND_USER(NOT_FOUND, "존재하지 않는 사용자입니다."), + NOT_FOUND_PERSONALITY(NOT_FOUND, "존재하지 않는 성격입니다."), ; private final HttpStatus httpStatus; diff --git a/src/test/java/com/meetup/teame/backend/domain/user/controller/UserControllerTest.java b/src/test/java/com/meetup/teame/backend/domain/user/controller/UserControllerTest.java index 470713b..961e706 100644 --- a/src/test/java/com/meetup/teame/backend/domain/user/controller/UserControllerTest.java +++ b/src/test/java/com/meetup/teame/backend/domain/user/controller/UserControllerTest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.meetup.teame.backend.domain.activity.entity.Activity; -import com.meetup.teame.backend.domain.activity.entity.Personality; +import com.meetup.teame.backend.domain.personality.Personality; import com.meetup.teame.backend.domain.experience.entity.Experience; import com.meetup.teame.backend.domain.experience.entity.ExperienceType; import com.meetup.teame.backend.domain.user.dto.response.ReadMainRes; @@ -17,14 +17,11 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.springframework.test.web.servlet.result.MockMvcResultMatchers; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import java.time.LocalDateTime; import java.util.List; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;