Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ public interface ReadPostRepository extends JpaRepository<ReadPost, Long> {

@Query("""
SELECT rp FROM ReadPost rp
JOIN FETCH rp.post
WHERE rp.user = :user
JOIN FETCH rp.post
WHERE rp.user.id = :userId
AND (rp.readDurationSeconds IS NULL OR rp.readDurationSeconds > 10)
ORDER BY rp.readAt DESC
""")
List<ReadPost> findRecentReadPostsByUserWithMinDuration(@Param("user") User user, Pageable pageable);
List<ReadPost> findRecentReadPostsByUserIdWithMinDuration(@Param("userId") Long userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,6 @@ List<BookmarkDto> findBookmarksWithCursor(

Optional<ScrabPost> findByUserAndPost(User user, Post post);

@Query("SELECT sp FROM ScrabPost sp JOIN FETCH sp.post WHERE sp.user = :user ORDER BY sp.scrappedAt DESC")
List<ScrabPost> findRecentScrapPostsByUser(@Param("user") User user, Pageable pageable);
@Query("SELECT sp FROM ScrabPost sp JOIN FETCH sp.post WHERE sp.user.id = :userId ORDER BY sp.scrappedAt DESC")
List<ScrabPost> findRecentScrapPostsByUserId(@Param("userId") Long userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.techfork.domain.activity.repository;

import com.techfork.domain.activity.entity.SearchHistory;
import com.techfork.domain.user.entity.User;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -11,6 +10,6 @@

public interface SearchHistoryRepository extends JpaRepository<SearchHistory, Long> {

@Query("SELECT sh FROM SearchHistory sh WHERE sh.user = :user ORDER BY sh.searchedAt DESC")
List<SearchHistory> findRecentSearchHistoriesByUser(@Param("user") User user, Pageable pageable);
@Query("SELECT sh FROM SearchHistory sh WHERE sh.user.id = :userId ORDER BY sh.searchedAt DESC")
List<SearchHistory> findRecentSearchHistoriesByUserId(@Param("userId") Long userId, Pageable pageable);
}
2 changes: 2 additions & 0 deletions src/main/java/com/techfork/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.BatchSize;

import java.time.LocalDateTime;
import java.util.ArrayList;
Expand Down Expand Up @@ -57,6 +58,7 @@ public class Post extends BaseEntity {
private TechBlog techBlog;

@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
@BatchSize(size = 100)
private List<PostKeyword> keywords = new ArrayList<>();

@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.techfork.domain.post.repository;

import com.techfork.domain.post.entity.Post;
import com.techfork.domain.post.entity.PostKeyword;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -9,11 +8,6 @@
import java.util.List;

public interface PostKeywordRepository extends JpaRepository<PostKeyword, Long> {

List<PostKeyword> findByPost(Post post);

void deleteByPost(Post post);

@Query("SELECT pk FROM PostKeyword pk WHERE pk.post.id IN :postIds")
List<PostKeyword> findByPostIdIn(@Param("postIds") List<Long> postIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public interface PostRepository extends JpaRepository<Post, Long> {
Set<String> findExistingUrls(@Param("urls") List<String> urls);

@Query("""
SELECT p FROM Post p
SELECT DISTINCT p FROM Post p
LEFT JOIN FETCH p.keywords
WHERE p.summary IS NULL OR p.summary = ''
""")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ private List<MmrCandidate> searchCandidatesWithCustomReadHistory(
*/
private List<MmrCandidate> searchCandidates(float[] userProfileVector, User user) throws IOException {
// 이미 읽은 글 ID 목록
Set<Long> readPostIds = readPostRepository.findRecentReadPostsByUserWithMinDuration(user, PageRequest.of(0, 1000))
Set<Long> readPostIds = readPostRepository.findRecentReadPostsByUserIdWithMinDuration(user.getId(), PageRequest.of(0, 1000))
.stream()
.map(readPost -> readPost.getPost().getId())
.collect(Collectors.toSet());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.techfork.domain.user.controller;

import com.techfork.domain.user.dto.InterestListResponse;
import com.techfork.domain.user.dto.SaveInterestRequest;
import com.techfork.domain.user.service.InterestCommandService;
import com.techfork.domain.user.dto.OnboardingRequest;
import com.techfork.domain.user.service.InterestQueryService;
import com.techfork.domain.user.service.UserCommandService;
import com.techfork.global.common.code.SuccessCode;
import com.techfork.global.response.BaseResponse;
import io.swagger.v3.oas.annotations.Operation;
Expand All @@ -22,7 +22,7 @@
public class OnboardingController {

private final InterestQueryService interestQueryService;
private final InterestCommandService interestCommandService;
private final UserCommandService userCommandService;

@Operation(
summary = "관심사 목록 조회",
Expand All @@ -35,17 +35,15 @@ public ResponseEntity<BaseResponse<InterestListResponse>> getInterests() {
}

@Operation(
summary = "내 관심사 저장",
description = "온보딩 시 사용자가 선택한 관심사를 저장합니다. 카테고리별로 세부 키워드를 선택할 수 있습니다."
summary = "내 정보 및 관심사 저장",
description = "온보딩 시 사용자의 정보와 선택한 관심사를 저장합니다. 카테고리별로 세부 키워드를 선택할 수 있습니다."
)
@PostMapping("/interests")
public ResponseEntity<BaseResponse<Void>> saveInterests(
@Valid @RequestBody SaveInterestRequest request
@PostMapping("/complete")
public ResponseEntity<BaseResponse<Void>> completeOnboarding(
@RequestHeader(value = "X-User-Id", required = false, defaultValue = "1") Long userId,
@Valid @RequestBody OnboardingRequest request
) {
// TODO: userId Auth 인증 기반으로 추출
Long userId = 1L;

interestCommandService.saveUserInterests(userId, request);
userCommandService.completeOnboarding(userId, request);
return BaseResponse.of(SuccessCode.CREATED);
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/techfork/domain/user/dto/OnboardingRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.techfork.domain.user.dto;

import jakarta.validation.constraints.*;

import java.util.List;

public record OnboardingRequest(
@NotBlank(message = "닉네임은 필수입니다.")
@Size(min = 2, max = 20, message = "닉네임은 2-20자여야 합니다.")
String nickname,

@NotBlank(message = "이메일은 필수입니다.")
@Email(message = "올바른 이메일 형식이 아닙니다.")
String email,

@Size(max = 100, message = "한줄소개는 100자 이하여야 합니다.")
String description,

@NotNull(message = "관심사 목록은 필수입니다.")
@NotEmpty(message = "관심사를 최소 1개 이상 선택해주세요.")
List<UserInterestDto> interests
) {
}
41 changes: 32 additions & 9 deletions src/main/java/com/techfork/domain/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,48 @@
package com.techfork.domain.user.entity;

import com.techfork.global.common.BaseTimeEntity;
import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.PersistenceCreator;

import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "users")
@Getter
@NoArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User extends BaseTimeEntity {

@OneToMany
@JoinColumn(name = "user_id")
private List<UserInterestCategory> interestCategories;
private String nickName;

@Column(unique = true)
private String email;

private String description;

@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserInterestCategory> interestCategories = new ArrayList<>();

@PersistenceCreator
@Builder
private User(String nickName, String email, String description) {
this.nickName = nickName;
this.email = email;
this.description = description;
}

public static User create() {
return User.builder()
.build();
}

public void updateUser(String nickName, String email, String description) {
this.nickName = nickName;
this.email = email;
this.description = description;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.techfork.domain.user.repository;

import com.techfork.domain.user.entity.User;
import com.techfork.domain.user.entity.UserInterestCategory;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -9,15 +8,11 @@
import java.util.List;

public interface UserInterestCategoryRepository extends JpaRepository<UserInterestCategory, Long> {

void deleteByUser(User user);

@Query("""
SELECT uic
FROM UserInterestCategory uic
SELECT DISTINCT uic FROM UserInterestCategory uic
LEFT JOIN FETCH uic.keywords
WHERE uic.user = :user
WHERE uic.user.id = :userId
""")
List<UserInterestCategory> findByUserWithKeywords(@Param("user") User user);
List<UserInterestCategory> findByUserIdWithKeywords(@Param("userId") Long userId);

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@

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

public interface UserRepository extends JpaRepository<User, Long> {

@Query("""
SELECT DISTINCT u FROM User u
LEFT JOIN FETCH u.interestCategories
WHERE u.id = :userId
""")
Optional<User> findByIdWithInterestCategories(@Param("userId") Long userId);

/**
* 최근 특정 시간 이후 활동한 사용자 조회
* (읽은 포스트, 스크랩, 검색 기록 중 하나라도 있으면 활성 사용자)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import com.techfork.domain.user.enums.EInterestCategory;
import com.techfork.domain.user.enums.EInterestKeyword;
import com.techfork.domain.user.exception.UserErrorCode;
import com.techfork.domain.user.repository.UserInterestCategoryRepository;
import com.techfork.domain.user.repository.UserRepository;
import com.techfork.global.exception.GeneralException;
import lombok.RequiredArgsConstructor;
Expand All @@ -25,26 +24,23 @@
public class InterestCommandService {

private final UserRepository userRepository;
private final UserInterestCategoryRepository userInterestCategoryRepository;
private final UserProfileService userProfileService;

public void updateUserInterests(Long userId, SaveInterestRequest request) {
saveUserInterests(userId, request);
}

public void saveUserInterests(Long userId, SaveInterestRequest request) {
User user = userRepository.findById(userId)
User user = userRepository.findByIdWithInterestCategories(userId)
.orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND));

userInterestCategoryRepository.deleteByUser(user);
saveUserInterests(user, request);
}

void saveUserInterests(User user, SaveInterestRequest request) {
user.getInterestCategories().clear();
List<UserInterestCategory> categories = createCategoriesFromRequest(user, request);
userInterestCategoryRepository.saveAll(categories);
user.getInterestCategories().addAll(categories);

log.info("Saved {} interest categories for user {}", categories.size(), userId);
log.info("Saved {} interest categories for user {}", categories.size(), user.getId());

// 관심사 저장/수정 시 사용자 프로필 재생성
userProfileService.generateUserProfile(userId);
userProfileService.generateUserProfile(user.getId());
}

private List<UserInterestCategory> createCategoriesFromRequest(User user, SaveInterestRequest request) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public UserInterestResponse getUserInterests(Long userId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND));

List<UserInterestCategory> categories = userInterestCategoryRepository.findByUserWithKeywords(user);
List<UserInterestCategory> categories = userInterestCategoryRepository.findByUserIdWithKeywords(user.getId());
List<UserInterestDto> userInterestDtos = interestConverter.toUserInterestDtoList(categories);

return UserInterestResponse.builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.techfork.domain.user.service;

import com.techfork.domain.user.dto.OnboardingRequest;
import com.techfork.domain.user.dto.SaveInterestRequest;
import com.techfork.domain.user.entity.User;
import com.techfork.domain.user.exception.UserErrorCode;
import com.techfork.domain.user.repository.UserRepository;
import com.techfork.global.exception.GeneralException;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@Slf4j
@Service
@RequiredArgsConstructor
public class UserCommandService {

private final InterestCommandService interestCommandService;

private final UserRepository userRepository;

public void completeOnboarding(Long userId, @Valid OnboardingRequest request) {
User user = userRepository.findByIdWithInterestCategories(userId)
.orElseThrow(() -> new GeneralException(UserErrorCode.USER_NOT_FOUND));

user.updateUser(request.nickname(), request.email(), request.description());

interestCommandService.saveUserInterests(user, new SaveInterestRequest(request.interests()));
}
}
Loading
Loading