11package com .gamzabat .algohub .feature .solution .service ;
22
3+ import java .time .Duration ;
34import java .time .LocalDate ;
45import java .time .LocalDateTime ;
6+ import java .util .ArrayList ;
7+ import java .util .Comparator ;
8+ import java .util .LinkedHashMap ;
59import java .util .List ;
10+ import java .util .Map ;
11+ import java .util .Optional ;
612
713import org .springframework .data .domain .Page ;
814import org .springframework .data .domain .Pageable ;
2935import com .gamzabat .algohub .feature .solution .domain .Solution ;
3036import com .gamzabat .algohub .feature .solution .domain .SolutionComment ;
3137import com .gamzabat .algohub .feature .solution .dto .CreateSolutionRequest ;
38+ import com .gamzabat .algohub .feature .solution .dto .GetCurrentSolvingStatusResponse ;
3239import com .gamzabat .algohub .feature .solution .dto .GetSolutionResponse ;
3340import com .gamzabat .algohub .feature .solution .dto .GetSolutionWithGroupIdResponse ;
41+ import com .gamzabat .algohub .feature .solution .dto .GetSolvingStatusPerProblemResponse ;
3442import com .gamzabat .algohub .feature .solution .enums .ProgressCategory ;
3543import com .gamzabat .algohub .feature .solution .exception .CannotFoundSolutionException ;
3644import com .gamzabat .algohub .feature .solution .repository .SolutionCommentRepository ;
@@ -94,11 +102,7 @@ public GetSolutionResponse getSolution(User user, Long solutionId) {
94102 public Page <GetSolutionResponse > getMySolutionsInGroupInProgress (User user , Long groupId , Integer problemNumber ,
95103 String language ,
96104 String result , Pageable pageable ) {
97- StudyGroup group = studyGroupRepository .findById (groupId )
98- .orElseThrow (() -> new CannotFoundGroupException ("존재하지 않는 그룹입니다." ));
99- if (!groupMemberRepository .existsByUserAndStudyGroup (user , group )) {
100- throw new GroupMemberValidationException (HttpStatus .FORBIDDEN .value (), "참여하지 않은 그룹입니다." );
101- }
105+ StudyGroup group = validateGroupAndMember (user , groupId );
102106
103107 Page <GetSolutionResponse > inProgressSolutions = solutionRepository .findAllFilteredMySolutionsInGroup (user ,
104108 group , problemNumber , language , result , ProgressCategory .IN_PROGRESS , pageable )
@@ -112,11 +116,7 @@ public Page<GetSolutionResponse> getMySolutionsInGroupInProgress(User user, Long
112116 public Page <GetSolutionResponse > getMySolutionsInGroupExpired (User user , Long groupId , Integer problemNumber ,
113117 String language ,
114118 String result , Pageable pageable ) {
115- StudyGroup group = studyGroupRepository .findById (groupId )
116- .orElseThrow (() -> new CannotFoundGroupException ("존재하지 않는 그룹입니다." ));
117- if (!groupMemberRepository .existsByUserAndStudyGroup (user , group )) {
118- throw new GroupMemberValidationException (HttpStatus .FORBIDDEN .value (), "참여하지 않은 그룹입니다." );
119- }
119+ StudyGroup group = validateGroupAndMember (user , groupId );
120120
121121 Page <GetSolutionResponse > expiredSolutions = solutionRepository .findAllFilteredMySolutionsInGroup (user , group ,
122122 problemNumber , language , result , ProgressCategory .EXPIRED , pageable )
@@ -153,6 +153,120 @@ public Page<GetSolutionWithGroupIdResponse> getMySolutionsExpired(User user, Int
153153 return expiredSolutions ;
154154 }
155155
156+ @ Transactional (readOnly = true )
157+ public List <GetCurrentSolvingStatusResponse > getCurrentSolvingStatuses (User user , Long groupId ) {
158+ StudyGroup group = validateGroupAndMember (user , groupId );
159+
160+ List <Problem > inProgressProblems = problemRepository .findAllInProgressProblem (group );
161+ List <GroupMember > members = groupMemberRepository .findAllByStudyGroup (group );
162+
163+ Map <GroupMember , SolvedStatusResult > solvedStatuses = calculateMemberStatusRanks (members , inProgressProblems );
164+
165+ return createSolvingStatusResponses (solvedStatuses );
166+ }
167+
168+ private Map <GroupMember , SolvedStatusResult > calculateMemberStatusRanks (List <GroupMember > members ,
169+ List <Problem > inProgressProblems ) {
170+ Map <GroupMember , SolvedStatusResult > ranks = new LinkedHashMap <>();
171+
172+ for (GroupMember member : members ) {
173+ int totalSubmissionCount = 0 ;
174+ Duration totalPassedTime = Duration .ZERO ;
175+ List <GetSolvingStatusPerProblemResponse > statusResponses = new ArrayList <>();
176+
177+ for (Problem problem : inProgressProblems ) {
178+ List <Solution > solutions = solutionRepository .findAllByUserAndProblem (member .getUser (), problem );
179+ int submissionCount = solutions .size ();
180+ Long firstCorrectSolutionId = null ;
181+ String firstCorrectDuration = "--" ;
182+ boolean solved = false ;
183+
184+ Optional <Solution > firstCorrectSolution = solutions .stream ()
185+ .filter (solution -> isCorrect (solution .getResult ()))
186+ .min (Comparator .comparing (Solution ::getSolvedDateTime ));
187+
188+ if (firstCorrectSolution .isPresent ()) {
189+ Solution solution = firstCorrectSolution .get ();
190+
191+ firstCorrectSolutionId = solution .getId ();
192+
193+ Duration duration = calculateGap (problem , solution );
194+ totalPassedTime = totalPassedTime .plus (duration );
195+ firstCorrectDuration = convertToSolvedTimeFormat (duration );
196+ solved = true ;
197+ }
198+ totalSubmissionCount += submissionCount ;
199+
200+ statusResponses .add (new GetSolvingStatusPerProblemResponse (
201+ problem .getId (), firstCorrectSolutionId ,
202+ submissionCount , firstCorrectDuration , solved
203+ ));
204+ }
205+
206+ float totalScore = calculateTotalScore (totalSubmissionCount , totalPassedTime );
207+ String formattedPassedTime = convertToSolvedTimeFormat (totalPassedTime );
208+ ranks .put (member ,
209+ new SolvedStatusResult (totalScore , totalSubmissionCount , formattedPassedTime , statusResponses ));
210+ }
211+ return ranks ;
212+ }
213+
214+ private Duration calculateGap (Problem problem , Solution solution ) {
215+ LocalDateTime startDate = problem .getStartDate ().atStartOfDay ();
216+ LocalDateTime solvedDateTime = solution .getSolvedDateTime ();
217+ return Duration .between (startDate , solvedDateTime );
218+ }
219+
220+ private float calculateTotalScore (int totalSubmissionCount , Duration totalPassedTime ) {
221+ float totalScore = 0 ;
222+ if (!(totalSubmissionCount == 0 || totalPassedTime .isZero ())) {
223+ long minutes = totalPassedTime .toMinutes ();
224+ totalScore = (float )1 / (totalSubmissionCount * minutes );
225+ }
226+ return totalScore ;
227+ }
228+
229+ private List <GetCurrentSolvingStatusResponse > createSolvingStatusResponses (
230+ Map <GroupMember , SolvedStatusResult > ranks ) {
231+ List <GroupMember > memberOrders = ranks .keySet ().stream ()
232+ .sorted ((m1 , m2 ) -> Float .compare (ranks .get (m2 ).totalScore , ranks .get (m1 ).totalScore ))
233+ .toList ();
234+
235+ List <GetCurrentSolvingStatusResponse > responses = new ArrayList <>();
236+ for (int i = 0 ; i < memberOrders .size (); i ++) {
237+ GroupMember member = memberOrders .get (i );
238+ responses .add (new GetCurrentSolvingStatusResponse (
239+ i + 1 , member .getUser ().getNickname (),
240+ ranks .get (member ).totalSubmissionCount ,
241+ ranks .get (member ).totalPassedTime ,
242+ ranks .get (member ).problems
243+ ));
244+ }
245+ return responses ;
246+ }
247+
248+ private void sendNewSolutionNotification (StudyGroup group , GroupMember solver , Problem problem ) {
249+ List <GroupMember > groupMembers = groupMemberRepository .findAllByStudyGroup (group ).stream ()
250+ .filter (member -> !member .getId ().equals (solver .getId ()))
251+ .toList ();
252+
253+ notificationService .sendNotificationToMembers (
254+ group ,
255+ groupMembers ,
256+ problem ,
257+ null ,
258+ NotificationCategory .NEW_SOLUTION_POSTED ,
259+ NotificationCategory .NEW_SOLUTION_POSTED .getMessage (solver .getUser ().getNickname ())
260+ );
261+ }
262+
263+ private String convertToSolvedTimeFormat (Duration duration ) {
264+ long totalMinutes = duration .toMinutes ();
265+ long hours = totalMinutes / 60 ;
266+ long minutes = totalMinutes % 60 ;
267+ return String .format ("%d:%02d" , hours , minutes );
268+ }
269+
156270 private GetSolutionWithGroupIdResponse getGetSolutionWithGroupIdResponse (User user , Solution solution ) {
157271 Integer correctCount = getCorrectCount (solution );
158272 Integer submitMemberCount = solutionRepository .countDistinctUsersByProblem (solution .getProblem ());
@@ -244,19 +358,13 @@ public void createSolution(CreateSolutionRequest request) {
244358
245359 }
246360
247- private void sendNewSolutionNotification (StudyGroup group , GroupMember solver , Problem problem ) {
248- List <GroupMember > groupMembers = groupMemberRepository .findAllByStudyGroup (group ).stream ()
249- .filter (member -> !member .getId ().equals (solver .getId ()))
250- .toList ();
251-
252- notificationService .sendNotificationToMembers (
253- group ,
254- groupMembers ,
255- problem ,
256- null ,
257- NotificationCategory .NEW_SOLUTION_POSTED ,
258- NotificationCategory .NEW_SOLUTION_POSTED .getMessage (solver .getUser ().getNickname ())
259- );
361+ private StudyGroup validateGroupAndMember (User user , Long groupId ) {
362+ StudyGroup group = studyGroupRepository .findById (groupId )
363+ .orElseThrow (() -> new CannotFoundGroupException ("존재하지 않는 그룹입니다." ));
364+ if (!groupMemberRepository .existsByUserAndStudyGroup (user , group )) {
365+ throw new GroupMemberValidationException (HttpStatus .FORBIDDEN .value (), "참여하지 않은 그룹입니다." );
366+ }
367+ return group ;
260368 }
261369
262370 private boolean isCorrect (String result ) {
@@ -291,4 +399,10 @@ private boolean isAllCommentsRead(Solution solution) {
291399
292400 return true ;
293401 }
402+
403+ private record SolvedStatusResult (float totalScore ,
404+ int totalSubmissionCount ,
405+ String totalPassedTime ,
406+ List <GetSolvingStatusPerProblemResponse > problems ) {
407+ }
294408}
0 commit comments