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/.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/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/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/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/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 7692d5e2..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]);
@@ -248,6 +250,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 +269,16 @@ export function UserProgress({fromDate, toDate}) {
{user.rating}
+ onMouseLeave={tooltip.onMouseLeave}>{user.rating}
+ {
+ increasedRating && <>
+
+ {increasedRating}
+ >
+ }
+
+
{isWeekPass ?
PASS : ''}
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)
}
}, []);
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];
+// }
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/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/domain/weekly_result/model/WeeklyResult.java b/src/main/java/com/baektracker/domain/weekly_result/model/WeeklyResult.java
index 0c882277..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
@@ -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();
}
@@ -80,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 0ddd9dbb..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,6 +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.setScore(score);
if (weeklyResult.getState() == WeeklyResultState.None) {
WeeklyResultState state = getWeeklyResultState(score);
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);
}
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/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
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
+