From d2697644de4b6464dd921f343b49eb66936812ee Mon Sep 17 00:00:00 2001 From: chanrhan Date: Wed, 4 Feb 2026 11:06:24 +0900 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=EC=A3=BC=EA=B0=84=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=EB=A7=88=EC=A7=80=EB=A7=89=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=ED=8C=85=20=EC=A0=90=EC=88=98=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?(=EB=A7=88=EC=9D=B4=EA=B7=B8=EB=A0=88=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/db/migration/V8__add_column_weekly_results.sql | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/main/resources/db/migration/V8__add_column_weekly_results.sql diff --git a/src/main/resources/db/migration/V8__add_column_weekly_results.sql b/src/main/resources/db/migration/V8__add_column_weekly_results.sql new file mode 100644 index 00000000..fcc15817 --- /dev/null +++ b/src/main/resources/db/migration/V8__add_column_weekly_results.sql @@ -0,0 +1,2 @@ +alter table weekly_results + add column last_rating int default 0 From eed2bafa2a2f48bfd4c87c5034e37dbdbf95b129 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Wed, 4 Feb 2026 11:48:11 +0900 Subject: [PATCH 2/7] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EA=B0=B1=EC=8B=A0=20job=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20=EB=B0=B1=EC=A4=80=20=EC=8A=A4=ED=81=AC=EB=9E=98?= =?UTF-8?q?=ED=95=91=20=EC=8B=9C=20=EB=A7=9E=EC=9D=80=20=EB=AC=B8=EC=A0=9D?= =?UTF-8?q?=EA=B0=80=20=EC=9E=88=EB=8B=A4=EB=A9=B4=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EA=B0=B1=EC=8B=A0=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/user/service/UserService.java | 22 +++++++++++-------- .../global/code/ApiResponseCode.java | 1 + .../baektracker/global/config/JobConfig.java | 5 ++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/baektracker/domain/user/service/UserService.java b/src/main/java/com/baektracker/domain/user/service/UserService.java index 9214941f..f9539af6 100644 --- a/src/main/java/com/baektracker/domain/user/service/UserService.java +++ b/src/main/java/com/baektracker/domain/user/service/UserService.java @@ -44,15 +44,19 @@ public List getUsers() { public void updateUserInfoFromSolvedAc() { List users = userRepository.findAll(); for (User user : users) { - SolvedAcUser solvedAcUser = solvedAcService.searchUser(user.getUsername()); - - try { - user.setLevel(solvedAcUser.items().get(0).tier()); - user.setRating(solvedAcUser.items().get(0).rating()); - } catch (NullPointerException e) { - e.printStackTrace(); - continue; - } + updateUserInfoFromSolvedAc(user); + } + } + + @Transactional + public void updateUserInfoFromSolvedAc(User user) { + SolvedAcUser solvedAcUser = solvedAcService.searchUser(user.getUsername()); + + try { + user.setLevel(solvedAcUser.items().get(0).tier()); + user.setRating(solvedAcUser.items().get(0).rating()); + } catch (NullPointerException e) { + throw CustomException.of(ApiResponseCode.SOLVED_AC_USER_INFO_ERROR, "username: " + user.getUsername()); } } diff --git a/src/main/java/com/baektracker/global/code/ApiResponseCode.java b/src/main/java/com/baektracker/global/code/ApiResponseCode.java index d883fbea..6066d8df 100644 --- a/src/main/java/com/baektracker/global/code/ApiResponseCode.java +++ b/src/main/java/com/baektracker/global/code/ApiResponseCode.java @@ -70,6 +70,7 @@ public enum ApiResponseCode { TOKEN_PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "토큰 파싱 중 오류가 발생했습니다."), BAEKJOON_PAGE_SCRAP_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "백준 페이지 스크랩 중 오류가 발생했습니다."), SOLVED_AC_PROBLEM_SEARCH_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "[Solved-ac] 문제 정보를 가져오는 중 오류가 발생했습니다"), + SOLVED_AC_USER_INFO_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "[Solved-ac] 사용자 정보를 가져오는 중 오류가 발생했습니다"), SOLVED_AC_NOT_FOUND_USER(HttpStatus.INTERNAL_SERVER_ERROR, "[Solved-ac] 사용자 정보를 찾을 수 없습니다."); private final HttpStatus httpStatus; diff --git a/src/main/java/com/baektracker/global/config/JobConfig.java b/src/main/java/com/baektracker/global/config/JobConfig.java index 4f093e9d..e0ee7517 100644 --- a/src/main/java/com/baektracker/global/config/JobConfig.java +++ b/src/main/java/com/baektracker/global/config/JobConfig.java @@ -1,6 +1,5 @@ package com.baektracker.global.config; -import com.baektracker.global.job.RecordUserLevelJob; import com.baektracker.global.job.RecordWeeklyResultJob; import jakarta.annotation.PostConstruct; import java.util.HashMap; @@ -24,11 +23,11 @@ public class JobConfig { @PostConstruct // '의존성 주입이 완료 된 후 실행되는 메소드'를 지정하는 어노테이션 public void run() { JobDetail recordWeeklyResultJob = getJobDetail(RecordWeeklyResultJob.class, new HashMap()); - JobDetail userLevelJob = getJobDetail(RecordUserLevelJob.class, new HashMap()); +// JobDetail userLevelJob = getJobDetail(RecordUserLevelJob.class, new HashMap()); try { scheduler.scheduleJob(recordWeeklyResultJob, getCronTrigger("0 59 23 ? * SUN")); // 매주 일요일 23:59 - scheduler.scheduleJob(userLevelJob, getCronTrigger("0 1 0 ? * *")); // 매일 23:59 +// scheduler.scheduleJob(userLevelJob, getCronTrigger("0 1 0 ? * *")); // 매일 23:59 } catch (SchedulerException e) { throw new RuntimeException(e); } From 5d38db6e84845a9371c540c587f07f038ac7a026 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Wed, 4 Feb 2026 11:48:56 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EB=AC=B8=EC=A0=9C=20=ED=98=84?= =?UTF-8?q?=ED=99=A9=20=EC=A1=B0=ED=9A=8C=20API=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=EB=B0=94=EB=94=94=EC=97=90=20=EB=A0=88=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=A6=9D=EA=B0=80=EC=9C=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/src/css/styles.module.css | 18 +++++++++++++++++- .../src/js/components/UserProgress.jsx | 12 +++++++++++- .../baekjoon/service/BaekjoonService.java | 9 +++++++++ .../domain/problem/dto/WeeklyUserProgress.java | 3 ++- .../service/BaekjoonProblemService.java | 12 +++++++----- .../domain/test/controller/TestController.java | 7 +++++++ .../weekly_result/model/WeeklyResult.java | 3 +++ .../service/WeeklyResultService.java | 1 + 8 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/main/frontend/src/css/styles.module.css b/src/main/frontend/src/css/styles.module.css index d82e9f24..5f49254f 100644 --- a/src/main/frontend/src/css/styles.module.css +++ b/src/main/frontend/src/css/styles.module.css @@ -485,8 +485,24 @@ .userProgressName .rating_text { margin: 0 6px; - font-size: 14px; + font-size: 16px; + cursor: pointer; +} + +.userProgressName .increase_icon { + margin-left: 4px; + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 10px solid #1e90ff; /* 파란색 */ +} + +.userProgressName .increased_rating_text { + margin: 0 3px 0 2px; + font-size: 13px; cursor: pointer; + color: #484848; } .userProgressName .pass_text { diff --git a/src/main/frontend/src/js/components/UserProgress.jsx b/src/main/frontend/src/js/components/UserProgress.jsx index 7692d5e2..775cd4b9 100644 --- a/src/main/frontend/src/js/components/UserProgress.jsx +++ b/src/main/frontend/src/js/components/UserProgress.jsx @@ -248,6 +248,7 @@ export function UserProgress({fromDate, toDate}) { const userProgress = problems[user.id]; const score = userProgress ? userProgress.score : 0; const isWeekPass = userProgress?.isWeekPass ?? false + const increasedRating = userProgress.increasedRating > 0 ? userProgress.increasedRating : null; const problemList = userProgress?.problems; return ( @@ -266,7 +267,16 @@ export function UserProgress({fromDate, toDate}) { {user.rating} + onMouseLeave={tooltip.onMouseLeave}>{user.rating} + { + increasedRating && <> + + {increasedRating} + + } + + {isWeekPass ? PASS : ''} diff --git a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java index ccad2753..27998a7d 100644 --- a/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java +++ b/src/main/java/com/baektracker/domain/baekjoon/service/BaekjoonService.java @@ -8,6 +8,7 @@ import com.baektracker.domain.problem.service.BaekjoonProblemService; import com.baektracker.domain.user.model.User; import com.baektracker.domain.user.repository.UserRepository; +import com.baektracker.domain.user.service.UserService; import com.baektracker.global.code.ApiResponseCode; import com.baektracker.global.exception.CustomException; import java.io.IOException; @@ -29,6 +30,7 @@ @RequiredArgsConstructor public class BaekjoonService { private final BaekjoonProblemService problemService; + private final UserService userService; private final SolvedProblemRepository solvedProblemRepository; private final UserRepository userRepository; private static final String gRecaptchaResponse = "03AFcWeA5mQSaepuaaR7U57_-xDPkMdoyyCHIw-GzIjiJwJ7dc85B0DUuLfj25b1CsGB_9zUOTXZ4P6eAbCq-ySOvJt1hLaCt_lXXzQPclXypdWyy4cOHmb9AmWrneWc6oDQ2eeGlWyg_ziSQlaLJgZDHAIE2T1h5NG5PXasZgvRv8q7bsOqv_tI4YqnjDJW0qyeDb-NveASxddrqUYRB0jKb2je7g-OBRafdGjNgwrhWP8Wgw7i_twMZjYdS2VOhkQtUwu2N88qbcNlt9sboHFKBHEDkcIE2cfDQ5dB7s7lYH5H1CJPGmqqNxAiF6A-ssV5z0Cpd8tg-d_clgwVDC-QsKZ6RQLJmAG0h5_IIXH-CsVoFeTTcodslWXcTJofA7eKN5OhGo8oOTjYUs7kjvhDJi4if6_ohi4oriDa1R_vm_R9mCH3bxSlXTWXBEkf37n6_PiVwfVYk5oucLgDORbhGllQ_7DEZr3wF3NGqlcL5y9ITpNH-cWJYpPA6ZA-_a_Pq_WbDgr5DTbwsN5j258djDcdD_vB4bElyaKWS812nNLFYTyFTyssfLiTE13w5k0qpm2L-gAbrOrJaxdIOZj6WMhKiLmm4K0pEZ-hEDNNQZ_UU835Ia95ArXHm5gl9ruzvF8PZBm4kKsosP3y3eRuSdT0NBALJ4eRGbxp56RPmMjMkqR7_19moOIgqN0p-q2cSRatqj9kH7qKeYNh7a39aE7kPSuX5XVIBVekrqKwPlAOdb2ueFS6iC5qa0xq9a4epJRhZfLZKI0-t6H6fdupuEQY54co-bEK0ypEMxYSS7VKNHMiwqsJfR0aH2P9V4TLIZlIpzHILioY9SzOwDMM6N26B_zpXh_etnVSG4Qya0HTVNC-45a_DVe4La9RM5oDKCzfyGhF1FUfFX-Zm9z7Ye7Fp-Z9r6hKDT_I7o4WlQC_OSVUCveuYFkeH5sEbxzJ6G6hVXgI0A-RZBHwKxdBH1MG1UizNWdw\n"; @@ -63,6 +65,13 @@ public int loadBaekjoonProblemStatus() { user.updateLastReadTime(LocalDateTime.now()); solvedProblemRepository.saveAll(scrappedProblems); count += scrappedProblems.size(); + + long correctCount = scrappedProblems.stream() + .filter(v -> v.getResultId().equals(4)).count(); + if (correctCount > 0) { + userService.updateUserInfoFromSolvedAc(user); + } + } } return count; diff --git a/src/main/java/com/baektracker/domain/problem/dto/WeeklyUserProgress.java b/src/main/java/com/baektracker/domain/problem/dto/WeeklyUserProgress.java index 500ad477..4a7318ed 100644 --- a/src/main/java/com/baektracker/domain/problem/dto/WeeklyUserProgress.java +++ b/src/main/java/com/baektracker/domain/problem/dto/WeeklyUserProgress.java @@ -6,7 +6,8 @@ public record WeeklyUserProgress( Long userId, Integer score, Boolean isWeekPass, + Integer increasedRating, List problems ) { -} \ No newline at end of file +} diff --git a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java index d613981f..3fc71cc7 100644 --- a/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java +++ b/src/main/java/com/baektracker/domain/problem/service/BaekjoonProblemService.java @@ -85,10 +85,10 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { return WeeklyUsersProgressResponse.from(progresses); } Map> userMap = new HashMap<>(); - Map weekPassMap = weeklyResults.stream() + Map weekPassMap = weeklyResults.stream() .collect(Collectors.toMap( WeeklyResult::getUserId, - this::isWeekPass + (v) -> v )); Map> coSolverMap = coSolvedUsers.stream() @@ -106,13 +106,14 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { userMap.put(userId, list); } for (User user : users) { + WeeklyResult wr = weekPassMap.get(user.getId()); boolean isWeekPass = false; if (weekPassMap.containsKey(user.getId())) { - isWeekPass = weekPassMap.get(user.getId()); + isWeekPass = isWeekPass(wr); } List userProblems = userMap.get(user.getId()); if (userProblems == null || userProblems.isEmpty()) { - progresses.add(new WeeklyUserProgress(user.getId(), 0, isWeekPass, null)); + progresses.add(new WeeklyUserProgress(user.getId(), 0, isWeekPass, 0, null)); continue; } List details = userProblems.stream() @@ -126,7 +127,8 @@ public WeeklyUsersProgressResponse getWeeklyUsersProgress(LocalDate fromDate) { .map(this::mapProblemToScore) .reduce(Integer::sum) .orElse(0); - progresses.add(new WeeklyUserProgress(user.getId(), score, isWeekPass, details)); + int increasedRating = user.getRating() - wr.getLastRating(); + progresses.add(new WeeklyUserProgress(user.getId(), score, isWeekPass, increasedRating, details)); } return WeeklyUsersProgressResponse.from(progresses); } diff --git a/src/main/java/com/baektracker/domain/test/controller/TestController.java b/src/main/java/com/baektracker/domain/test/controller/TestController.java index 0560f7da..3013ffc4 100644 --- a/src/main/java/com/baektracker/domain/test/controller/TestController.java +++ b/src/main/java/com/baektracker/domain/test/controller/TestController.java @@ -31,4 +31,11 @@ public ResponseEntity testUserLevelJob() { userService.updateUserInfoFromSolvedAc(); return ResponseEntity.noContent().build(); } + + @GetMapping("/job/streak") + public ResponseEntity testUpdateStreak(@RequestParam LocalDate date) { + weeklyResultService.updateUserStreaks(date); + return ResponseEntity.noContent().build(); + } + } diff --git a/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java b/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java index 0c882277..8f513d75 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java +++ b/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java @@ -53,6 +53,9 @@ public class WeeklyResult { @JoinColumn(name = "user_id") private User user; + @Column(name = "last_rating") + private Integer lastRating; + public Long getUserId() { return user.getId(); } diff --git a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java index 0ddd9dbb..5cf2965c 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java +++ b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java @@ -138,6 +138,7 @@ public void updateWeeklyResults(LocalDate fromDate, LocalDate toDate) { for (WeeklyResult weeklyResult : weeklyResults) { int score = userScoreMap.getOrDefault(weeklyResult.getUserId(), 0); int fine = 0; + weeklyResult.setLastRating(weeklyResult.getUser().getRating()); weeklyResult.setScore(score); if (weeklyResult.getState() == WeeklyResultState.None) { WeeklyResultState state = getWeeklyResultState(score); From 213f827506aa9c2156c22551f2b2e48fc7e5d324 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Tue, 7 Apr 2026 18:02:00 +0900 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=EB=A0=88=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 51 +++++-------------- src/main/frontend/package-lock.json | 17 ------- .../weekly_result/model/WeeklyResult.java | 1 + .../service/WeeklyResultService.java | 3 +- .../global/job/RecordWeeklyResultJob.java | 2 +- .../resources/mapper/WeeklyResultMapper.xml | 43 +++++++++------- 6 files changed, 42 insertions(+), 75 deletions(-) diff --git a/build.gradle b/build.gradle index 02d8e48e..c0ff3e17 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ plugins { id 'java' id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' } repositories { @@ -23,12 +24,6 @@ dependencies { implementation libs.org.mybatis.spring.boot.mybatis.spring.boot.starter runtimeOnly libs.org.springframework.boot.spring.boot.devtools - // Test - testImplementation libs.org.junit.jupiter.junit.jupiter - testImplementation libs.org.mockito.mockito.junit.jupiter - testImplementation libs.org.assertj.assertj.core - - // JPA implementation libs.org.springframework.boot.spring.boot.starter.data.jpa @@ -83,57 +78,38 @@ tasks.withType(Javadoc) { options.encoding = 'UTF-8' } -tasks.test { - useJUnitPlatform() -} - def frontendDir = "$projectDir/src/main/frontend" -def querydslDir = layout.buildDirectory.dir("generated/querydsl").get().asFile -def reactBuildDir = layout.buildDirectory.dir("frontend").get().asFile sourceSets { main { - java { - srcDir querydslDir - } resources { srcDirs = ["$projectDir/src/main/resources"] } } } -// ✅ (추가) Q 클래스 생성 위치를 querydslDir로 고정 -tasks.withType(JavaCompile).configureEach { - options.generatedSourceOutputDirectory = querydslDir +bootJar { + dependsOn "copyReactBuildFiles" } -tasks.named("jar") { - dependsOn("copyReactBuildFiles") - from(reactBuildDir) { - into("static") // classpath:/static/ 로 들어감 - } +processResources { + dependsOn "copyReactBuildFiles" } -tasks.named("bootJar") { - dependsOn("copyReactBuildFiles") - from(reactBuildDir) { - into("static") - } -} - -tasks.register('installReact', Exec) { +task installReact(type: Exec) { workingDir "$frontendDir" inputs.dir "$frontendDir" group = BasePlugin.BUILD_GROUP if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) { - commandLine 'npm.cmd', 'ci' + commandLine "npm.cmd", "audit", "fix" + commandLine 'npm.cmd', 'install' } else { - commandLine 'npm', 'ci' + commandLine "npm", "audit", "fix" + commandLine 'npm', 'install' } } -tasks.register('buildReact', Exec) { - environment "CI", "false" +task buildReact(type: Exec) { dependsOn "installReact" workingDir "$frontendDir" inputs.dir "$frontendDir" @@ -144,8 +120,9 @@ tasks.register('buildReact', Exec) { commandLine "npm", "run-script", "build" } } -tasks.register('copyReactBuildFiles', Copy) { + +task copyReactBuildFiles(type: Copy) { dependsOn "buildReact" from "$frontendDir/build" - into reactBuildDir + into "$buildDir/resources/main/static" } diff --git a/src/main/frontend/package-lock.json b/src/main/frontend/package-lock.json index 21b53200..60db1fb7 100644 --- a/src/main/frontend/package-lock.json +++ b/src/main/frontend/package-lock.json @@ -16487,23 +16487,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, - "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java b/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java index 8f513d75..36dd15e0 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java +++ b/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java @@ -83,6 +83,7 @@ public static WeeklyResult from(LocalDate date, User user) { .score(0) .state(WeeklyResultState.None) .fine(0) + .lastRating(user.getRating()) .user(user) .build(); } diff --git a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java index 5cf2965c..5d873534 100644 --- a/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java +++ b/src/main/java/com/baektracker/domain/weekly_result/service/WeeklyResultService.java @@ -115,6 +115,7 @@ public void updateWeekPass(WeekPassRequestDto dto) { weeklyResult.setState(state); } + @Transactional public void insertInitialWeeklyResults(LocalDate date) { List users = userRepository.findAll(); List weeklyResults = users.stream() @@ -138,7 +139,7 @@ public void updateWeeklyResults(LocalDate fromDate, LocalDate toDate) { for (WeeklyResult weeklyResult : weeklyResults) { int score = userScoreMap.getOrDefault(weeklyResult.getUserId(), 0); int fine = 0; - weeklyResult.setLastRating(weeklyResult.getUser().getRating()); +// weeklyResult.setLastRating(weeklyResult.getUser().getRating()); weeklyResult.setScore(score); if (weeklyResult.getState() == WeeklyResultState.None) { WeeklyResultState state = getWeeklyResultState(score); diff --git a/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java b/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java index e7c841fd..a3aad17a 100644 --- a/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java +++ b/src/main/java/com/baektracker/global/job/RecordWeeklyResultJob.java @@ -37,6 +37,6 @@ public void execute(JobExecutionContext jobExecutionContext) throws JobExecution weeklyResultService.insertInitialWeeklyResults(nextWeekDate); // 각 사용자별 스트릭 갱신 - weeklyResultService.updateUserStreaks(fromDate); + weeklyResultService.updateUserStreaks(nextWeekDate); } } diff --git a/src/main/resources/mapper/WeeklyResultMapper.xml b/src/main/resources/mapper/WeeklyResultMapper.xml index 8d879ecb..742e58dd 100644 --- a/src/main/resources/mapper/WeeklyResultMapper.xml +++ b/src/main/resources/mapper/WeeklyResultMapper.xml @@ -4,25 +4,30 @@ "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> - # 해당 쿼리를 실행하려는 날짜 기준, 이전 주까지의 스트릭을 갱신 -# 이번 주의 주간 결과는 포함 안함 - set @start_date := (select date_sub(#{date}, interval weekday(#{date}) day)); + WITH RECURSIVE + base AS (SELECT u.id AS user_id, + DATE_SUB(#{date}, INTERVAL WEEKDAY(#{date}) DAY) AS dt, + 0 AS cnt, + 0 AS state + FROM users u - update users u - set streak=(with recursive base as (select @start_date as dt, - 0 as cnt, - 0 as state - union all - select date_sub(dt, interval 1 week) as dt, - cnt + (ws.state = 1) as cnt, - ws.state as state - from base - left join weekly_results ws - on ws.year_week = - date_format(date_sub(dt, interval 1 week), '%Y-%u') and - ws.user_id = u.id - where ws.state in (1, 3)) - select max(cnt) - from base) + UNION ALL + + SELECT b.user_id, + DATE_SUB(b.dt, INTERVAL 1 WEEK) AS dt, + b.cnt + (ws.state = 1) AS cnt, + ws.state AS state + FROM base b + LEFT JOIN weekly_results ws + ON ws.user_id = b.user_id + AND ws.year_week = DATE_FORMAT(DATE_SUB(b.dt, INTERVAL 1 WEEK), '%Y-%u') + WHERE ws.state IN (1, 3)), + agg AS (SELECT user_id, MAX(cnt) AS streak + FROM base + GROUP BY user_id) + UPDATE users u + JOIN agg ON agg.user_id = u.id + SET u.streak = agg.streak + From 212f612be9604158fb292504152c35cb8780ef11 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Tue, 7 Apr 2026 18:17:57 +0900 Subject: [PATCH 5/7] =?UTF-8?q?fix:=20(CI=20=EC=8B=A4=ED=8C=A8)=20ESLint?= =?UTF-8?q?=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../frontend/src/js/setup/utils/ObjectUtil.js | 77 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index 99dc1614..c71403fe 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,4 @@ src/main/resources/application-dev.yml .env *.tar +/.claude/ diff --git a/src/main/frontend/src/js/setup/utils/ObjectUtil.js b/src/main/frontend/src/js/setup/utils/ObjectUtil.js index 7d9af6e9..99b2433d 100644 --- a/src/main/frontend/src/js/setup/utils/ObjectUtil.js +++ b/src/main/frontend/src/js/setup/utils/ObjectUtil.js @@ -1,47 +1,46 @@ - export const ObjectUtils = { - isEmpty: (value)=>{ + isEmpty: (value) => { const result = value === null || value === undefined || value === '' || value.length === 0; - if(result){ + if (result) { return true; } - if(typeof value === 'object'){ - return Object.keys(value).length === 0; + if (typeof value === 'object') { + return Object.keys(value).length === 0; } return false; }, - isEmptyArray: (array)=>{ - return !array || typeof array !== "object" || array.length === 0 - }, - isEmptyMap(map){ - for(const key in map){ - if(!ObjectUtils.isEmpty(map[key])){ - return false; - } - } - return true; - }, - convertBooleanArrayToString(arr){ - let str = ''; - arr.map((value)=>{ - str += (value) ? '1' : '0'; - }) - return str; - }, - transposeArray: (arr) => arr.reduce( - (result, row) => row.map((_, i) => [...(result[i] || []), row[i]]), - [] - ), - toggleOf, - varToString, -} - -function toggleOf(arr, index){ - for(let i in arr){ - arr[i] = (i == index); - } + // isEmptyArray: (array)=>{ + // return !array || typeof array !== "object" || array.length === 0 + // }, + // isEmptyMap(map){ + // for(const key in map){ + // if(!ObjectUtils.isEmpty(map[key])){ + // return false; + // } + // } + // return true; + // }, + // convertBooleanArrayToString(arr){ + // let str = ''; + // arr.map((value)=>{ + // str += (value) ? '1' : '0'; + // }) + // return str; + // }, + // transposeArray: (arr) => arr.reduce( + // (result, row) => row.map((_, i) => [...(result[i] || []), row[i]]), + // [] + // ), + // toggleOf, + // varToString, } - -function varToString(_var){ - return Object.keys(_var)[0]; -} \ No newline at end of file +// +// function toggleOf(arr, index){ +// for(let i in arr){ +// arr[i] = (i == index); +// } +// } +// +// function varToString(_var){ +// return Object.keys(_var)[0]; +// } From f0cf6d35dc8576383bf9ae41a27608be440beab9 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Tue, 7 Apr 2026 18:31:30 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20esLint=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/frontend/package.json | 7 +++- src/main/frontend/src/App.js | 41 +++++++++---------- .../frontend/src/js/components/Header.jsx | 38 ++++++++--------- .../src/js/components/MarkedProblemItem.jsx | 4 +- .../src/js/components/UserProgress.jsx | 2 + .../src/js/components/WeekProblems.jsx | 3 +- .../src/js/modal/FineReceiptModal.jsx | 2 + src/main/frontend/src/js/modal/LayerModal.js | 15 ++++--- 8 files changed, 62 insertions(+), 50 deletions(-) diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 4a8a7b90..389ca802 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -28,7 +28,12 @@ "extends": [ "react-app", "react-app/jest" - ] + ], + "rules": { + "react-hooks/exhaustive-deps": "off", + "array-callback-return": "off", + "eqeqeq": "off" + } }, "browserslist": { "production": [ diff --git a/src/main/frontend/src/App.js b/src/main/frontend/src/App.js index ec025d96..642e2741 100644 --- a/src/main/frontend/src/App.js +++ b/src/main/frontend/src/App.js @@ -1,5 +1,4 @@ import React from 'react'; -import logo from './logo.svg'; import './App.css'; import {Helmet} from "react-helmet-async"; import ModalContainer from "./js/setup/modal/ModalContainer"; @@ -9,28 +8,28 @@ import {Route, Routes} from "react-router-dom"; import MainPage from "./js/components/MainPage"; function App() { - // 전역 툴팁 함수들 등록 - React.useEffect(() => { - registerGlobalTooltip(); - }, []); + // 전역 툴팁 함수들 등록 + React.useEffect(() => { + registerGlobalTooltip(); + }, []); - return ( -
- - Baektracker - - - - - - - }> + return ( +
+ + Baektracker + + + + + + + }> - - - -
- ); +
+
+
+
+ ); } export default App; diff --git a/src/main/frontend/src/js/components/Header.jsx b/src/main/frontend/src/js/components/Header.jsx index 1211a11c..15d6df8e 100644 --- a/src/main/frontend/src/js/components/Header.jsx +++ b/src/main/frontend/src/js/components/Header.jsx @@ -26,7 +26,7 @@ export function Header({fromDate, toDate, setFromDate, setToDate}) { }) } - const setWeekDates = (date: Date) => { + const setWeekDates = (date) => { const fd = DateUtils.getFirstDateOfWeek(date); const td = DateUtils.getLastDateOfWeek(date); setFromDate(DateUtils.dateToStringYYMMdd(fd)) @@ -93,21 +93,21 @@ export function Header({fromDate, toDate, setFromDate, setToDate}) { ) } -// FineInfo 컴포넌트 -const FineInfo = ({totalFine, onShowReceipt, className}) => { - const formatCurrency = (amount) => { - return new Intl.NumberFormat("ko-KR").format(amount) - } - - return ( -
-
- 누적 벌금 - ₩{formatCurrency(totalFine)} -
- -
- ) -} +// // FineInfo 컴포넌트 +// const FineInfo = ({totalFine, onShowReceipt, className}) => { +// const formatCurrency = (amount) => { +// return new Intl.NumberFormat("ko-KR").format(amount) +// } +// +// return ( +//
+//
+// 누적 벌금 +// ₩{formatCurrency(totalFine)} +//
+// +//
+// ) +// } diff --git a/src/main/frontend/src/js/components/MarkedProblemItem.jsx b/src/main/frontend/src/js/components/MarkedProblemItem.jsx index 072beca4..5a4cbc5c 100644 --- a/src/main/frontend/src/js/components/MarkedProblemItem.jsx +++ b/src/main/frontend/src/js/components/MarkedProblemItem.jsx @@ -29,7 +29,7 @@ export function MarkedProblemItem({problem, index}) { window.open(`https://www.acmicpc.net/problem/${problem.problemId}`, "_blank") }} className={cm(`${styles.userProgressProblemItem} ${problem.solved ? styles.userProgressSolved : styles.userProgressUnsolved}`, - `${problem.is_shared_problem == 1 && styles.is_shared_problem}`)} + `${problem.is_shared_problem === 1 && styles.is_shared_problem}`)} onMouseEnter={tooltip.onMouseEnter} onMouseLeave={tooltip.onMouseLeave} > @@ -44,4 +44,4 @@ export function MarkedProblemItem({problem, index}) { ) -} \ No newline at end of file +} diff --git a/src/main/frontend/src/js/components/UserProgress.jsx b/src/main/frontend/src/js/components/UserProgress.jsx index 775cd4b9..d12c95d2 100644 --- a/src/main/frontend/src/js/components/UserProgress.jsx +++ b/src/main/frontend/src/js/components/UserProgress.jsx @@ -24,11 +24,13 @@ export function UserProgress({fromDate, toDate}) { const threeDotsRefs = useRef({}) const longPressTimer = useRef(null) + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { getAllUsers() initLoad(); }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { getWeeklyUsersProgress(); }, [fromDate, toDate]); diff --git a/src/main/frontend/src/js/components/WeekProblems.jsx b/src/main/frontend/src/js/components/WeekProblems.jsx index 425ac9b9..84d5947a 100644 --- a/src/main/frontend/src/js/components/WeekProblems.jsx +++ b/src/main/frontend/src/js/components/WeekProblems.jsx @@ -16,6 +16,7 @@ export function WeekProblems({fromDate, toDate}) { const [weeklyProblemInputs, setWeeklyProblemInputs] = useState([-1, -1, -1]) + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { getWeeklyProblems(); }, [fromDate, toDate]); @@ -112,4 +113,4 @@ export function WeekProblems({fromDate, toDate}) { ) -} \ No newline at end of file +} diff --git a/src/main/frontend/src/js/modal/FineReceiptModal.jsx b/src/main/frontend/src/js/modal/FineReceiptModal.jsx index b2fde467..8f69d3ac 100644 --- a/src/main/frontend/src/js/modal/FineReceiptModal.jsx +++ b/src/main/frontend/src/js/modal/FineReceiptModal.jsx @@ -19,10 +19,12 @@ export function FineReceiptModal(props) { const [totalSum, setTotalSum] = useState(0) const [fold, setFold] = useState(true) + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { getTotalFine() }, []); + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { getMonthFine() // getWeeklyResult(); diff --git a/src/main/frontend/src/js/modal/LayerModal.js b/src/main/frontend/src/js/modal/LayerModal.js index 45cc39db..aff7b257 100644 --- a/src/main/frontend/src/js/modal/LayerModal.js +++ b/src/main/frontend/src/js/modal/LayerModal.js @@ -7,15 +7,18 @@ import {ScrollUtils} from "../utils/ScrollUtils"; Usage ex) */ -export const LayerModal = ({modalRef, scrollable, children, top, left, width, backgroundColor, - height, windowBlocked, minWidth, maxWidth, minHeight, maxHeight, paddingBottom}) => { +export const LayerModal = ({ + modalRef, scrollable, children, top, left, width, backgroundColor, + height, windowBlocked, minWidth, maxWidth, minHeight, maxHeight, paddingBottom + }) => { const [fadeIn, setFadeIn] = useState(false); const scrollRef = useRef(null) + // eslint-disable-next-line react-hooks/exhaustive-deps useEffect(() => { let prevScrollY = null; - if(!scrollable){ + if (!scrollable) { prevScrollY = ScrollUtils.preventScroll(scrollRef.current.body); } const timer = setTimeout(() => { @@ -23,7 +26,7 @@ export const LayerModal = ({modalRef, scrollable, children, top, left, width, ba }, 100) return () => { - if(prevScrollY){ + if (prevScrollY) { ScrollUtils.allowScroll(scrollRef.current.body, prevScrollY) } prevScrollY = null; @@ -34,10 +37,10 @@ export const LayerModal = ({modalRef, scrollable, children, top, left, width, ba useEffect(() => { let prevScrollY = null; - if(windowBlocked){ + if (windowBlocked) { prevScrollY = ScrollUtils.preventScroll(document.body); } - return ()=>{ + return () => { ScrollUtils.allowScroll(document.body, prevScrollY) } }, []); From a1e03d71c4640c77f2716951ab810a59251e8850 Mon Sep 17 00:00:00 2001 From: chanrhan Date: Tue, 7 Apr 2026 18:35:55 +0900 Subject: [PATCH 7/7] =?UTF-8?q?chore:=20git=20actions=20CI=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD=20(CI=3Dfalse)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 2 ++ src/main/frontend/package.json | 7 +------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 027089f6..8167bb73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,4 +39,6 @@ jobs: run: chmod +x gradlew - name: Build (includes React build) + env: + CI: false run: ./gradlew clean build --no-daemon diff --git a/src/main/frontend/package.json b/src/main/frontend/package.json index 389ca802..4a8a7b90 100644 --- a/src/main/frontend/package.json +++ b/src/main/frontend/package.json @@ -28,12 +28,7 @@ "extends": [ "react-app", "react-app/jest" - ], - "rules": { - "react-hooks/exhaustive-deps": "off", - "array-callback-return": "off", - "eqeqeq": "off" - } + ] }, "browserslist": { "production": [