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 @@ -53,7 +53,8 @@ private JPAQuery<Long> rankingCountQuery(StudyGroup studyGroup) {
.from(ranking)
.join(ranking.member, groupMember)
.join(groupMember.user, user)
.where(ranking.member.studyGroup.eq(studyGroup));
.where(ranking.member.studyGroup.eq(studyGroup)
.and(ranking.solvedCount.gt(0)));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
import com.gamzabat.algohub.feature.notification.domain.Notification;
import com.gamzabat.algohub.feature.problem.domain.Problem;
import com.gamzabat.algohub.feature.user.domain.User;

public interface NotificationRepository extends JpaRepository<Notification, Long> {
Expand All @@ -19,6 +20,10 @@ public interface NotificationRepository extends JpaRepository<Notification, Long
@Query("delete from Notification n where n.studyGroup = :studyGroup")
void deleteAllByStudyGroup(StudyGroup studyGroup);

@Modifying
@Query("DELETE FROM Notification n WHERE n.problem = :problem")
void deleteAllByProblem(Problem problem);

@Modifying
@Query("DELETE FROM Notification n WHERE n.user = :user AND n.studyGroup = :studyGroup")
void deleteAllByUserAndStudyGroup(User user, StudyGroup studyGroup);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gamzabat.algohub.feature.problem.repository.querydsl;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

Expand All @@ -10,6 +12,8 @@
public interface CustomProblemRepository {
Page<Problem> findAllInProgressProblem(User user, StudyGroup group, Boolean unsolvedOnly, Pageable pageable);

List<Problem> findAllInProgressProblem(StudyGroup group);

Page<Problem> findAllExpiredProblem(StudyGroup group, Pageable pageable);

Page<Problem> findAllQueuedProblem(StudyGroup group, Pageable pageable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static com.gamzabat.algohub.feature.solution.domain.QSolution.*;

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

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand Down Expand Up @@ -41,6 +42,16 @@ public Page<Problem> findAllInProgressProblem(User user, StudyGroup group, Boole
return findAllProblemByCondition(condition, pageable);
}

@Override
public List<Problem> findAllInProgressProblem(StudyGroup group) {
JPAQuery<Problem> query = queryFactory.selectFrom(problem)
.where(problem.studyGroup.eq(group)
.and(problem.deletedAt.isNull())
.and(problem.startDate.loe(LocalDate.now()))
.and(problem.endDate.goe(LocalDate.now())));
return query.fetch();
}

@Override
public Page<Problem> findAllExpiredProblem(StudyGroup group, Pageable pageable) {
BooleanExpression condition = problem.studyGroup.eq(group)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import com.gamzabat.algohub.feature.group.studygroup.repository.GroupMemberRepository;
import com.gamzabat.algohub.feature.group.studygroup.repository.StudyGroupRepository;
import com.gamzabat.algohub.feature.notification.enums.NotificationCategory;
import com.gamzabat.algohub.feature.notification.repository.NotificationRepository;
import com.gamzabat.algohub.feature.notification.service.NotificationService;
import com.gamzabat.algohub.feature.problem.domain.Problem;
import com.gamzabat.algohub.feature.problem.dto.CreateProblemRequest;
Expand All @@ -53,6 +54,7 @@ public class ProblemService {
private final GroupMemberRepository groupMemberRepository;
private final NotificationService notificationService;
private final RestTemplate restTemplate;
private final NotificationRepository notificationRepository;

@Transactional
public void createProblem(User user, Long groupId, CreateProblemRequest request) {
Expand Down Expand Up @@ -126,11 +128,6 @@ private void checkProblemValidation(Problem problem) {
throw new ProblemValidationException(HttpStatus.FORBIDDEN.value(),
"문제 수정이 불가합니다. : 이미 종료된 문제입니다.");
}
if (problem.getStartDate().isBefore(LocalDate.now()) || problem.getStartDate().equals(LocalDate.now())) {
throw new ProblemValidationException(HttpStatus.FORBIDDEN.value(),
"문제 수정이 불가합니다. : 이미 진행 중인 문제입니다.");
}

}

private void checkProblemEndDate(EditProblemRequest request, Problem problem) {
Expand Down Expand Up @@ -212,6 +209,7 @@ public void deleteProblem(User user, Long problemId) {

solutionRepository.deleteAllByProblem(problem);
problemRepository.delete(problem);
notificationRepository.deleteAllByProblem(problem);
log.info("success to delete problem user_id={} , problem_id = {}", user.getId(), problemId);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gamzabat.algohub.feature.solution.controller;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
Expand All @@ -17,6 +19,7 @@
import com.gamzabat.algohub.common.annotation.AuthedUser;
import com.gamzabat.algohub.exception.RequestException;
import com.gamzabat.algohub.feature.solution.dto.CreateSolutionRequest;
import com.gamzabat.algohub.feature.solution.dto.GetCurrentSolvingStatusResponse;
import com.gamzabat.algohub.feature.solution.dto.GetSolutionResponse;
import com.gamzabat.algohub.feature.solution.dto.GetSolutionWithGroupIdResponse;
import com.gamzabat.algohub.feature.solution.service.SolutionService;
Expand Down Expand Up @@ -123,4 +126,12 @@ public ResponseEntity<Page<GetSolutionWithGroupIdResponse>> getMySolutions(@Auth
problemNumber, language, result, pageable);
return ResponseEntity.ok().body(response);
}

@GetMapping("/groups/{groupId}/solutions/current-status")
@Operation(summary = "풀이 현황 테이블 조회 API", description = "진행 중인 문제들에 대해 풀이 현황 테이블을 조회하는 API")
public ResponseEntity<List<GetCurrentSolvingStatusResponse>> getCurrentSolvingStatus(@AuthedUser User user,
@PathVariable Long groupId) {
List<GetCurrentSolvingStatusResponse> response = solutionService.getCurrentSolvingStatuses(user, groupId);
return ResponseEntity.ok().body(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.gamzabat.algohub.feature.solution.dto;

import java.util.List;

public record GetCurrentSolvingStatusResponse(Integer rank,
String nickname,
Integer totalSubmissionCount,
String totalPassedTime,
List<GetSolvingStatusPerProblemResponse> problems) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.gamzabat.algohub.feature.solution.dto;

public record GetSolvingStatusPerProblemResponse(Long problemId,
Long firstCorrectSolutionId,
int submissionCount,
String firstCorrectDuration,
boolean solved) {
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gamzabat.algohub.feature.solution.repository;

import java.util.List;
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
Expand Down Expand Up @@ -59,4 +60,11 @@ Long countDistinctCorrectSolutionsByUserAndGroup(User user, StudyGroup group,
+ "FROM Problem p "
+ "WHERE p.studyGroup = :studyGroup)")
void deleteAllByStudyGroupAndUser(StudyGroup studyGroup, User user);

@Query("SELECT s "
+ "FROM Solution s "
+ "WHERE s.deletedAt IS NULL "
+ "AND s.user = :user "
+ "AND s.problem = :problem")
List<Solution> findAllByUserAndProblem(User user, Problem problem);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package com.gamzabat.algohub.feature.solution.service;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -29,8 +35,10 @@
import com.gamzabat.algohub.feature.solution.domain.Solution;
import com.gamzabat.algohub.feature.solution.domain.SolutionComment;
import com.gamzabat.algohub.feature.solution.dto.CreateSolutionRequest;
import com.gamzabat.algohub.feature.solution.dto.GetCurrentSolvingStatusResponse;
import com.gamzabat.algohub.feature.solution.dto.GetSolutionResponse;
import com.gamzabat.algohub.feature.solution.dto.GetSolutionWithGroupIdResponse;
import com.gamzabat.algohub.feature.solution.dto.GetSolvingStatusPerProblemResponse;
import com.gamzabat.algohub.feature.solution.enums.ProgressCategory;
import com.gamzabat.algohub.feature.solution.exception.CannotFoundSolutionException;
import com.gamzabat.algohub.feature.solution.repository.SolutionCommentRepository;
Expand Down Expand Up @@ -94,11 +102,7 @@ public GetSolutionResponse getSolution(User user, Long solutionId) {
public Page<GetSolutionResponse> getMySolutionsInGroupInProgress(User user, Long groupId, Integer problemNumber,
String language,
String result, Pageable pageable) {
StudyGroup group = studyGroupRepository.findById(groupId)
.orElseThrow(() -> new CannotFoundGroupException("존재하지 않는 그룹입니다."));
if (!groupMemberRepository.existsByUserAndStudyGroup(user, group)) {
throw new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "참여하지 않은 그룹입니다.");
}
StudyGroup group = validateGroupAndMember(user, groupId);

Page<GetSolutionResponse> inProgressSolutions = solutionRepository.findAllFilteredMySolutionsInGroup(user,
group, problemNumber, language, result, ProgressCategory.IN_PROGRESS, pageable)
Expand All @@ -112,11 +116,7 @@ public Page<GetSolutionResponse> getMySolutionsInGroupInProgress(User user, Long
public Page<GetSolutionResponse> getMySolutionsInGroupExpired(User user, Long groupId, Integer problemNumber,
String language,
String result, Pageable pageable) {
StudyGroup group = studyGroupRepository.findById(groupId)
.orElseThrow(() -> new CannotFoundGroupException("존재하지 않는 그룹입니다."));
if (!groupMemberRepository.existsByUserAndStudyGroup(user, group)) {
throw new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "참여하지 않은 그룹입니다.");
}
StudyGroup group = validateGroupAndMember(user, groupId);

Page<GetSolutionResponse> expiredSolutions = solutionRepository.findAllFilteredMySolutionsInGroup(user, group,
problemNumber, language, result, ProgressCategory.EXPIRED, pageable)
Expand Down Expand Up @@ -153,6 +153,120 @@ public Page<GetSolutionWithGroupIdResponse> getMySolutionsExpired(User user, Int
return expiredSolutions;
}

@Transactional(readOnly = true)
public List<GetCurrentSolvingStatusResponse> getCurrentSolvingStatuses(User user, Long groupId) {
StudyGroup group = validateGroupAndMember(user, groupId);

List<Problem> inProgressProblems = problemRepository.findAllInProgressProblem(group);
List<GroupMember> members = groupMemberRepository.findAllByStudyGroup(group);

Map<GroupMember, SolvedStatusResult> solvedStatuses = calculateMemberStatusRanks(members, inProgressProblems);

return createSolvingStatusResponses(solvedStatuses);
}

private Map<GroupMember, SolvedStatusResult> calculateMemberStatusRanks(List<GroupMember> members,
List<Problem> inProgressProblems) {
Map<GroupMember, SolvedStatusResult> ranks = new LinkedHashMap<>();

for (GroupMember member : members) {
int totalSubmissionCount = 0;
Duration totalPassedTime = Duration.ZERO;
List<GetSolvingStatusPerProblemResponse> statusResponses = new ArrayList<>();

for (Problem problem : inProgressProblems) {
List<Solution> solutions = solutionRepository.findAllByUserAndProblem(member.getUser(), problem);
int submissionCount = solutions.size();
Long firstCorrectSolutionId = null;
String firstCorrectDuration = "--";
boolean solved = false;

Optional<Solution> firstCorrectSolution = solutions.stream()
.filter(solution -> isCorrect(solution.getResult()))
.min(Comparator.comparing(Solution::getSolvedDateTime));

if (firstCorrectSolution.isPresent()) {
Solution solution = firstCorrectSolution.get();

firstCorrectSolutionId = solution.getId();

Duration duration = calculateGap(problem, solution);
totalPassedTime = totalPassedTime.plus(duration);
firstCorrectDuration = convertToSolvedTimeFormat(duration);
solved = true;
}
totalSubmissionCount += submissionCount;

statusResponses.add(new GetSolvingStatusPerProblemResponse(
problem.getId(), firstCorrectSolutionId,
submissionCount, firstCorrectDuration, solved
));
}

float totalScore = calculateTotalScore(totalSubmissionCount, totalPassedTime);
String formattedPassedTime = convertToSolvedTimeFormat(totalPassedTime);
ranks.put(member,
new SolvedStatusResult(totalScore, totalSubmissionCount, formattedPassedTime, statusResponses));
}
return ranks;
}

private Duration calculateGap(Problem problem, Solution solution) {
LocalDateTime startDate = problem.getStartDate().atStartOfDay();
LocalDateTime solvedDateTime = solution.getSolvedDateTime();
return Duration.between(startDate, solvedDateTime);
}

private float calculateTotalScore(int totalSubmissionCount, Duration totalPassedTime) {
float totalScore = 0;
if (!(totalSubmissionCount == 0 || totalPassedTime.isZero())) {
long minutes = totalPassedTime.toMinutes();
totalScore = (float)1 / (totalSubmissionCount * minutes);
}
return totalScore;
}

private List<GetCurrentSolvingStatusResponse> createSolvingStatusResponses(
Map<GroupMember, SolvedStatusResult> ranks) {
List<GroupMember> memberOrders = ranks.keySet().stream()
.sorted((m1, m2) -> Float.compare(ranks.get(m2).totalScore, ranks.get(m1).totalScore))
.toList();

List<GetCurrentSolvingStatusResponse> responses = new ArrayList<>();
for (int i = 0; i < memberOrders.size(); i++) {
GroupMember member = memberOrders.get(i);
responses.add(new GetCurrentSolvingStatusResponse(
i + 1, member.getUser().getNickname(),
ranks.get(member).totalSubmissionCount,
ranks.get(member).totalPassedTime,
ranks.get(member).problems
));
}
return responses;
}

private void sendNewSolutionNotification(StudyGroup group, GroupMember solver, Problem problem) {
List<GroupMember> groupMembers = groupMemberRepository.findAllByStudyGroup(group).stream()
.filter(member -> !member.getId().equals(solver.getId()))
.toList();

notificationService.sendNotificationToMembers(
group,
groupMembers,
problem,
null,
NotificationCategory.NEW_SOLUTION_POSTED,
NotificationCategory.NEW_SOLUTION_POSTED.getMessage(solver.getUser().getNickname())
);
}

private String convertToSolvedTimeFormat(Duration duration) {
long totalMinutes = duration.toMinutes();
long hours = totalMinutes / 60;
long minutes = totalMinutes % 60;
return String.format("%d:%02d", hours, minutes);
}

private GetSolutionWithGroupIdResponse getGetSolutionWithGroupIdResponse(User user, Solution solution) {
Integer correctCount = getCorrectCount(solution);
Integer submitMemberCount = solutionRepository.countDistinctUsersByProblem(solution.getProblem());
Expand Down Expand Up @@ -244,19 +358,13 @@ public void createSolution(CreateSolutionRequest request) {

}

private void sendNewSolutionNotification(StudyGroup group, GroupMember solver, Problem problem) {
List<GroupMember> groupMembers = groupMemberRepository.findAllByStudyGroup(group).stream()
.filter(member -> !member.getId().equals(solver.getId()))
.toList();

notificationService.sendNotificationToMembers(
group,
groupMembers,
problem,
null,
NotificationCategory.NEW_SOLUTION_POSTED,
NotificationCategory.NEW_SOLUTION_POSTED.getMessage(solver.getUser().getNickname())
);
private StudyGroup validateGroupAndMember(User user, Long groupId) {
StudyGroup group = studyGroupRepository.findById(groupId)
.orElseThrow(() -> new CannotFoundGroupException("존재하지 않는 그룹입니다."));
if (!groupMemberRepository.existsByUserAndStudyGroup(user, group)) {
throw new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "참여하지 않은 그룹입니다.");
}
return group;
}

private boolean isCorrect(String result) {
Expand Down Expand Up @@ -291,4 +399,10 @@ private boolean isAllCommentsRead(Solution solution) {

return true;
}

private record SolvedStatusResult(float totalScore,
int totalSubmissionCount,
String totalPassedTime,
List<GetSolvingStatusPerProblemResponse> problems) {
}
}
Loading
Loading