Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
142 commits
Select commit Hold shift + click to select a range
cbde362
refactor: update application configuration files for logging and envi…
jbh010204 Aug 7, 2025
fe516dc
refactor: update application configuration files for logging and envi…
jbh010204 Aug 7, 2025
6e365b9
refactor: banner valid 추가
chominju02 Aug 7, 2025
510da44
refactor: AdminDashboard response 수정
chominju02 Aug 7, 2025
84a88f2
refactor: AdminDashboard count 함수 구현
chominju02 Aug 7, 2025
cc3e589
refactor: 신청 중복 검증 로직 이동
chominju02 Aug 7, 2025
36f5a58
refactor: 신청 약관 동의 체크 validator 구현
chominju02 Aug 7, 2025
c9f0463
refactor: 신청 응시과목 notNull 추가
chominju02 Aug 7, 2025
77ec41d
refactor: event null 체크 및 duration startDate 변경
chominju02 Aug 7, 2025
59a4aee
refactor: recommendation 변수 변경 및 notBlank 추가
chominju02 Aug 7, 2025
da821bd
refactor: profile null 체크 추가
chominju02 Aug 7, 2025
eb090a8
refactor: 신청 multi-insert 시 deleted 옵션 추가, 과목 삭제 시 p.deleted 옵션 검사 추가
chominju02 Aug 7, 2025
f883a22
refactor: 에러 코드 추가
chominju02 Aug 7, 2025
37fc049
refactor: request valid 검증 및 lunch 등록 여부 검증 로직 추가
chominju02 Aug 7, 2025
1562bbc
remove: 주석 제거
chominju02 Aug 7, 2025
af910d3
refactor: examTicketImgUrl 생성 함수 추가
chominju02 Aug 7, 2025
b9e1687
refactor: add default value for Discord webhook URL and prevent notif…
jbh010204 Aug 8, 2025
bc960b8
Merge branch 'develop' of https://github.com/mosu-dev/mosu-server int…
chominju02 Aug 8, 2025
ebfa48d
refactor: 관리자 DashBoardResponse 수정
chominju02 Aug 8, 2025
f279815
refactor: whiteList 수정
chominju02 Aug 8, 2025
795a6e9
chore: dev.mosuedu.com:3000 cors 설정 추가
toothlessdev Aug 9, 2025
6ea148a
feat: update self-deploy workflow to include JDK 21 setup and Docker …
polyglot-k Aug 9, 2025
9e44526
MOSU fix: 회원가입 정보 추가
wlgns12370 Aug 9, 2025
b61bb05
MOSU-258 refactor: 경합이 발생할 수 있는 상황을 방지하기 위해 filter 로직 수정
jbh010204 Aug 9, 2025
50cba22
MOSU-258 refactor: RequestCounter 객체 내애서 AtomicInteger사용
jbh010204 Aug 9, 2025
553dd16
MOSU-258 refactor: LEVEL_0 Duration 값 변경
jbh010204 Aug 9, 2025
5bc6c38
MOSU-258 test: IP 차단 로직 동시성 안전성 확보 및 LEVEL_0 페널티 시간 조정
jbh010204 Aug 9, 2025
6341200
MOSU-258 refactor: 리뷰 내용 반영
jbh010204 Aug 9, 2025
921b2ef
MOSU feat: 이용약관 정보 추가
wlgns12370 Aug 9, 2025
5d7fb7b
Merge pull request #263 from mosu-dev/refactor/mosu-258
jbh010204 Aug 9, 2025
46e7c7a
MOSU-264 refactor: Dirty Checking을 위한 Transaction 분리
wlgns12370 Aug 9, 2025
34a04b5
MOSU-264 test: 마케팅 동의 여부 파싱 테스트
wlgns12370 Aug 9, 2025
6bd062b
MOSU-264 test: 테스트 중복 로직 삭제
wlgns12370 Aug 9, 2025
caa0723
MOSU-264 refactor: null 검증 로직 제거
wlgns12370 Aug 9, 2025
df0354e
MOSU-264 test: @Nested 제거
wlgns12370 Aug 9, 2025
572ddb0
MOSU-264 feat: DDL 구현
wlgns12370 Aug 9, 2025
b680c70
Merge pull request #265 from mosu-dev/refactor/mosu-264
wlgns12370 Aug 9, 2025
bc7d613
MOSU-267 feat: cache 적용할 도메인들 enum으로 정의
jbh010204 Aug 9, 2025
40f61d0
MOSU-267 feat: LocalCacheManager 구현
jbh010204 Aug 9, 2025
a0a4473
MOSU fix: DDL 삭제
wlgns12370 Aug 9, 2025
c5be4ad
MOSU-267 fix: 변수명 오타 수정
jbh010204 Aug 10, 2025
a0fd7db
MOSU-267 feat: 공지 도메인 캐싱 적용
jbh010204 Aug 10, 2025
6a43347
MOSU feat: data.sql 추가
wlgns12370 Aug 10, 2025
53b4e11
feat: update s3Key column definition to TEXT in File entity
polyglot-k Aug 10, 2025
1d40637
feat: remove CUSTOMER_KEY_CHECK endpoint from Whitelist
polyglot-k Aug 10, 2025
febbe32
refactor: remove unnecessary @NotBlank annotation from PhoneNumberPat…
jbh010204 Aug 10, 2025
e784335
Merge branch 'develop' into refactor/mosu-267
jbh010204 Aug 10, 2025
50915bc
Merge pull request #269 from mosu-dev/refactor/mosu-267
jbh010204 Aug 10, 2025
24f6e2e
Merge branch 'feat/mosu-249' of https://github.com/mosu-dev/mosu-serv…
polyglot-k Aug 10, 2025
9934b7d
MOSU-270 refactor: 전화번호로 사용자 검증
wlgns12370 Aug 10, 2025
2521b11
MOSU-270 refactor: 성별 응답 Custom
wlgns12370 Aug 10, 2025
03357a6
feat: reorganize application configuration files and add base profile
polyglot-k Aug 10, 2025
affed22
feat: update deployment scripts to use base environment variables and…
polyglot-k Aug 10, 2025
0a9d3ab
MOSU-273 feat: 등록된 전화번호 확인 구현
wlgns12370 Aug 10, 2025
6527121
MOSU-273 feat: 등록된 전화번호 확인 Whitelist 추가
wlgns12370 Aug 10, 2025
7f0bf73
feat: implement updateInquiry method for modifying existing inquiries
polyglot-k Aug 10, 2025
3dbbf21
feat: initialize DiscordNotifier with webhook URL and prevent notific…
polyglot-k Aug 10, 2025
da95311
feat: update DiscordNotifier to use blank check for webhook URL confi…
polyglot-k Aug 10, 2025
3f0a973
feat: increase title size limit in InquiryUpdateRequest to 300 charac…
polyglot-k Aug 10, 2025
08a58cd
feat: update logging configuration to specify log file path in applic…
polyglot-k Aug 10, 2025
5e01c1f
Merge pull request #257 from mosu-dev/feat/mosu-249
polyglot-k Aug 10, 2025
867e96d
feat: use sudo for Docker build and push commands in self-deploy YAML
polyglot-k Aug 10, 2025
533528e
feat: add NotBlankPhoneNumberPattern annotation for phone number vali…
jbh010204 Aug 10, 2025
1dad3f7
feat: add custom exception for invalid gender value
jbh010204 Aug 10, 2025
735cbf3
feat: replace PhoneNumberPattern with NotBlankPhoneNumberPattern for …
jbh010204 Aug 10, 2025
8a58f8a
fix: update PhoneNumberPattern regex to allow empty phone number
jbh010204 Aug 10, 2025
7a89399
fix: remove typo in error code for invalid gender value
jbh010204 Aug 10, 2025
301188d
fix: remove typo in error code for invalid gender value
jbh010204 Aug 10, 2025
8e786f0
feat: add authentication exception handling for exam application endp…
jbh010204 Aug 10, 2025
a5226a9
fix: remove caching from getNotices method in NoticeService
jbh010204 Aug 10, 2025
24695b6
feat: remove duplicate profiles configuration in application YAML
polyglot-k Aug 10, 2025
ecd6305
Merge branch 'develop' of https://github.com/mosu-dev/mosu-server int…
polyglot-k Aug 10, 2025
1359898
feat: move configuration imports to application-base.yml
polyglot-k Aug 10, 2025
2c7a1a2
feat: add exception handling for validation errors in GlobalException…
jbh010204 Aug 10, 2025
20381b4
feat: add custom validation for login ID format in UserController
jbh010204 Aug 10, 2025
5de298d
MOSU-273 fix: DataIntegrityViolationException 처리
wlgns12370 Aug 10, 2025
56d4ffe
MOSU-273 fix: 휴대폰 번호 확인 엔드 포인트 변경
wlgns12370 Aug 10, 2025
445393a
Merge remote-tracking branch 'origin/develop' into feature/mosu-273
wlgns12370 Aug 10, 2025
c0fa37c
Merge pull request #275 from mosu-dev/refactor/mosu-270
wlgns12370 Aug 10, 2025
348ff8a
Merge remote-tracking branch 'origin/develop' into feature/mosu-273
wlgns12370 Aug 10, 2025
e6b5d58
MOSU-273 fix: 검증 어노테이션 import 추가
wlgns12370 Aug 10, 2025
0373d2b
MOSU-273 refactor: API 연동을 위한 임시 secure false
wlgns12370 Aug 10, 2025
4cba953
Merge pull request #274 from mosu-dev/feature/mosu-273
wlgns12370 Aug 10, 2025
95e6ff8
MOSU refactor: 쿠키 domain dev.mosuedu.com 으로 변경
wlgns12370 Aug 10, 2025
a63a58b
MOSU refactor: 쿠키 domain .mosuedu.com 으로 변경
wlgns12370 Aug 10, 2025
91ddf3a
feat: add method to calculate refund amount in RefundService
polyglot-k Aug 10, 2025
f783688
feat: add endpoint to retrieve refund amount in RefundController
polyglot-k Aug 10, 2025
13a6ca9
feat: add RefundAmountResponse DTO for refund amount response
polyglot-k Aug 10, 2025
96f7f5f
MOSU refactor: 쿠키 prod 설정
wlgns12370 Aug 10, 2025
22a2b70
MOSU refactor: set secure flag to false for development cookies
wlgns12370 Aug 10, 2025
a01febf
feat: enhance inquiry search functionality with answer details and ne…
jbh010204 Aug 10, 2025
da43546
feat: 내 문의 내역 조회 시 답변도 같이 반환
jbh010204 Aug 10, 2025
1e4f080
refactor: 리뷰 내용 반영
jbh010204 Aug 10, 2025
8af2cdb
Merge pull request #290 from mosu-dev/refactor/mosu-287
jbh010204 Aug 10, 2025
f8e1232
refactor: set secure flag to true for development cookies
wlgns12370 Aug 10, 2025
76d795e
Merge remote-tracking branch 'origin/develop' into develop
wlgns12370 Aug 10, 2025
ee528cf
refactor: develop 쿠기 secure false전환
jbh010204 Aug 10, 2025
195f97e
feat: add deleteByExamApplicationId method to PaymentJpaRepository an…
polyglot-k Aug 10, 2025
3bc3eef
Merge branch 'develop' of https://github.com/mosu-dev/mosu-server int…
polyglot-k Aug 10, 2025
b7d6fb9
MOSU refactor: set secure flag to true for development cookies
wlgns12370 Aug 10, 2025
2c99aaa
refactor: s3key 길이 최대 255 자 넘지 않도록 설정
jbh010204 Aug 10, 2025
ff671d9
refactor: remove CUSTOMER_KEY_CHECK from whitelist
wlgns12370 Aug 10, 2025
d0157b1
refactor: 이벤트 등록 시 파일 temp -> update로 tag변경
jbh010204 Aug 10, 2025
05927d7
MOSU refactor: update lunch option text to "도시락 X"
wlgns12370 Aug 10, 2025
25ae7f0
feat: 쿠키 검증 제약 추가
jbh010204 Aug 10, 2025
c0576ab
feat: develop 쿠키 secure false 변경
jbh010204 Aug 10, 2025
5264184
feat: 쿠키 검증 api 구현
jbh010204 Aug 10, 2025
5d270db
Merge pull request #298 from mosu-dev/refactor/mosu-293
jbh010204 Aug 10, 2025
8d2f64f
MOSU-297 feat: 카카오 로그인 실패 로직 구현
wlgns12370 Aug 10, 2025
9b6d103
Merge pull request #300 from mosu-dev/feature/mosu-297
wlgns12370 Aug 10, 2025
b01684d
MOSU refactor: set secure flag to false for development cookies
wlgns12370 Aug 10, 2025
f410cab
MOSU refactor: set secure flag to true for development cookies
wlgns12370 Aug 10, 2025
9acbab2
refactor: update cookie creation methods to use development cookies w…
wlgns12370 Aug 10, 2025
28cf726
refactor: set secure flag to true for development cookies
wlgns12370 Aug 10, 2025
3c34ef3
MOSU refactor: profile null 체크 추가
wlgns12370 Aug 10, 2025
4db5506
MOSU refactor: enhance OAuth2 login failure handling with redirect an…
wlgns12370 Aug 10, 2025
9716879
feat: update refund processing to change payment status to ABORTED an…
polyglot-k Aug 11, 2025
37b8e9c
feat: add method to find payments by status and exam application ID i…
polyglot-k Aug 11, 2025
04fe08c
feat: remove payment deletion by exam application ID after refund not…
polyglot-k Aug 11, 2025
0fc0ead
feat: update method to find exam application by application ID in Cre…
polyglot-k Aug 11, 2025
09a3dea
feat: update deposit status to DONE in DepositSuccessEventHandler
polyglot-k Aug 11, 2025
c8c936c
feat: enhance admin dashboard to count aborted refunds and update ban…
polyglot-k Aug 11, 2025
3a9d195
feat: add agreement validation method to AgreementRequest
polyglot-k Aug 11, 2025
5951dc1
feat: remove existsByUserIdAndExamIds query from ApplicationJpaReposi…
polyglot-k Aug 11, 2025
f85c830
feat: update subjects field to use List instead of Set in Application…
polyglot-k Aug 11, 2025
a46f2ac
feat: enhance application validation by adding agreement check and im…
polyglot-k Aug 11, 2025
cd6da2b
feat: remove commented code from BannerRequest to improve clarity
polyglot-k Aug 11, 2025
c825149
feat: rename refundCounts to refundAbortedCounts in DashBoardResponse…
polyglot-k Aug 11, 2025
26e233c
feat: update DurationRequest to use current date for startDate in toD…
polyglot-k Aug 11, 2025
201b2b8
feat: simplify education description in EditProfileRequest schema
polyglot-k Aug 11, 2025
ec85327
feat: add error codes for terms agreement and exam application requir…
polyglot-k Aug 11, 2025
c72ab75
feat: improve null handling for attachment and duration in EventReque…
polyglot-k Aug 11, 2025
e9372d1
feat: add deleted flag handling in ExamApplication repository queries
polyglot-k Aug 11, 2025
29053d7
feat: refactor exam ticket image URL retrieval and enhance request va…
polyglot-k Aug 11, 2025
a1d61d4
feat: enhance lunch availability check to include lunch price validation
polyglot-k Aug 11, 2025
d7ecbe2
feat: add validation annotations for required fields in ExamRequest
polyglot-k Aug 11, 2025
d3d563a
feat: add exam date validation in register method of ExamService
polyglot-k Aug 11, 2025
64800d9
feat: add filter for deleted payments in deleteExamSubjectsWithDonePa…
polyglot-k Aug 11, 2025
d2a4f2d
feat: remove logging from GetApplicationsStepProcessor
polyglot-k Aug 11, 2025
716093d
feat: rename recommendee fields to recommender in projection and DTO
polyglot-k Aug 11, 2025
14dcdf9
feat: add method to count users by role exclusion in UserJpaRepository
polyglot-k Aug 11, 2025
4084362
feat: simplify user existence check in SignUpAccountStepProcessor
polyglot-k Aug 11, 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
11 changes: 9 additions & 2 deletions .github/workflows/docker-depoly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ jobs:
script: |
cd /home/ubuntu/mosu

echo "${{ secrets.ENV_BLUE }}" > .env.blue
echo "${{ secrets.ENV_GREEN }}" > .env.green
echo "${{ secrets.ENV_BASE }}" > .env
echo "${{ secrets.ENV_BASE }}" > .env.blue
echo "${{ secrets.ENV_BASE }}" > .env.green

echo "${{ secrets.ENV }}" >> .env
echo "${{ secrets.ENV }}" >> .env.blue
echo "${{ secrets.ENV }}" >> .env.green

echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env
echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env.blue
echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env.green

./deploy.sh
54 changes: 51 additions & 3 deletions .github/workflows/self-depoly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,68 @@ name: Docker CI/CD - Deploy
on:
workflow_dispatch:
branches:
- test
- develop
jobs:
deploy:
runs-on: self-hosted

steps:
- name: Checkout source
uses: actions/checkout@v3

- name: Set up JDK 21
uses: actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'

- name: Cache Gradle files
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
gradle-${{ runner.os }}-

- name: Clone external repo with jar into libs/
run: |
mkdir -p libs
git clone https://x-access-token:${{ secrets.GH_PAT }}@github.com/mosu-dev/mosu-kmc-jar.git temp-jar
cp temp-jar/*.jar libs/

- name: Build with Gradle
run: ./gradlew build -x test

- name: Log in to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Build Docker image
run: sudo docker build -t kangtaehyun1107/mosu-server:${{ github.sha }} .
working-directory:
- name: Push Docker image
run: sudo docker push kangtaehyun1107/mosu-server:${{ github.sha }}

- name: Deploy via SSH
run: |
cd ~/mosu-server

echo "${{ secrets.TEST_ENV_BLUE }}" > .env.blue
echo "${{ secrets.TEST_ENV_GREEN }}" > .env.green
echo "${{ secrets.ENV_BASE }}" > .env
echo "${{ secrets.ENV_BASE }}" > .env.blue
echo "${{ secrets.ENV_BASE }}" > .env.green

echo "${{ secrets.ENV_TEST }}" >> .env
echo "${{ secrets.ENV_TEST }}" >> .env.blue
echo "${{ secrets.ENV_TEST }}" >> .env.green

echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env
echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env.blue
echo "APP_IMAGE_VERSION=${{ github.sha }}" >> .env.green

sudo docker stop $(sudo docker ps -aq) || true
sudo docker rm $(sudo docker ps -aq) || true
echo "Stopping all containers..."
Expand Down
19 changes: 7 additions & 12 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
testImplementation 'org.projectlombok:lombok'
testImplementation 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testCompileOnly 'org.projectlombok:lombok'
testImplementation 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

// 인증사 관련 의존성
implementation 'javax.servlet:jstl:1.2'
implementation "org.apache.tomcat.embed:tomcat-embed-jasper"
Expand Down Expand Up @@ -69,7 +70,6 @@ dependencies {
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:mysql:1.20.0'


// security
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
Expand Down Expand Up @@ -103,11 +103,6 @@ dependencies {

runtimeOnly 'com.h2database:h2'

testImplementation 'org.springframework.boot:spring-boot-testcontainers:3.3.5'
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcoscntainers:mysql:1.20.0'

annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

implementation 'org.apache.commons:commons-pool2:2.12.1'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package life.mosu.mosuserver.application.admin;

import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository;
import life.mosu.mosuserver.domain.refund.repository.RefundJpaRepository;
import life.mosu.mosuserver.domain.refund.repository.RefundFailureLogJpaRepository;
import life.mosu.mosuserver.domain.user.entity.UserRole;
import life.mosu.mosuserver.domain.user.repository.UserJpaRepository;
import life.mosu.mosuserver.presentation.admin.dto.DashBoardResponse;
import lombok.RequiredArgsConstructor;
Expand All @@ -13,15 +14,19 @@ public class AdminDashboardService {

private final ExamApplicationJpaRepository examApplicationJpaRepository;
private final UserJpaRepository userJpaRepository;
private final RefundJpaRepository refundJpaRepository;
private final RefundFailureLogJpaRepository refundFailureLogJpaRepository;

// 대시보드 정보 조회
public DashBoardResponse getAll() {
Long applicationCounts = examApplicationJpaRepository.count();
Long refundCounts = refundJpaRepository.count();
Long userCounts = userJpaRepository.count();
return new DashBoardResponse(applicationCounts, refundCounts, userCounts);
}

Long applicationCounts = examApplicationJpaRepository.countAll();
Long refundAbortedCounts = refundFailureLogJpaRepository.count();
Long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN);
Comment on lines +22 to +24
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

count 계열은 primitive 사용 권장 + 시그니처/의도 재확인

  • Spring Data의 count()는 primitive long 반환이 일반적입니다. 지역 변수도 primitive 사용을 권장합니다(불필요한 박싱/널 가능성 제거).
  • examApplicationJpaRepository.countAll()은 기본 count()와 의미가 다른가요? 소프트 삭제 제외/필터링 등의 의도가 있다면 주석/메서드명으로 드러나게 해 주세요. 단순 전체 건수라면 기본 count() 사용이 명확합니다.
  • ROLE 필터: Not(≠ ADMIN)는 향후 새로운 Role 추가 시 예기치 않은 포함 이슈가 생길 수 있습니다. “포함해야 할 일반 사용자 Role의 명시적 포함(In) 조건”으로 전환하는 방안도 검토해 주세요(예: ROLE_USER, ROLE_STUDENT 등 도메인 합의 필요).

적용 가능한 최소 변경(박싱 제거):

-        Long applicationCounts = examApplicationJpaRepository.countAll();
-        Long refundAbortedCounts = refundFailureLogJpaRepository.count();
-        Long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN);
+        long applicationCounts = examApplicationJpaRepository.countAll();
+        long refundAbortedCounts = refundFailureLogJpaRepository.count();
+        long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN);

읽기 전용 트랜잭션 부여(스냅샷 일관성 및 미세한 성능 이점):

// add import:
import org.springframework.transaction.annotation.Transactional;

// annotate method:
@Transactional(readOnly = true)
public DashBoardResponse getAll() { ... }

🏁 Script executed:

#!/bin/bash
# Locate the repository interface for examApplicationJpaRepository
repo_file=$(fd --full-path "ExamApplicationJpaRepository.java")
echo "Found file: $repo_file"

# Show the countAll method signature
rg -n "countAll\s*\(" "$repo_file"

# Also confirm the return types for other count methods
rg -n "countByUserRoleNot\s*\(" "$repo_file"

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Locate UserJpaRepository and show the signature of countByUserRoleNot
user_repo=$(fd --full-path "UserJpaRepository.java")
echo "UserJpaRepository found at: $user_repo"
rg -n "countByUserRoleNot" "$user_repo"

# Locate RefundFailureLogJpaRepository and show count signature
refund_repo=$(fd --full-path "RefundFailureLogJpaRepository.java")
echo "RefundFailureLogJpaRepository found at: $refund_repo"
rg -n "count" "$refund_repo" | head -n 10

# Check AdminDashboardService for existing @Transactional on getAll method
service_file=$(fd --full-path "AdminDashboardService.java")
echo "Service file: $service_file"
rg -n "@Transactional" "$service_file"
rg -n "public DashBoardResponse getAll" -n "$service_file"

Length of output: 1690


🏁 Script executed:

#!/bin/bash
# Show context around countAll in ExamApplicationJpaRepository to verify if it's custom or just alias
rg -n -A3 -B3 'countAll' src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java

Length of output: 264


Use primitive long, clarify custom countAll, and add read-only transaction

  • The custom countAll() in ExamApplicationJpaRepository applies filters (paymentStatus = 'DONE' & deleted = false), so its name should reflect that intent (e.g. countDoneNonDeleted() or add Javadoc) rather than implying an unfiltered “all.”
  • Switch to primitive long in AdminDashboardService.getAll() to avoid unnecessary boxing and nullability.
  • Consider replacing countByUserRoleNot(UserRole.ROLE_ADMIN) with an explicit inclusion query (e.g. countByUserRoleIn(...)) to prevent accidental inclusion of future roles.
  • Mark the service method as a read-only transaction for consistency snapshot and slight performance gain.

Locations:

  • src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java (lines 22–24 and method signature)
  • src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java (line 196)

Suggested changes:

--- a/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java
+++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java
@@ -20,6 +20,7 @@
 import org.springframework.transaction.annotation.Transactional;
 
+@Transactional(readOnly = true)
 public class AdminDashboardService {
     public DashBoardResponse getAll() {
-        Long applicationCounts = examApplicationJpaRepository.countAll();
-        Long refundAbortedCounts = refundFailureLogJpaRepository.count();
-        Long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN);
+        long applicationCounts = examApplicationJpaRepository.countAll();
+        long refundAbortedCounts = refundFailureLogJpaRepository.count();
+        long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN);
         // ...
     }
 }

And in the repository:

// src/.../ExamApplicationJpaRepository.java

/**
 * Counts exam applications with paymentStatus = DONE and not deleted.
 */
@Query("""
    SELECT COUNT(p) FROM ExamApplication p
    WHERE p.paymentStatus = 'DONE'
      AND p.deleted = false
""")
long countDoneNonDeleted();
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java
around lines 22 to 24 and the method signature, change the return types from
Long to primitive long to avoid unnecessary boxing and nullability. Rename the
custom repository method countAll() in
src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java
at line 196 to countDoneNonDeleted() and update its Javadoc to clarify it counts
only exam applications with paymentStatus = DONE and deleted = false. Replace
countByUserRoleNot(UserRole.ROLE_ADMIN) with countByUserRoleIn(...) specifying
all non-admin roles explicitly to avoid accidental inclusion of future roles.
Finally, annotate the service method with a read-only transaction annotation to
ensure consistency and improve performance.


return DashBoardResponse.of(
applicationCounts,
refundAbortedCounts,
userCounts
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ public CreateApplicationResponse apply(Long userId, ApplicationRequest request)
List<Long> examIds = request.examApplication().stream()
.map(ExamApplicationRequest::examId)
.toList();

validator.agreedToTerms(request);
validator.requestNoDuplicateExams(examIds);
return handleApplication(
userId,
examIds,
Expand Down Expand Up @@ -78,7 +81,6 @@ private CreateApplicationResponse handleApplication(
List<ExamApplicationRequest> examApplications,
FileRequest admissionTicket
) {
validator.requestNoDuplicateExams(examIds);
List<ExamJpaEntity> exams = examJpaRepository.findAllById(examIds);
validator.examDateNotPassed(exams);
validator.examNotFull(exams);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,8 @@
import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse;
import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationWithStatus;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class GetApplicationsStepProcessor implements
Expand All @@ -33,7 +31,6 @@ public class GetApplicationsStepProcessor implements
public List<ApplicationResponse> process(Long userId) {

List<ApplicationJpaEntity> applications = applicationJpaRepository.findAllByUserId(userId);
log.info("applications info: {}", applications.size());
if (applications.isEmpty()) {
return List.of();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,41 @@
import java.util.Set;
import java.util.stream.Collectors;
import life.mosu.mosuserver.application.exam.cache.ExamQuotaCacheManager;
import life.mosu.mosuserver.domain.application.repository.ApplicationJpaRepository;
import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity;
import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository;
import life.mosu.mosuserver.domain.exam.entity.ExamStatus;
import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository;
import life.mosu.mosuserver.global.exception.CustomRuntimeException;
import life.mosu.mosuserver.global.exception.ErrorCode;
import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest;
import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@RequiredArgsConstructor
public class ApplicationValidator {

private final ExamJpaRepository examJpaRepository;
private final ApplicationJpaRepository applicationJpaRepository;
private final ExamApplicationJpaRepository examApplicationJpaRepository;
private final ExamQuotaCacheManager examQuotaCacheManager;

public void agreedToTerms(ApplicationRequest request) {
if (!request.agreement().validateAgreement()) {
throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
}
}
Comment on lines +30 to +34
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add null safety check for the agreement field.

The method logic is correct, but consider adding a null check for the agreement field to prevent potential NullPointerException.

 public void agreedToTerms(ApplicationRequest request) {
+    if (request.agreement() == null) {
+        throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
+    }
     if (!request.agreement().validateAgreement()) {
         throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void agreedToTerms(ApplicationRequest request) {
if (!request.agreement().validateAgreement()) {
throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
}
}
public void agreedToTerms(ApplicationRequest request) {
if (request.agreement() == null) {
throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
}
if (!request.agreement().validateAgreement()) {
throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS);
}
}
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java
around lines 30 to 34, add a null check for the agreement field in the
agreedToTerms method before calling validateAgreement() to prevent
NullPointerException. Modify the condition to first verify that
request.agreement() is not null, and if it is null or validateAgreement()
returns false, then throw the CustomRuntimeException with
ErrorCode.NOT_AGREED_TO_TERMS.


public void requestNoDuplicateExams(List<Long> examIds) {
Set<Long> examIdSet = new HashSet<>(examIds);
if (examIds.size() != examIdSet.size()) {
throw new CustomRuntimeException(ErrorCode.EXAM_DUPLICATED);
}
if (examIdSet.isEmpty()) {
throw new CustomRuntimeException(ErrorCode.EXAM_NOT_APPLIED);
}
}

public void examIdsAndLunchSelection(List<ExamApplicationRequest> requests) {
Expand All @@ -39,12 +51,9 @@ public void examIdsAndLunchSelection(List<ExamApplicationRequest> requests) {
List<Long> requestedExamIds = requests.stream()
.map(ExamApplicationRequest::examId)
.toList();
Set<Long> examIdSet = new HashSet<>(requestedExamIds);

List<ExamJpaEntity> existingExams = examJpaRepository.findAllById(requestedExamIds);

if (existingExams.size() != requestedExamIds.size()) {
throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND);
}
List<ExamJpaEntity> existingExams = examJpaRepository.findAllById(examIdSet);

lunchSelection(requests, existingExams);
}
Expand All @@ -56,16 +65,18 @@ private void lunchSelection(List<ExamApplicationRequest> requests,
.map(ExamJpaEntity::getId)
.collect(Collectors.toSet());

boolean hasInvalidLunchRequest = requests.stream()
.anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked());
requests.stream()
.filter(req -> req.isLunchChecked() && examsWithoutLunch.contains(req.examId()))
.findFirst()
.ifPresent(req -> {
throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID);
});

if (hasInvalidLunchRequest) {
throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID);
}
}

public void noDuplicateApplication(Long userId, List<Long> examIds) {
boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds);
boolean alreadyApplied = examApplicationJpaRepository.existsByUserIdAndExamIds(userId,
examIds);
if (alreadyApplied) {
throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ public class SignUpAccountStepProcessor implements StepProcessor<UserJpaEntity,
@Transactional
@Override
public UserJpaEntity process(UserJpaEntity user) {
if (userRepository.existsByLoginId(user.getLoginId())) {
if (userRepository.existsByPhoneNumber(user.getPhoneNumber())) {
throw new CustomRuntimeException(ErrorCode.USER_ALREADY_EXISTS);
} else if (userRepository.existsByLoginId(user.getLoginId())) {
throw new CustomRuntimeException(ErrorCode.USER_ALREADY_EXISTS);
}
return userRepository.save(user);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package life.mosu.mosuserver.application.caffeine;

import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import life.mosu.mosuserver.domain.caffeine.CacheGroup;
import life.mosu.mosuserver.domain.caffeine.CacheType;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

@EnableCaching
@Configuration
public class LocalCacheConfig {

@Bean
public LocalCacheManager localCacheManager() {
List<Cache> caches = Arrays.stream(CacheGroup.values())
.filter(g -> g.getCacheType() == CacheType.LOCAL
|| g.getCacheType() == CacheType.COMPOSITE)
.map(g -> new CaffeineCache(
g.getCacheName(),
Caffeine.newBuilder()
.recordStats()
.expireAfterWrite(g.getExpiredAfterWrite())
.build()
)).collect(Collectors.toList());

return new LocalCacheManager(caches);
}

@Bean
@Primary
public CacheManager appCacheManager(LocalCacheManager localCacheManager) {
return localCacheManager;
}
}
Loading