Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4ad60d6
feat: 페이징 검증을 위한 util 클래스 추가
Gyuhyeok99 Feb 13, 2025
e66cd77
feat: 관리자 전용 GPA 페이징 조회 api 추가
Gyuhyeok99 Feb 13, 2025
ee2e26f
test: 관리자 전용 GPA 페이징 조회 api 테스트 코드 추가
Gyuhyeok99 Feb 13, 2025
e51a714
refactor: GPA 페이징 조회 uri 수정
Gyuhyeok99 Feb 13, 2025
0111dbf
feat: 페이징 응답 DTO 추가
Gyuhyeok99 Feb 13, 2025
98ac050
chore: 잘못 추가한 라인 삭제
Gyuhyeok99 Feb 13, 2025
0866334
feat: 거절 사유 관련 검증 어노테이션 추가
Gyuhyeok99 Feb 14, 2025
57a8190
feat: 관리자 전용 GPA 검증 api 추가
Gyuhyeok99 Feb 14, 2025
a6f624c
feat: 관리자 전용 GPA 수정 api 추가
Gyuhyeok99 Feb 14, 2025
bf729a7
refactor: updateGpaScore → updateGpa 메서드명 변경
Gyuhyeok99 Feb 14, 2025
d96d475
refactor: ScoreVerificationAdminService → GpaScoreVerificationAdminSe…
Gyuhyeok99 Feb 14, 2025
c1e2fad
test: 관리자 전용 GPA 수정 api 테스트 코드 추가
Gyuhyeok99 Feb 14, 2025
fb22382
test: 불필요한 GPA 검증 실패 테스트 제거
Gyuhyeok99 Feb 14, 2025
28eef3b
refactor: 페이징 처리 로직 이동 및 기본값 설정
Gyuhyeok99 Feb 15, 2025
f7ebafe
refactor: GPA 수정 및 검증 하나의 api로 통합
Gyuhyeok99 Feb 15, 2025
72d629c
test: 통합된 api에 맞게 테스트 코드 수정
Gyuhyeok99 Feb 15, 2025
c74d396
style: 코드리뷰 반영하여 네이밍 및 개행 수정
Gyuhyeok99 Feb 17, 2025
a0d4d5b
refactor: 임베디드 엔티티 DTO 변환 적용
Gyuhyeok99 Feb 17, 2025
fdb426e
refactor: GpaScore 프로젝션 및 ZoneId 변수로 분리
Gyuhyeok99 Feb 17, 2025
dc3d0fa
Merge branch 'develop' into feat/194-admin-score-verification
Gyuhyeok99 Feb 17, 2025
1435fb0
feat: 페이지네이션 검증 로직에 최대값 조건 추가
Gyuhyeok99 Feb 17, 2025
ba5a027
style: 2차 코드리뷰 반영하여 네이밍 및 개행 수정
Gyuhyeok99 Feb 18, 2025
abb963a
style: 개행 추가
Gyuhyeok99 Feb 18, 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
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public class QApplication extends EntityPathBase<Application> {

public final NumberPath<Long> id = createNumber("id", Long.class);

public final BooleanPath isDelete = createBoolean("isDelete");

public final QLanguageTest languageTest;

public final StringPath nicknameForApply = createString("nicknameForApply");
Expand All @@ -36,6 +38,10 @@ public class QApplication extends EntityPathBase<Application> {

public final com.example.solidconnection.siteuser.domain.QSiteUser siteUser;

public final StringPath term = createString("term");

public final com.example.solidconnection.university.domain.QUniversityInfoForApply thirdChoiceUniversity;

public final NumberPath<Integer> updateCount = createNumber("updateCount", Integer.class);

public final EnumPath<com.example.solidconnection.type.VerifyStatus> verifyStatus = createEnum("verifyStatus", com.example.solidconnection.type.VerifyStatus.class);
Expand Down Expand Up @@ -63,6 +69,7 @@ public QApplication(Class<? extends Application> type, PathMetadata metadata, Pa
this.languageTest = inits.isInitialized("languageTest") ? new QLanguageTest(forProperty("languageTest")) : null;
this.secondChoiceUniversity = inits.isInitialized("secondChoiceUniversity") ? new com.example.solidconnection.university.domain.QUniversityInfoForApply(forProperty("secondChoiceUniversity"), inits.get("secondChoiceUniversity")) : null;
this.siteUser = inits.isInitialized("siteUser") ? new com.example.solidconnection.siteuser.domain.QSiteUser(forProperty("siteUser")) : null;
this.thirdChoiceUniversity = inits.isInitialized("thirdChoiceUniversity") ? new com.example.solidconnection.university.domain.QUniversityInfoForApply(forProperty("thirdChoiceUniversity"), inits.get("thirdChoiceUniversity")) : null;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.querydsl.core.types.PathMetadata;
import javax.annotation.processing.Generated;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.dsl.PathInits;


/**
Expand All @@ -19,18 +20,32 @@ public class QSiteUser extends EntityPathBase<SiteUser> {

public static final QSiteUser siteUser = new QSiteUser("siteUser");

public final EnumPath<AuthType> authType = createEnum("authType", AuthType.class);

public final StringPath birth = createString("birth");

public final ListPath<com.example.solidconnection.community.comment.domain.Comment, com.example.solidconnection.community.comment.domain.QComment> commentList = this.<com.example.solidconnection.community.comment.domain.Comment, com.example.solidconnection.community.comment.domain.QComment>createList("commentList", com.example.solidconnection.community.comment.domain.Comment.class, com.example.solidconnection.community.comment.domain.QComment.class, PathInits.DIRECT2);

public final StringPath email = createString("email");

public final EnumPath<com.example.solidconnection.type.Gender> gender = createEnum("gender", com.example.solidconnection.type.Gender.class);

public final ListPath<com.example.solidconnection.score.domain.GpaScore, com.example.solidconnection.score.domain.QGpaScore> gpaScoreList = this.<com.example.solidconnection.score.domain.GpaScore, com.example.solidconnection.score.domain.QGpaScore>createList("gpaScoreList", com.example.solidconnection.score.domain.GpaScore.class, com.example.solidconnection.score.domain.QGpaScore.class, PathInits.DIRECT2);

public final NumberPath<Long> id = createNumber("id", Long.class);

public final ListPath<com.example.solidconnection.score.domain.LanguageTestScore, com.example.solidconnection.score.domain.QLanguageTestScore> languageTestScoreList = this.<com.example.solidconnection.score.domain.LanguageTestScore, com.example.solidconnection.score.domain.QLanguageTestScore>createList("languageTestScoreList", com.example.solidconnection.score.domain.LanguageTestScore.class, com.example.solidconnection.score.domain.QLanguageTestScore.class, PathInits.DIRECT2);

public final StringPath nickname = createString("nickname");

public final DateTimePath<java.time.LocalDateTime> nicknameModifiedAt = createDateTime("nicknameModifiedAt", java.time.LocalDateTime.class);

public final StringPath password = createString("password");

public final ListPath<com.example.solidconnection.community.post.domain.PostLike, com.example.solidconnection.community.post.domain.QPostLike> postLikeList = this.<com.example.solidconnection.community.post.domain.PostLike, com.example.solidconnection.community.post.domain.QPostLike>createList("postLikeList", com.example.solidconnection.community.post.domain.PostLike.class, com.example.solidconnection.community.post.domain.QPostLike.class, PathInits.DIRECT2);

public final ListPath<com.example.solidconnection.community.post.domain.Post, com.example.solidconnection.community.post.domain.QPost> postList = this.<com.example.solidconnection.community.post.domain.Post, com.example.solidconnection.community.post.domain.QPost>createList("postList", com.example.solidconnection.community.post.domain.Post.class, com.example.solidconnection.community.post.domain.QPost.class, PathInits.DIRECT2);

public final EnumPath<com.example.solidconnection.type.PreparationStatus> preparationStage = createEnum("preparationStage", com.example.solidconnection.type.PreparationStatus.class);

public final StringPath profileImageUrl = createString("profileImageUrl");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public class QUniversityInfoForApply extends EntityPathBase<UniversityInfoForApp

public final NumberPath<Long> id = createNumber("id", Long.class);

public final StringPath koreanName = createString("koreanName");

public final SetPath<LanguageRequirement, QLanguageRequirement> languageRequirements = this.<LanguageRequirement, QLanguageRequirement>createSet("languageRequirements", LanguageRequirement.class, QLanguageRequirement.class, PathInits.DIRECT2);

public final EnumPath<com.example.solidconnection.type.SemesterAvailableForDispatch> semesterAvailableForDispatch = createEnum("semesterAvailableForDispatch", com.example.solidconnection.type.SemesterAvailableForDispatch.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.solidconnection.admin.controller;

import com.example.solidconnection.admin.dto.GpaScoreResponse;
import com.example.solidconnection.admin.dto.GpaScoreSearchResponse;
import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest;
import com.example.solidconnection.admin.dto.ScoreSearchCondition;
import com.example.solidconnection.admin.service.AdminGpaScoreService;
import com.example.solidconnection.custom.response.PageResponse;
import com.example.solidconnection.util.PagingUtils;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RequestMapping("/admin/scores")
@RestController
public class AdminScoreController {

private final AdminGpaScoreService adminGpaScoreService;

@GetMapping("/gpas")
public ResponseEntity<PageResponse<GpaScoreSearchResponse>> searchGpaScores(
@Valid @ModelAttribute ScoreSearchCondition scoreSearchCondition,
@PageableDefault(page = 1) Pageable pageable
) {
PagingUtils.validatePage(pageable.getPageNumber(), pageable.getPageSize());
Pageable internalPageable = PageRequest.of(pageable.getPageNumber() - 1, pageable.getPageSize());
Page<GpaScoreSearchResponse> page = adminGpaScoreService.searchGpaScores(scoreSearchCondition, internalPageable);
return ResponseEntity.ok(PageResponse.of(page));
}

@PatchMapping("/gpas/{gpa-score-id}")
public ResponseEntity<GpaScoreResponse> updateGpaScore(
@PathVariable("gpa-score-id") Long gpaScoreId,
@Valid @RequestBody GpaScoreUpdateRequest request
) {
GpaScoreResponse response = adminGpaScoreService.updateGpaScore(gpaScoreId, request);
return ResponseEntity.ok(response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.solidconnection.admin.dto;

public record GpaResponse(
double gpa,
double gpaCriteria,
String gpaReportUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.solidconnection.admin.dto;

import com.example.solidconnection.score.domain.GpaScore;
import com.example.solidconnection.type.VerifyStatus;

public record GpaScoreResponse(
long id,
double gpa,
double gpaCriteria,
VerifyStatus verifyStatus,
String rejectedReason
) {
public static GpaScoreResponse from(GpaScore gpaScore) {
return new GpaScoreResponse(
gpaScore.getId(),
gpaScore.getGpa().getGpa(),
gpaScore.getGpa().getGpaCriteria(),
gpaScore.getVerifyStatus(),
gpaScore.getRejectedReason()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.example.solidconnection.admin.dto;

public record GpaScoreSearchResponse(
GpaScoreStatusResponse gpaScoreStatusResponse,
SiteUserResponse siteUserResponse
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.example.solidconnection.admin.dto;

import com.example.solidconnection.type.VerifyStatus;

import java.time.ZonedDateTime;

public record GpaScoreStatusResponse(
long id,
GpaResponse gpaResponse,
VerifyStatus verifyStatus,
String rejectedReason,
ZonedDateTime createdAt,
ZonedDateTime updatedAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.solidconnection.admin.dto;

import com.example.solidconnection.custom.validation.annotation.RejectedReasonRequired;
import com.example.solidconnection.type.VerifyStatus;
import jakarta.validation.constraints.NotNull;

@RejectedReasonRequired
public record GpaScoreUpdateRequest(

@NotNull(message = "GPA를 입력해주세요.")
Double gpa,

@NotNull(message = "GPA 기준을 입력해주세요.")
Double gpaCriteria,

@NotNull(message = "승인 상태를 설정해주세요.")
VerifyStatus verifyStatus,

String rejectedReason
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.solidconnection.admin.dto;

import com.example.solidconnection.type.VerifyStatus;

import java.time.LocalDate;

public record ScoreSearchCondition(
VerifyStatus verifyStatus,
String nickname,
LocalDate createdAt) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.solidconnection.admin.dto;

public record SiteUserResponse(
long id,
String nickname,
String profileImageUrl
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.solidconnection.admin.service;

import com.example.solidconnection.admin.dto.GpaScoreResponse;
import com.example.solidconnection.admin.dto.GpaScoreSearchResponse;
import com.example.solidconnection.admin.dto.GpaScoreUpdateRequest;
import com.example.solidconnection.admin.dto.ScoreSearchCondition;
import com.example.solidconnection.application.domain.Gpa;
import com.example.solidconnection.custom.exception.CustomException;
import com.example.solidconnection.score.domain.GpaScore;
import com.example.solidconnection.score.repository.GpaScoreRepository;
import com.example.solidconnection.type.VerifyStatus;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import static com.example.solidconnection.custom.exception.ErrorCode.GPA_SCORE_NOT_FOUND;

@RequiredArgsConstructor
@Service
public class AdminGpaScoreService {

private final GpaScoreRepository gpaScoreRepository;

@Transactional(readOnly = true)
public Page<GpaScoreSearchResponse> searchGpaScores(ScoreSearchCondition scoreSearchCondition, Pageable pageable) {
return gpaScoreRepository.searchGpaScores(scoreSearchCondition, pageable);
}

@Transactional
public GpaScoreResponse updateGpaScore(Long gpaScoreId, GpaScoreUpdateRequest request) {
GpaScore gpaScore = gpaScoreRepository.findById(gpaScoreId)
.orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND));
gpaScore.updateGpaScore(
new Gpa(
request.gpa(),
request.gpaCriteria(),
gpaScore.getGpa().getGpaReportUrl()
),
request.verifyStatus(),
request.verifyStatus() == VerifyStatus.REJECTED ? request.rejectedReason() : null
);
return GpaScoreResponse.from(gpaScore);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.util.Optional;

import static com.example.solidconnection.custom.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_GPA_SCORE;
import static com.example.solidconnection.custom.exception.ErrorCode.GPA_SCORE_NOT_FOUND;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_GPA_SCORE_STATUS;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE;
import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS;
Expand Down Expand Up @@ -88,7 +88,7 @@ public boolean apply(SiteUser siteUser, ApplyRequest applyRequest) {

private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) {
GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)
.orElseThrow(() -> new CustomException(INVALID_GPA_SCORE));
.orElseThrow(() -> new CustomException(GPA_SCORE_NOT_FOUND));
if (gpaScore.getVerifyStatus() != VerifyStatus.APPROVED) {
throw new CustomException(INVALID_GPA_SCORE_STATUS);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public enum ErrorCode {
UNIVERSITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "대학교를 찾을 수 없습니다."),
REGION_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 지역을 찾을 수 없습니다."),
COUNTRY_NOT_FOUND_BY_KOREAN_NAME(HttpStatus.NOT_FOUND.value(), "이름에 해당하는 국가를 찾을 수 없습니다."),
GPA_SCORE_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 학점입니다."),

// auth
USER_ALREADY_SIGN_OUT(HttpStatus.UNAUTHORIZED.value(), "로그아웃 되었습니다."),
Expand Down Expand Up @@ -88,11 +89,15 @@ public enum ErrorCode {
NOT_LIKED_UNIVERSITY(HttpStatus.BAD_REQUEST.value(), "좋아요하지 않은 대학입니다."),

// score
INVALID_GPA_SCORE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 학점입니다."),
INVALID_GPA_SCORE_STATUS(HttpStatus.BAD_REQUEST.value(), "학점이 승인되지 않았습니다."),
INVALID_LANGUAGE_TEST_SCORE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 어학성적입니다."),
INVALID_LANGUAGE_TEST_SCORE_STATUS(HttpStatus.BAD_REQUEST.value(), "어학성적이 승인되지 않았습니다."),
USER_DO_NOT_HAVE_GPA(HttpStatus.BAD_REQUEST.value(), "해당 유저의 학점을 찾을 수 없음"),
REJECTED_REASON_REQUIRED(HttpStatus.BAD_REQUEST.value(), "거절 사유가 필요합니다."),

// page
INVALID_PAGE(HttpStatus.BAD_REQUEST.value(), "페이지 번호는 1 이상 50 이하만 가능합니다."),
INVALID_SIZE(HttpStatus.BAD_REQUEST.value(), "페이지 크기는 1 이상 50 이하만 가능합니다."),

// general
JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.example.solidconnection.custom.response;

import org.springframework.data.domain.Page;

import java.util.List;

public record PageResponse<T>(
List<T> content,
int pageNumber,
int pageSize,
long totalElements,
int totalPages
) {
/*
* 페이지 번호는 1부터 시작하는 것이 사용자 입장에서 더 직관적이기 때문에 1을 더해줌
*/
public static <T> PageResponse<T> of(Page<T> page) {
return new PageResponse<>(
page.getContent(),
page.getNumber() + 1,
page.getSize(),
page.getTotalElements(),
page.getTotalPages()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.example.solidconnection.custom.validation.annotation;

import com.example.solidconnection.custom.validation.validator.RejectedReasonValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = RejectedReasonValidator.class)
public @interface RejectedReasonRequired {

String message() default "거절 사유 입력값이 올바르지 않습니다.";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Loading