Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4669945
[Feat] 도메인 모델 및 TagType 정의
junggyo1020 Oct 12, 2025
050b098
[Feat] JPA 엔티티 추가
junggyo1020 Oct 12, 2025
37bbc29
[Feat] 추천 스터디 관련 repository 추가
junggyo1020 Oct 12, 2025
d48723e
[Feat] 홈 추천 스터디 API 응답 DTO 정의
junggyo1020 Oct 12, 2025
fd8d311
[Feat] 홈 추천 스터디 API 비즈니스 로직 구현
junggyo1020 Oct 12, 2025
c01c911
[Feat] 홈 추천 스터디 조회 API 컨트롤러 구현
junggyo1020 Oct 12, 2025
02fc886
[Test] 추천 기능 서비스 & 컨트롤러 테스트 코드 작성
junggyo1020 Oct 12, 2025
10db4c7
[Feat] 추천 스터디 집계 배치 서비스 구현
junggyo1020 Oct 12, 2025
324948c
[Feat] 추천 스터디 집계 관련 메서드 추가
junggyo1020 Oct 26, 2025
946cfd9
[Feat] 스케쥴러 작성
junggyo1020 Oct 12, 2025
a46aab1
[Test] 추천 기능 서비스 & 컨트롤러 테스트 코드 작성
junggyo1020 Oct 12, 2025
0b2562d
[Refactor] AuthedUser를 통해 User 객체를 받아와 Param으로 사용하도록 수정
junggyo1020 Oct 19, 2025
12c94f1
[Refactor] Builder와 함께 NotNull 검증 로직 추가
junggyo1020 Oct 19, 2025
824fc03
[Test] 테스트 코드 수정
junggyo1020 Oct 19, 2025
cedc542
[Refactor] 개행 추가
junggyo1020 Oct 19, 2025
980af59
[Refactor] 개행 추가
junggyo1020 Oct 19, 2025
16aa199
[Refactor] 개행 추가
junggyo1020 Oct 19, 2025
2c4ed04
[Refactor] User 엔티티와 직접 @ManyToOne 연관관계 맺도록 수정
junggyo1020 Oct 19, 2025
3968eb9
[Test] 테스트 코드 수정
junggyo1020 Oct 19, 2025
875248e
[Test] Assertions.assertAll 적용
junggyo1020 Oct 19, 2025
62c96ba
[Feat] 홈 화면 추천 API 응답에 스터디 기간 추가
junggyo1020 Oct 19, 2025
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
@@ -1,11 +1,13 @@
package com.gamzabat.algohub.feature.group.studygroup.repository;

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

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.gamzabat.algohub.feature.group.studygroup.domain.GroupMember;
import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
Expand All @@ -31,4 +33,13 @@ public interface GroupMemberRepository extends JpaRepository<GroupMember, Long>
@Modifying
@Query("delete from GroupMember gm where gm.studyGroup = :studyGroup")
void deleteAllByStudyGroup(StudyGroup studyGroup);

@Query("SELECT COUNT(gm) FROM GroupMember gm WHERE gm.studyGroup = :studyGroup AND gm.joinDate BETWEEN :start AND :end")
Integer countByStudyGroupAndJoinDateBetween(@Param("studyGroup") StudyGroup studyGroup,
@Param("start") LocalDate start, @Param("end") LocalDate end);

@Query("SELECT COUNT(gm) FROM GroupMember gm WHERE gm.studyGroup = :studyGroup AND gm.joinDate < :date")
Integer countByStudyGroupAndJoinDateBefore(@Param("studyGroup") StudyGroup studyGroup,
@Param("date") LocalDate date);

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
import com.gamzabat.algohub.feature.problem.domain.Problem;
Expand Down Expand Up @@ -62,4 +63,8 @@ AND EXISTS (
@Modifying
@Query("update Problem p set p.deletedAt = CURRENT_TIMESTAMP where p.studyGroup = :studyGroup")
void deleteAllByStudyGroup(StudyGroup studyGroup);

@Query("SELECT p FROM Problem p WHERE p.studyGroup = :studyGroup AND p.startDate BETWEEN :start AND :end AND p.deletedAt IS NULL")
List<Problem> findAllByStudyGroupAndStartDateBetween(@Param("studyGroup") StudyGroup studyGroup,
@Param("start") LocalDate start, @Param("end") LocalDate end);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.gamzabat.algohub.feature.recommendation.controller;

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 com.gamzabat.algohub.common.annotation.AuthedUser;
import com.gamzabat.algohub.feature.recommendation.dto.HomeRecommendationsResponse;
import com.gamzabat.algohub.feature.recommendation.service.RecommendationService;
import com.gamzabat.algohub.feature.user.domain.User;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api")
@Tag(name = "추천 API", description = "홈 화면 추천 스터디 API")
public class RecommendationController {

private final RecommendationService recommendationService;

@GetMapping("/home/recommendations")
@Operation(summary = "홈 추천 스터디 조회 API")
public ResponseEntity<HomeRecommendationsResponse> getHomeRecommendations(
@AuthedUser User user
) {
HomeRecommendationsResponse response = recommendationService.getHomeRecommendations(user.getId());
return ResponseEntity.ok().body(response);
}
}
Comment thread
junggyo1020 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package com.gamzabat.algohub.feature.recommendation.domain;

import java.time.LocalDateTime;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "group_activity_weekly")
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
Comment thread
junggyo1020 marked this conversation as resolved.
public class GroupActivityWeekly {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_group_id", nullable = false)
private StudyGroup studyGroup;

@Column(name = "week_start", nullable = false)
private LocalDateTime weekStart;

@Column(name = "week_end", nullable = false)
private LocalDateTime weekEnd;
Comment thread
junggyo1020 marked this conversation as resolved.

@Column(name = "submissions", nullable = false)
private Integer submissions;

@Column(name = "comments", nullable = false)
private Integer comments;

@Column(name = "active_score", nullable = false)
private Double activeScore;

@Builder
public GroupActivityWeekly(
@NotNull StudyGroup studyGroup,
@NotNull LocalDateTime weekStart,
@NotNull LocalDateTime weekEnd,
@NotNull Integer submissions,
@NotNull Integer comments,
@NotNull Double activeScore) {
this.studyGroup = studyGroup;
this.weekStart = weekStart;
this.weekEnd = weekEnd;
this.submissions = submissions;
this.comments = comments;
this.activeScore = activeScore;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.gamzabat.algohub.feature.recommendation.domain;

import java.time.LocalDateTime;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "group_difficulty_monthly_rolling")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class GroupDifficultyMonthlyRolling {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_group_id", nullable = false)
private StudyGroup studyGroup;

@Column(name = "window_start", nullable = false)
private LocalDateTime windowStart;

@Column(name = "window_end", nullable = false)
private LocalDateTime windowEnd;

@Column(name = "avg_difficulty", nullable = false)
private Double avgDifficulty;

@Builder
public GroupDifficultyMonthlyRolling(
@NotNull StudyGroup studyGroup,
@NotNull LocalDateTime windowStart,
@NotNull LocalDateTime windowEnd,
@NotNull Double avgDifficulty) {
this.studyGroup = studyGroup;
this.windowStart = windowStart;
this.windowEnd = windowEnd;
this.avgDifficulty = avgDifficulty;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package com.gamzabat.algohub.feature.recommendation.domain;

import java.time.LocalDateTime;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "group_join_monthly_rolling")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class GroupJoinMonthlyRolling {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_group_id", nullable = false)
private StudyGroup studyGroup;

@Column(name = "window_start", nullable = false)
private LocalDateTime windowStart;

@Column(name = "window_end", nullable = false)
private LocalDateTime windowEnd;

@Column(name = "new_members", nullable = false)
private Integer newMembers;

@Column(name = "members_before_window", nullable = false)
private Integer membersBeforeWindow;

@Column(name = "join_rate", nullable = false)
private Double joinRate;

@Builder
public GroupJoinMonthlyRolling(
@NotNull StudyGroup studyGroup,
@NotNull LocalDateTime windowStart,
@NotNull LocalDateTime windowEnd,
@NotNull Integer newMembers,
@NotNull Integer membersBeforeWindow,
@NotNull Double joinRate) {
this.studyGroup = studyGroup;
this.windowStart = windowStart;
this.windowEnd = windowEnd;
this.newMembers = newMembers;
this.membersBeforeWindow = membersBeforeWindow;
this.joinRate = joinRate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.gamzabat.algohub.feature.recommendation.domain;

import java.time.LocalDateTime;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Table(name = "study_group_tag")
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class StudyGroupTag {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_group_id", nullable = false)
private StudyGroup studyGroup;

@Enumerated(EnumType.STRING)
@Column(name = "tag_type", nullable = false, length = 64)
Comment thread
junggyo1020 marked this conversation as resolved.
private TagType tagType;

@Column(name = "score", nullable = false)
private Double score;

@Column(name = "first_achieved_at", nullable = false)
private LocalDateTime firstAchievedAt;

@Column(name = "window_start", nullable = false)
private LocalDateTime windowStart;

@Column(name = "window_end", nullable = false)
private LocalDateTime windowEnd;

@Column(name = "computed_at", nullable = false)
private LocalDateTime computedAt;

@Builder
public StudyGroupTag(
@NotNull StudyGroup studyGroup,
@NotNull TagType tagType,
@NotNull Double score,
@NotNull LocalDateTime firstAchievedAt,
@NotNull LocalDateTime windowStart,
@NotNull LocalDateTime windowEnd,
@NotNull LocalDateTime computedAt) {
this.studyGroup = studyGroup;
this.tagType = tagType;
this.score = score;
this.firstAchievedAt = firstAchievedAt;
this.windowStart = windowStart;
this.windowEnd = windowEnd;
this.computedAt = computedAt;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.gamzabat.algohub.feature.recommendation.domain;

public enum TagType {
MOST_ACTIVE_THIS_WEEK,
HIGH_JOIN_RATE_RECENT,
SIMILAR_DIFFICULTY
}
Loading
Loading