diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ac375db23..0dbdd061d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,6 +3,7 @@ name: Build Gradle and Deploy on: push: branches: [ "release" ] + workflow_dispatch: jobs: build-gradle: @@ -62,15 +63,6 @@ jobs: key: ${{ secrets.PRIVATE_KEY }} source: "./docker-compose.yml" target: "/home/${{ secrets.USERNAME }}/solid-connect-server/" - - - name: Copy nginx configuration file to remote - uses: appleboy/scp-action@master - with: - host: ${{ secrets.HOST }} - username: ${{ secrets.USERNAME }} - key: ${{ secrets.PRIVATE_KEY }} - source: "./nginx.conf" - target: "/home/${{ secrets.USERNAME }}/solid-connect-server/" - name: Run docker compose uses: appleboy/ssh-action@master @@ -81,5 +73,5 @@ jobs: script_stop: true script: | cd /home/${{ secrets.USERNAME }}/solid-connect-server - docker-compose down - docker-compose up -d --build + docker compose down + docker compose up -d --build diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..a7033a673 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/main/resources/secret"] + path = src/main/resources/secret + url = https://github.com/solid-connection/solid-connect-secret diff --git a/build.gradle b/build.gradle index 3799f64bc..5157e8f6c 100644 --- a/build.gradle +++ b/build.gradle @@ -36,8 +36,8 @@ dependencies {//todo: 안쓰는 의존성이나 deprecated된 의존성 제거 implementation 'com.amazonaws:aws-java-sdk-s3:1.12.470' implementation 'org.hibernate.validator:hibernate-validator:8.0.1.Final' implementation 'jakarta.annotation:jakarta.annotation-api:2.1.1' - implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' - testImplementation "org.mockito:mockito-core:3.3.3" + implementation 'org.apache.commons:commons-lang3:3.12.0' + testImplementation 'org.mockito:mockito-core:3.3.3' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' diff --git a/docker-compose.yml b/docker-compose.yml index e7358d2b2..8813a4e58 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,27 +17,15 @@ services: depends_on: - redis - solid-connect-server: + solid-connection-server: build: context: . dockerfile: Dockerfile - container_name: solid-connect-server + container_name: solid-connection-server ports: - "8080:8080" environment: - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=6379 depends_on: - - redis - - nginx: - image: nginx:latest - container_name: nginx - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx.conf:/etc/nginx/conf.d/default.conf - - /etc/letsencrypt:/etc/letsencrypt - depends_on: - - solid-connect-server + - redis \ No newline at end of file diff --git a/nginx.conf b/docs/nginx.conf similarity index 75% rename from nginx.conf rename to docs/nginx.conf index e94acb4e3..303463bce 100644 --- a/nginx.conf +++ b/docs/nginx.conf @@ -3,7 +3,7 @@ server { # http를 사용하는 경우 주석 해제 # location / { -# proxy_pass http://solid-connect-server:8080; +# proxy_pass http://solid-connection-server:8080; # proxy_set_header Host $host; # proxy_set_header X-Real-IP $remote_addr; # proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -18,8 +18,8 @@ server { server { listen 443 ssl; - ssl_certificate /etc/letsencrypt/live/api.solid-connect.net/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/api.solid-connect.net/privkey.pem; + ssl_certificate /etc/letsencrypt/live/api.solid-connection.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/api.solid-connection.com/privkey.pem; client_max_body_size 10M; ssl_protocols TLSv1.2 TLSv1.3; @@ -31,14 +31,10 @@ server { ssl_stapling_verify on; location / { - proxy_pass http://solid-connect-server:8080; + proxy_pass http://solid-connection-server:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } - - location ~ /.well-known/acme-challenge { # 인증서 갱신에 필요한 경로 설정 - allow all; - } } \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java index d6695df28..dce62235f 100644 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java +++ b/src/main/java/com/example/solidconnection/application/controller/ApplicationController.java @@ -2,12 +2,9 @@ import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; import com.example.solidconnection.application.dto.ApplicationsResponse; -import com.example.solidconnection.application.dto.ScoreRequest; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; -import com.example.solidconnection.application.dto.VerifyStatusResponse; +import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.service.ApplicationQueryService; import com.example.solidconnection.application.service.ApplicationSubmissionService; -import com.example.solidconnection.application.service.VerifyStatusQueryService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -24,32 +21,22 @@ @RequiredArgsConstructor @RequestMapping("/application") @RestController -public class ApplicationController implements ApplicationControllerSwagger { +public class ApplicationController { private final ApplicationSubmissionService applicationSubmissionService; private final ApplicationQueryService applicationQueryService; - private final VerifyStatusQueryService verifyStatusQueryService; - @PostMapping("/score") - public ResponseEntity submitScore( + // 지원서 제출하기 api + @PostMapping() + public ResponseEntity apply( Principal principal, - @Valid @RequestBody ScoreRequest scoreRequest) { - boolean result = applicationSubmissionService.submitScore(principal.getName(), scoreRequest); + @Valid @RequestBody ApplyRequest applyRequest) { + boolean result = applicationSubmissionService.apply(principal.getName(), applyRequest); return ResponseEntity .status(HttpStatus.OK) .body(new ApplicationSubmissionResponse(result)); } - @PostMapping("/university") - public ResponseEntity submitUniversityChoice( - Principal principal, - @Valid @RequestBody UniversityChoiceRequest universityChoiceRequest) { - boolean result = applicationSubmissionService.submitUniversityChoice(principal.getName(), universityChoiceRequest); - return ResponseEntity - .status(HttpStatus.OK) - .body(new ApplicationSubmissionResponse(result)); - } - @GetMapping public ResponseEntity getApplicants( Principal principal, @@ -69,11 +56,4 @@ public ResponseEntity getApplicantsForUserCompetitors( return ResponseEntity .ok(result); } - - @GetMapping("/status") - public ResponseEntity getApplicationVerifyStatus(Principal principal) { - VerifyStatusResponse result = verifyStatusQueryService.getVerifyStatus(principal.getName()); - return ResponseEntity - .ok(result); - } } diff --git a/src/main/java/com/example/solidconnection/application/controller/ApplicationControllerSwagger.java b/src/main/java/com/example/solidconnection/application/controller/ApplicationControllerSwagger.java deleted file mode 100644 index f531923ac..000000000 --- a/src/main/java/com/example/solidconnection/application/controller/ApplicationControllerSwagger.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.example.solidconnection.application.controller; - -import com.example.solidconnection.application.dto.ApplicationSubmissionResponse; -import com.example.solidconnection.application.dto.ApplicationsResponse; -import com.example.solidconnection.application.dto.ScoreRequest; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; -import com.example.solidconnection.application.dto.VerifyStatusResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestParam; -import io.swagger.v3.oas.annotations.parameters.RequestBody; - -import java.security.Principal; - -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - -@Tag(name = "Application", description = "지원 정보 API") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) -public interface ApplicationControllerSwagger { - - @Operation( - summary = "대학 성적과 어학 성적 제출", - requestBody = @RequestBody( - description = "대학 성적과 어학 성적", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ScoreRequest.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "대학 성적과 어학 성적 제출 성공", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ApplicationSubmissionResponse.class) - ) - ) - } - ) - ResponseEntity submitScore(Principal principal, @Valid @RequestBody ScoreRequest scoreRequest); - - @Operation( - summary = "지망 대학 제출", - requestBody = @RequestBody( - description = "지망 대학", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UniversityChoiceRequest.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "지망 대학 제출 성공", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ApplicationSubmissionResponse.class) - ) - ) - } - ) - ResponseEntity submitUniversityChoice(Principal principal, @Valid @RequestBody UniversityChoiceRequest universityChoiceRequest); - - @Operation( - summary = "지원자 목록 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "지원자 목록 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ApplicationsResponse.class) - ) - ) - } - ) - ResponseEntity getApplicants(Principal principal, @RequestParam(required = false) String region, @RequestParam(required = false) String keyword); - - @Operation( - summary = "성적 승인 상태 확인", - responses = { - @ApiResponse( - responseCode = "200", - description = "성적 승인 상태 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = VerifyStatusResponse.class) - ) - ) - } - ) - ResponseEntity getApplicationVerifyStatus(Principal principal); -} diff --git a/src/main/java/com/example/solidconnection/application/domain/Application.java b/src/main/java/com/example/solidconnection/application/domain/Application.java index 085141f22..7faf77e6e 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Application.java +++ b/src/main/java/com/example/solidconnection/application/domain/Application.java @@ -8,6 +8,7 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -51,16 +52,19 @@ public class Application { @Column(length = 50, nullable = false) private String term; - @ManyToOne + @Column(columnDefinition = "TINYINT(1) NOT NULL DEFAULT 0") + private Boolean isDelete; + + @ManyToOne(fetch = FetchType.LAZY) private UniversityInfoForApply firstChoiceUniversity; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private UniversityInfoForApply secondChoiceUniversity; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private UniversityInfoForApply thirdChoiceUniversity; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) private SiteUser siteUser; public Application( @@ -76,14 +80,53 @@ public Application( this.verifyStatus = PENDING; } - public void updateGpaAndLanguageTest( + public Application( + SiteUser siteUser, + Gpa gpa, + LanguageTest languageTest, + String term, + Integer updateCount, + UniversityInfoForApply firstChoiceUniversity, + UniversityInfoForApply secondChoiceUniversity, + UniversityInfoForApply thirdChoiceUniversity, + String nicknameForApply) { + this.siteUser = siteUser; + this.gpa = gpa; + this.languageTest = languageTest; + this.term = term; + this.updateCount = updateCount; + this.firstChoiceUniversity = firstChoiceUniversity; + this.secondChoiceUniversity = secondChoiceUniversity; + this.thirdChoiceUniversity = thirdChoiceUniversity; + this.nicknameForApply = nicknameForApply; + this.verifyStatus = PENDING; + } + + public Application( + SiteUser siteUser, Gpa gpa, - LanguageTest languageTest) { + LanguageTest languageTest, + String term, + UniversityInfoForApply firstChoiceUniversity, + UniversityInfoForApply secondChoiceUniversity, + UniversityInfoForApply thirdChoiceUniversity, + String nicknameForApply) { + this.siteUser = siteUser; this.gpa = gpa; this.languageTest = languageTest; + this.term = term; + this.updateCount = 0; + this.firstChoiceUniversity = firstChoiceUniversity; + this.secondChoiceUniversity = secondChoiceUniversity; + this.thirdChoiceUniversity = thirdChoiceUniversity; + this.nicknameForApply = nicknameForApply; this.verifyStatus = PENDING; } + public void setIsDeleteTrue() { + this.isDelete = true; + } + public void updateUniversityChoice( UniversityInfoForApply firstChoiceUniversity, UniversityInfoForApply secondChoiceUniversity, diff --git a/src/main/java/com/example/solidconnection/application/domain/Gpa.java b/src/main/java/com/example/solidconnection/application/domain/Gpa.java index 82803dae9..85b12d047 100644 --- a/src/main/java/com/example/solidconnection/application/domain/Gpa.java +++ b/src/main/java/com/example/solidconnection/application/domain/Gpa.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -10,6 +11,7 @@ @AllArgsConstructor @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @Embeddable +@EqualsAndHashCode(of = {"gpa", "gpaCriteria", "gpaReportUrl"}) public class Gpa { @Column(nullable = false, name = "gpa") diff --git a/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java b/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java index a1e579ad8..4295372d4 100644 --- a/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java +++ b/src/main/java/com/example/solidconnection/application/domain/LanguageTest.java @@ -6,6 +6,7 @@ import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -13,6 +14,7 @@ @AllArgsConstructor @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @Embeddable +@EqualsAndHashCode(of = {"languageTestType", "languageTestScore", "languageTestReportUrl"}) public class LanguageTest { @Column(nullable = false, name = "language_test_type", length = 10) diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java index d03f1c9a3..9835491b1 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicantResponse.java @@ -3,24 +3,11 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.type.LanguageTestType; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "지원자") public record ApplicantResponse( - - @Schema(description = "닉네임", example = "행복한 개발자") String nicknameForApply, - - @Schema(description = "GPA", example = "3.85") double gpa, - - @Schema(description = "어학 시험 유형", example = "TOEFL_IBT") LanguageTestType testType, - - @Schema(description = "어학 시험 점수", example = "110") String testScore, - - @Schema(description = "현재 사용자가 해당 지원지인지", example = "true") boolean isMine) { public static ApplicantResponse of(Application application, boolean isMine) { diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java index 279f2b150..4f353733b 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationSubmissionResponse.java @@ -1,10 +1,5 @@ package com.example.solidconnection.application.dto; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "지원 정보 제출 성공 여부") public record ApplicationSubmissionResponse( - - @Schema(description = "제출 성공 여부", example = "true") boolean isSuccess) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java index 2e3025137..a3429c1ef 100644 --- a/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/ApplicationsResponse.java @@ -1,19 +1,9 @@ package com.example.solidconnection.application.dto; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; - import java.util.List; -@Schema(description = "1지망과 2지망 대학과 그 대학에 지원한 지원자 정보") public record ApplicationsResponse( - - @ArraySchema(arraySchema = @Schema(description = "1지망 대학에 지원한 지원자 목록")) List firstChoice, - - @ArraySchema(arraySchema = @Schema(description = "2지망 대학에 지원한 지원자 목록")) List secondChoice, - - @ArraySchema(arraySchema = @Schema(description = "3지망 대학에 지원한 지원자 목록")) List thirdChoice) { } diff --git a/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java new file mode 100644 index 000000000..49c4b01ce --- /dev/null +++ b/src/main/java/com/example/solidconnection/application/dto/ApplyRequest.java @@ -0,0 +1,14 @@ +package com.example.solidconnection.application.dto; + +import jakarta.validation.constraints.NotNull; + +public record ApplyRequest( + @NotNull(message = "gpa score id를 입력해주세요.") + Long gpaScoreId, + + @NotNull(message = "language test score id를 입력해주세요.") + Long languageTestScoreId, + + UniversityChoiceRequest universityChoiceRequest +) { +} diff --git a/src/main/java/com/example/solidconnection/application/dto/ScoreRequest.java b/src/main/java/com/example/solidconnection/application/dto/ScoreRequest.java deleted file mode 100644 index 1f17be430..000000000 --- a/src/main/java/com/example/solidconnection/application/dto/ScoreRequest.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.example.solidconnection.application.dto; - - -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.type.LanguageTestType; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; - -@Schema(description = "대학 성적과 어학 시험 성적") -public record ScoreRequest( - @NotNull(message = "어학 종류를 입력해주세요.") - @Schema(description = "어학 시험 종류", example = "TOEFL", required = true) - LanguageTestType languageTestType, - - @NotBlank(message = "어학 점수를 입력해주세요.") - @Schema(description = "어학 시험 점수", example = "115", required = true) - String languageTestScore, - - @NotBlank(message = "어학 증명서를 첨부해주세요.") - @Schema(description = "어학 증명서 URL", example = "http://example.com/test-report.pdf", required = true) - String languageTestReportUrl, - - @NotNull(message = "학점을 입력해주세요.") - @Schema(description = "GPA", example = "3.5", required = true) - Double gpa, - - @NotNull(message = "학점 기준을 입력해주세요.") - @Schema(description = "GPA 계산 기준", example = "4.0", required = true) - Double gpaCriteria, - - @NotBlank(message = "대학 성적 증명서를 첨부해주세요.") - @Schema(description = "대학 성적 증명서 URL", example = "http://example.com/gpa-report.pdf", required = true) - String gpaReportUrl) { - - public Gpa toGpa() { - return new Gpa( - this.gpa, - this.gpaCriteria, - this.gpaReportUrl); - } - - public LanguageTest toLanguageTest() { - return new LanguageTest( - this.languageTestType, - this.languageTestScore, - this.languageTestReportUrl - ); - } -} diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java index 84751786b..1d3415003 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityApplicantsResponse.java @@ -1,26 +1,14 @@ package com.example.solidconnection.application.dto; import com.example.solidconnection.university.domain.UniversityInfoForApply; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; -@Schema(description = "대학과 그 대학에 지원한 지원자 정보") public record UniversityApplicantsResponse( - @Schema(description = "대학의 한국어 이름", example = "괌대학") String koreanName, - - @Schema(description = "선발 인원", example = "4") int studentCapacity, - - @Schema(description = "지역", example = "영미권") String region, - - @Schema(description = "국가", example = "미국") String country, - - @ArraySchema(schema = @Schema(description = "지원자 목록", implementation = ApplicantResponse.class)) List applicants) { public static UniversityApplicantsResponse of(UniversityInfoForApply universityInfoForApply, List applicant) { diff --git a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java b/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java index a76799571..2d05cfe5b 100644 --- a/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java +++ b/src/main/java/com/example/solidconnection/application/dto/UniversityChoiceRequest.java @@ -1,17 +1,11 @@ package com.example.solidconnection.application.dto; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; -@Schema(description = "지망 대학") public record UniversityChoiceRequest( - @NotNull(message = "1지망 대학교를 입력해주세요.") - @Schema(description = "1지망 대학교의 지원 정보 ID", example = "1") Long firstChoiceUniversityId, - @Schema(description = "2지망 대학교의 지원 정보 ID (선택사항)", example = "2", nullable = true) Long secondChoiceUniversityId, - - @Schema(description = "3지망 대학교의 지원 정보 ID (선택사항)", example = "3", nullable = true) - Long thirdChoiceUniversityId) {} + Long thirdChoiceUniversityId) { +} diff --git a/src/main/java/com/example/solidconnection/application/dto/VerifyStatusResponse.java b/src/main/java/com/example/solidconnection/application/dto/VerifyStatusResponse.java deleted file mode 100644 index 8019e9f8e..000000000 --- a/src/main/java/com/example/solidconnection/application/dto/VerifyStatusResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.example.solidconnection.application.dto; - -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "지원 상태와 지망 대학 변경 횟수") -public record VerifyStatusResponse( - - @Schema(description = "지원 상태", example = "SUBMITTED_PENDING") - String status, - - @Schema(description = "지망 대학 변경 횟수", example = "1") - int updateCount) { -} diff --git a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java index 8f30c196c..1a06ec321 100644 --- a/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java +++ b/src/main/java/com/example/solidconnection/application/repository/ApplicationRepository.java @@ -6,6 +6,8 @@ import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.UniversityInfoForApply; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.util.List; @@ -18,15 +20,22 @@ public interface ApplicationRepository extends JpaRepository boolean existsByNicknameForApply(String nicknameForApply); - Optional findTop1BySiteUser_EmailOrderByTermDesc(String email); + List findAllByFirstChoiceUniversityAndVerifyStatusAndTerm( + UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term); - Optional findBySiteUserAndTerm(SiteUser siteUser, String term); + List findAllBySecondChoiceUniversityAndVerifyStatusAndTerm( + UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term); - List findAllByFirstChoiceUniversityAndVerifyStatusAndTerm(UniversityInfoForApply firstChoiceUniversity, VerifyStatus verifyStatus, String term); + List findAllByThirdChoiceUniversityAndVerifyStatusAndTerm( + UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term); - List findAllBySecondChoiceUniversityAndVerifyStatusAndTerm(UniversityInfoForApply secondChoiceUniversity, VerifyStatus verifyStatus, String term); - - List findAllByThirdChoiceUniversityAndVerifyStatusAndTerm(UniversityInfoForApply thirdChoiceUniversity, VerifyStatus verifyStatus, String term); + @Query(""" + SELECT a FROM Application a + WHERE a.siteUser = :siteUser + AND a.term = :term + AND a.isDelete = false + """) + Optional findBySiteUserAndTerm(@Param("siteUser") SiteUser siteUser, @Param("term") String term); default Application getApplicationBySiteUserAndTerm(SiteUser siteUser, String term) { return findBySiteUserAndTerm(siteUser, term) diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java index 66ae84918..68cf9c0aa 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationQueryService.java @@ -36,6 +36,7 @@ public class ApplicationQueryService { private final UniversityInfoForApplyRepository universityInfoForApplyRepository; private final SiteUserRepository siteUserRepository; private final UniversityFilterRepositoryImpl universityFilterRepository; + @Value("${university.term}") public String term; diff --git a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java index b23b876c7..f82e9ad76 100644 --- a/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java +++ b/src/main/java/com/example/solidconnection/application/service/ApplicationSubmissionService.java @@ -1,15 +1,17 @@ package com.example.solidconnection.application.service; import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.domain.Gpa; -import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.dto.ScoreRequest; +import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.cache.annotation.DefaultCacheOut; import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.VerifyStatus; import com.example.solidconnection.university.domain.UniversityInfoForApply; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import lombok.RequiredArgsConstructor; @@ -17,9 +19,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; -import static com.example.solidconnection.custom.exception.ErrorCode.*; +import static com.example.solidconnection.custom.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; +import static com.example.solidconnection.custom.exception.ErrorCode.CANT_APPLY_FOR_SAME_UNIVERSITY; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_GPA_SCORE; +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; @RequiredArgsConstructor @Service @@ -30,63 +39,26 @@ public class ApplicationSubmissionService { private final ApplicationRepository applicationRepository; private final UniversityInfoForApplyRepository universityInfoForApplyRepository; private final SiteUserRepository siteUserRepository; + private final GpaScoreRepository gpaScoreRepository; + private final LanguageTestScoreRepository languageTestScoreRepository; @Value("${university.term}") private String term; - /* - * 학점과 영어 성적을 제출한다. - * - 금학기에 제출한 적이 있다면, 수정한다. - * - 성적을 제출한적이 한번도 없거나 제출한적이 있지만 금학기에 제출한 적이 없다면 새로 등록한다. - * - 수정을 하고 나면, 성적 승인 상태(verifyStatus)를 PENDING 상태로 변경한다. - * */ + // 학점 및 어학성적이 모두 유효한 경우에만 지원서 등록이 가능하다. + // 기존에 있던 status field 우선 APRROVED로 입력시킨다. @Transactional - @DefaultCacheOut(key = "application:query", cacheManager = "customCacheManager", prefix = true) - public boolean submitScore(String email, ScoreRequest scoreRequest) { + public boolean apply(String email, ApplyRequest applyRequest) { SiteUser siteUser = siteUserRepository.getByEmail(email); - Gpa gpa = scoreRequest.toGpa(); - LanguageTest languageTest = scoreRequest.toLanguageTest(); - - applicationRepository.findBySiteUserAndTerm(siteUser, term) - .ifPresentOrElse( - // 금학기에 성적 제출 이력이 있는 경우 - application -> application.updateGpaAndLanguageTest(gpa, languageTest), - () -> { - // 성적 제출한적이 한번도 없는 경우 && 성적 제출한적이 있지만 금학기에 없는 경우 - applicationRepository.save(new Application(siteUser, gpa, languageTest, term)); - } - ); - return true; - } - - /* - * 지망 대학교를 제출한다. - * - 지망 대학중 중복된 대학교가 있는지 검증한다. - * - 지원 정보 제출 내역이 없다면, 지금의 프로세스(성적 제출 후 지망대학 제출)에 벗어나는 요청이므로 예외를 응답한다. - * - 기존에 제출한 적이 있다면, 수정한다. - * - 수정 횟수 제한을 초과하지 않았는지 검증한다. - * - 새로운 '제출 닉네임'을 부여한다. (악의적으로 타인의 변경 기록을 추적하는 것을 막기 위해) - * - 성적 승인 상태(verifyStatus) 는 변경하지 않는다. - * */ - @Transactional - @DefaultCacheOut(key = "application:query", cacheManager = "customCacheManager", prefix = true) - public boolean submitUniversityChoice(String email, UniversityChoiceRequest universityChoiceRequest) { + UniversityChoiceRequest universityChoiceRequest = applyRequest.universityChoiceRequest(); validateUniversityChoices(universityChoiceRequest); - // 성적 제출한 적이 한번도 없는 경우 - Application existingApplication = applicationRepository.findTop1BySiteUser_EmailOrderByTermDesc(email) - .orElseThrow(() -> new CustomException(SCORE_SHOULD_SUBMITTED_FIRST)); + Long gpaScoreId = applyRequest.gpaScoreId(); + Long languageTestScoreId = applyRequest.languageTestScoreId(); + GpaScore gpaScore = getValidGpaScore(siteUser, gpaScoreId); + LanguageTestScore languageTestScore = getValidLanguageTestScore(siteUser, languageTestScoreId); - Application application = Optional.of(existingApplication) - .filter(app -> !app.getTerm().equals(term)) - .map(app -> { - // 성적 제출한 적이 있지만 금학기에 없는 경우, 이전 성적으로 새 Application 객체를 등록 - SiteUser siteUser = siteUserRepository.getByEmail(email); - return applicationRepository.save(new Application(siteUser, app.getGpa(), app.getLanguageTest(), term)); - }) - .orElse(existingApplication); // 금학기에 이미 성적 제출한 경우 기존 객체 사용 - - validateUpdateLimitNotExceed(application); + Optional application = applicationRepository.findBySiteUserAndTerm(siteUser, term); UniversityInfoForApply firstChoiceUniversity = universityInfoForApplyRepository .getUniversityInfoForApplyByIdAndTerm(universityChoiceRequest.firstChoiceUniversityId(), term); @@ -96,10 +68,44 @@ public boolean submitUniversityChoice(String email, UniversityChoiceRequest univ UniversityInfoForApply thirdChoiceUniversity = Optional.ofNullable(universityChoiceRequest.thirdChoiceUniversityId()) .map(id -> universityInfoForApplyRepository.getUniversityInfoForApplyByIdAndTerm(id, term)) .orElse(null); - application.updateUniversityChoice(firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname()); + + if (application.isEmpty()) { + Application newApplication = new Application(siteUser, gpaScore.getGpa(), languageTestScore.getLanguageTest(), + term, firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname()); + newApplication.setVerifyStatus(VerifyStatus.APPROVED); + applicationRepository.save(newApplication); + } else { + Application before = application.get(); + validateUpdateLimitNotExceed(before); + before.setIsDeleteTrue(); // 기존 이력 soft delete 수행한다. + + Application newApplication = new Application(siteUser, gpaScore.getGpa(), languageTestScore.getLanguageTest(), + term, before.getUpdateCount() + 1, firstChoiceUniversity, secondChoiceUniversity, thirdChoiceUniversity, getRandomNickname()); + newApplication.setVerifyStatus(VerifyStatus.APPROVED); + applicationRepository.save(newApplication); + } return true; } + private GpaScore getValidGpaScore(SiteUser siteUser, Long gpaScoreId) { + GpaScore gpaScore = gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId) + .orElseThrow(() -> new CustomException(INVALID_GPA_SCORE)); + if (gpaScore.getVerifyStatus() != VerifyStatus.APPROVED) { + throw new CustomException(INVALID_GPA_SCORE_STATUS); + } + return gpaScore; + } + + private LanguageTestScore getValidLanguageTestScore(SiteUser siteUser, Long languageTestScoreId) { + LanguageTestScore languageTestScore = languageTestScoreRepository + .findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId) + .orElseThrow(() -> new CustomException(INVALID_LANGUAGE_TEST_SCORE)); + if (languageTestScore.getVerifyStatus() != VerifyStatus.APPROVED) { + throw new CustomException(INVALID_LANGUAGE_TEST_SCORE_STATUS); + } + return languageTestScore; + } + private String getRandomNickname() { String randomNickname = NicknameCreator.createRandomNickname(); while (applicationRepository.existsByNicknameForApply(randomNickname)) { diff --git a/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java b/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java index 21a36dfab..d9243ce39 100644 --- a/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java +++ b/src/main/java/com/example/solidconnection/application/service/NicknameCreator.java @@ -9,18 +9,20 @@ @NoArgsConstructor(access = lombok.AccessLevel.PRIVATE) class NicknameCreator { - public static final List ADJECTIVES = List.copyOf( - Set.of("기쁜", "행복한", "즐거운", "밝은", "따뜻한", "시원한", "고고한", "예쁜", "신선한", "풍부한", "깨끗한", - "귀한", "눈부신", "멋진", "고귀한", "화려한", "상큼한", "활기찬", "유쾌한", "똘똘한", "친절한", "좋은", - "영리한", "용감한", "정직한", "성실한", "강인한", "귀여운", "순수한", "희망찬", "발랄한", "나른한", "후한", "빛나는", - "따스한", "안락한", "편안한", "성공한", "재미난", "청량한", "찬란한", "소중한", "특별한", "단순한", "반가운", "그리운") + public static final List ADJECTIVES = List.copyOf(Set.of( + "기쁜", "행복한", "즐거운", "밝은", "따뜻한", "시원한", "고고한", "예쁜", "신선한", "풍부한", "깨끗한", + "귀한", "눈부신", "멋진", "고귀한", "화려한", "상큼한", "활기찬", "유쾌한", "똘똘한", "친절한", "좋은", + "영리한", "용감한", "정직한", "성실한", "강인한", "귀여운", "순수한", "희망찬", "발랄한", "나른한", "후한", "빛나는", + "따스한", "안락한", "편안한", "성공한", "재미난", "청량한", "찬란한", "소중한", "특별한", "단순한", "반가운", "그리운") ); - public static final List NOUNS = List.copyOf( - Set.of("청춘", "토끼", "기사", "곰", "사슴", "여우", "팬더", "이슬", "새싹", "햇빛", "나비", "별", "달", "구름", - "사탕", "젤리", "마법", "풍선", "캔디", "초코", "인형", "쿠키", "요정", "장미", "마녀", "보물", "꽃", "보석", - "달빛", "오리", "날개", "여행", "편지", "불꽃", "파도", "별빛", "구슬", "노래", "음표", "선율", "미소", "가방", "거울", - "씨앗", "열매", "바다", "약속", "구두", "공기", "등불", "촛불", "진주", "꿀벌", "예감", "바람", "오전", "오후", "아침", "점심", "저녁") + public static final List NOUNS = List.copyOf(Set.of( + "청춘", "토끼", "기사", "곰", "사슴", "여우", "팬더", "이슬", "새싹", "햇빛", "나비", "별", "달", "구름", + "사탕", "젤리", "마법", "풍선", "캔디", "초코", "인형", "쿠키", "요정", "장미", "마녀", "보물", "꽃", "보석", + "달빛", "오리", "날개", "여행", "편지", "불꽃", "파도", "별빛", "구슬", "노래", "음표", "선율", "미소", "가방", + "거울", "씨앗", "열매", "바다", "약속", "구두", "공기", "등불", "촛불", "진주", "꿀벌", "예감", "바람", + "오전", "오후", "아침", "점심", "저녁") ); + private static final Random RANDOM = new Random(); public static String createRandomNickname() { diff --git a/src/main/java/com/example/solidconnection/application/service/VerifyStatusQueryService.java b/src/main/java/com/example/solidconnection/application/service/VerifyStatusQueryService.java deleted file mode 100644 index 33bb340d9..000000000 --- a/src/main/java/com/example/solidconnection/application/service/VerifyStatusQueryService.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.example.solidconnection.application.service; - -import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.dto.VerifyStatusResponse; -import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.VerifyStatus; -import lombok.RequiredArgsConstructor; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import java.util.Optional; - -import static com.example.solidconnection.application.service.VerifyStatusQueryService.ApplicationStatusResponse.NOT_SUBMITTED; -import static com.example.solidconnection.application.service.VerifyStatusQueryService.ApplicationStatusResponse.SCORE_SUBMITTED; -import static com.example.solidconnection.application.service.VerifyStatusQueryService.ApplicationStatusResponse.SUBMITTED_APPROVED; -import static com.example.solidconnection.application.service.VerifyStatusQueryService.ApplicationStatusResponse.SUBMITTED_PENDING; -import static com.example.solidconnection.application.service.VerifyStatusQueryService.ApplicationStatusResponse.SUBMITTED_REJECTED; - -@RequiredArgsConstructor -@Service -public class VerifyStatusQueryService { - - private final ApplicationRepository applicationRepository; - private final SiteUserRepository siteUserRepository; - - @Value("${university.term}") - private String term; - - /* - * 지원 상태를 조회한다. - * 학기별로 상태가 관리된다. - * */ - @Transactional(readOnly = true) - public VerifyStatusResponse getVerifyStatus(String email) { - SiteUser siteUser = siteUserRepository.getByEmail(email); - Optional application = applicationRepository.findBySiteUserAndTerm(siteUser,term); - - // 아무것도 제출 안함 - if (application.isEmpty()) { - return new VerifyStatusResponse(NOT_SUBMITTED.name(), 0); - } - - int updateCount = application.get().getUpdateCount(); - - // 제출한 상태 - if (application.get().getVerifyStatus() == VerifyStatus.PENDING) { - // 성적만 제출 - if (application.get().getFirstChoiceUniversity() == null) { - return new VerifyStatusResponse(SCORE_SUBMITTED.name(), 0); - } - // 성적 승인 대기 중 - return new VerifyStatusResponse(SUBMITTED_PENDING.name(), updateCount); - } - - // 성적 승인 반려 - if (application.get().getVerifyStatus() == VerifyStatus.REJECTED) { - return new VerifyStatusResponse(SUBMITTED_REJECTED.name(), updateCount); - } - - // 성적 승인 완료 - return new VerifyStatusResponse(SUBMITTED_APPROVED.name(), updateCount); - } - - public enum ApplicationStatusResponse { - NOT_SUBMITTED, // 어떤 것도 제출하지 않음 - COLLEGE_SUBMITTED, // 지망 대학만 제출 - SCORE_SUBMITTED, // 성적만 제출 - SUBMITTED_PENDING, // 성적 인증 대기 중 - SUBMITTED_REJECTED, // 성적 인증 승인 완료 - SUBMITTED_APPROVED // 성적 인증 반려 - } -} diff --git a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java index fa9d1f265..9862d0074 100644 --- a/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java +++ b/src/main/java/com/example/solidconnection/auth/client/KakaoOAuthClient.java @@ -25,12 +25,16 @@ public class KakaoOAuthClient { private final RestTemplate restTemplate; + @Value("${kakao.redirect_uri}") public String redirectUri; + @Value("${kakao.client_id}") private String clientId; + @Value("${kakao.token_url}") private String tokenUrl; + @Value("${kakao.user_info_url}") private String userInfoUrl; diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index b73812a2a..5f48124dc 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -22,7 +22,7 @@ @RequiredArgsConstructor @RequestMapping("/auth") @RestController -public class AuthController implements AuthControllerSwagger { +public class AuthController { private final AuthService authService; private final SignUpService signUpService; diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthControllerSwagger.java b/src/main/java/com/example/solidconnection/auth/controller/AuthControllerSwagger.java deleted file mode 100644 index 28ce96f69..000000000 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthControllerSwagger.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.example.solidconnection.auth.controller; - -import com.example.solidconnection.auth.dto.ReissueResponse; -import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.auth.dto.SignUpRequest; -import com.example.solidconnection.auth.dto.SignUpResponse; -import com.example.solidconnection.auth.dto.kakao.FirstAccessResponse; -import com.example.solidconnection.auth.dto.kakao.KakaoCodeRequest; -import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import org.springframework.http.ResponseEntity; - -import java.security.Principal; - -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - -@Tag(name = "Auth", description = "인증 API") -public interface AuthControllerSwagger { - - @Operation( - summary = "카카오 OAuth 처리", - requestBody = @RequestBody( - description = "클라이언트가 받아온 카카오 인증 코드", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = KakaoCodeRequest.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "로그인 성공 또는 회원가입을 위한 사용자 정보 불러오기 성공", - content = @Content( - mediaType = "application/json", - schema = @Schema(oneOf = {SignInResponse.class, FirstAccessResponse.class}) - ) - ) - } - ) - ResponseEntity processKakaoOauth(@RequestBody KakaoCodeRequest kakaoCodeRequest); - - @Operation( - summary = "회원가입", - requestBody = @RequestBody( - description = "회원가입 요청 정보", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = SignUpRequest.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "회원가입 성공, 엑세스 토큰과 리프레시 토큰 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = SignUpResponse.class) - ) - ) - } - ) - ResponseEntity signUp(@Valid @RequestBody SignUpRequest signUpRequest); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "로그아웃", - responses = { - @ApiResponse( - responseCode = "204", - description = "로그아웃 성공" - ) - } - ) - ResponseEntity signOut(Principal principal); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "회원 탈퇴", - responses = { - @ApiResponse( - responseCode = "200", - description = "회원 탈퇴 성공" - ) - } - ) - ResponseEntity quit(Principal principal); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "토큰 재발급", - responses = { - @ApiResponse( - responseCode = "200", - description = "토큰 재발급 성공, 새 토큰 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ReissueResponse.class) - ) - ) - } - ) - ResponseEntity reissueToken(Principal principal); -} diff --git a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java index 5e6097d38..48b55e6cb 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/ReissueResponse.java @@ -1,9 +1,5 @@ package com.example.solidconnection.auth.dto; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "토큰 재발급 응답") public record ReissueResponse( - @Schema(description = "새로 발급된 액세스 토큰", example = "newAccessToken123") String accessToken) { } diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java index ec89bbf16..400491b42 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignInResponse.java @@ -2,16 +2,8 @@ import com.example.solidconnection.auth.dto.kakao.KakaoOauthResponse; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "로그인 응답 데이터") public record SignInResponse( - @Schema(description = "사용자 등록 여부", example = "true") boolean isRegistered, - - @Schema(description = "발급된 액세스 토큰", example = "accessTokenExample123") String accessToken, - - @Schema(description = "발급된 리프레시 토큰", example = "refreshTokenExample123") String refreshToken) implements KakaoOauthResponse { } diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index e77cbd31b..fcb68cad1 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -5,38 +5,22 @@ import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; import com.fasterxml.jackson.annotation.JsonFormat; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; import java.util.List; -@Schema(description = "회원가입 요청 데이터") public record SignUpRequest( - @Schema(description = "카카오 인증 토큰", example = "kakaoToken123") String kakaoOauthToken, - - @ArraySchema(schema = @Schema(description = "관심 지역 목록", example = "아시아권")) List interestedRegions, - - @ArraySchema(schema = @Schema(description = "관심 국가 목록", example = "일본")) List interestedCountries, - - @Schema(description = "지원 준비 단계", example = "CONSIDERING") PreparationStatus preparationStatus, + String profileImageUrl, + Gender gender, @NotBlank(message = "닉네임을 입력해주세요.") - @Schema(description = "닉네임", example = "nickname123") String nickname, - @Schema(description = "프로필 이미지 URL", example = "http://example.com/profile.jpg") - String profileImageUrl, - - @Schema(description = "성별", example = "MALE") - Gender gender, - @JsonFormat(pattern = "yyyy-MM-dd") - @Schema(description = "생년월일", example = "1999-01-01") String birth) { public SiteUser toSiteUser(String email, Role role) { diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpResponse.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpResponse.java index b43eb95e8..2d74610cc 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpResponse.java @@ -1,12 +1,6 @@ package com.example.solidconnection.auth.dto; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "회원가입 후 응답 데이터") public record SignUpResponse( - @Schema(description = "액세스 토큰", example = "accessTokenSignup123") String accessToken, - - @Schema(description = "리프레시 토큰", example = "refreshTokenSignup123") String refreshToken) { } diff --git a/src/main/java/com/example/solidconnection/auth/dto/kakao/FirstAccessResponse.java b/src/main/java/com/example/solidconnection/auth/dto/kakao/FirstAccessResponse.java index 2f777c3b7..6d7130bf0 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/kakao/FirstAccessResponse.java +++ b/src/main/java/com/example/solidconnection/auth/dto/kakao/FirstAccessResponse.java @@ -1,23 +1,10 @@ package com.example.solidconnection.auth.dto.kakao; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "등록되지 않은 사용자의 최초 접속 시 응답 데이터") public record FirstAccessResponse( - - @Schema(description = "사용자 등록 여부", example = "false") boolean isRegistered, - - @Schema(description = "카카오 닉네임", example = "홍길동") String nickname, - - @Schema(description = "이메일", example = "user@example.com") String email, - - @Schema(description = "카카오 프로필 이미지 URL", example = "http://example.com/image.jpg") String profileImageUrl, - - @Schema(description = "우리 서비스에사 발급한 카카오 인증 토큰", example = "abc123xyz") String kakaoOauthToken) implements KakaoOauthResponse { public static FirstAccessResponse of(KakaoUserInfoDto kakaoUserInfoDto, String kakaoOauthToken) { diff --git a/src/main/java/com/example/solidconnection/auth/dto/kakao/KakaoCodeRequest.java b/src/main/java/com/example/solidconnection/auth/dto/kakao/KakaoCodeRequest.java index fcdbefb40..4fcfc5576 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/kakao/KakaoCodeRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/kakao/KakaoCodeRequest.java @@ -1,9 +1,5 @@ package com.example.solidconnection.auth.dto.kakao; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "클라이언트에서 받은 카카오 코드") public record KakaoCodeRequest( - @Schema(description = "카카오 코드", example = "ABCD1234") String code) { } diff --git a/src/main/java/com/example/solidconnection/board/controller/BoardController.java b/src/main/java/com/example/solidconnection/board/controller/BoardController.java index 29cfc249a..f6ebb27d0 100644 --- a/src/main/java/com/example/solidconnection/board/controller/BoardController.java +++ b/src/main/java/com/example/solidconnection/board/controller/BoardController.java @@ -3,22 +3,20 @@ import com.example.solidconnection.board.service.BoardService; import com.example.solidconnection.post.dto.BoardFindPostResponse; import com.example.solidconnection.type.BoardCode; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import java.util.ArrayList; import java.util.List; -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - @RestController @RequiredArgsConstructor @RequestMapping("/communities") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) public class BoardController { private final BoardService boardService; diff --git a/src/main/java/com/example/solidconnection/board/domain/Board.java b/src/main/java/com/example/solidconnection/board/domain/Board.java index 007553367..77d0aada8 100644 --- a/src/main/java/com/example/solidconnection/board/domain/Board.java +++ b/src/main/java/com/example/solidconnection/board/domain/Board.java @@ -1,8 +1,13 @@ package com.example.solidconnection.board.domain; import com.example.solidconnection.post.domain.Post; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.Getter; +import lombok.NoArgsConstructor; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/example/solidconnection/board/service/BoardService.java b/src/main/java/com/example/solidconnection/board/service/BoardService.java index 1ec5ac8b0..2513e0903 100644 --- a/src/main/java/com/example/solidconnection/board/service/BoardService.java +++ b/src/main/java/com/example/solidconnection/board/service/BoardService.java @@ -23,6 +23,18 @@ public class BoardService { private final BoardRepository boardRepository; + @Transactional(readOnly = true) + public List findPostsByCodeAndPostCategory(String code, String category) { + + String boardCode = validateCode(code); + PostCategory postCategory = validatePostCategory(category); + + Board board = boardRepository.getByCodeUsingEntityGraph(boardCode); + List postList = getPostListByPostCategory(board.getPostList(), postCategory); + + return BoardFindPostResponse.from(postList); + } + private String validateCode(String code) { try { return String.valueOf(BoardCode.valueOf(code)); @@ -31,25 +43,13 @@ private String validateCode(String code) { } } - private PostCategory validatePostCategory(String category){ - if(!EnumUtils.isValidEnum(PostCategory.class, category)){ + private PostCategory validatePostCategory(String category) { + if (!EnumUtils.isValidEnum(PostCategory.class, category)) { throw new CustomException(INVALID_POST_CATEGORY); } return PostCategory.valueOf(category); } - @Transactional(readOnly = true) - public List findPostsByCodeAndPostCategory(String code, String category) { - - String boardCode = validateCode(code); - PostCategory postCategory = validatePostCategory(category); - - Board board = boardRepository.getByCodeUsingEntityGraph(boardCode); - List postList = getPostListByPostCategory(board.getPostList(), postCategory); - - return BoardFindPostResponse.from(postList); - } - private List getPostListByPostCategory(List postList, PostCategory postCategory) { if (postCategory.equals(PostCategory.전체)) { return postList; diff --git a/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java b/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java index 34e2752b3..c785168b3 100644 --- a/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java +++ b/src/main/java/com/example/solidconnection/cache/CacheUpdateListener.java @@ -14,6 +14,7 @@ public class CacheUpdateListener implements MessageListener { private final CompletableFutureManager futureManager; + @Override public void onMessage(Message message, byte[] pattern) { String messageBody = new String(message.getBody(), StandardCharsets.UTF_8).replaceAll("^\"|\"$", ""); diff --git a/src/main/java/com/example/solidconnection/cache/CachingAspect.java b/src/main/java/com/example/solidconnection/cache/CachingAspect.java index 29c355372..816532022 100644 --- a/src/main/java/com/example/solidconnection/cache/CachingAspect.java +++ b/src/main/java/com/example/solidconnection/cache/CachingAspect.java @@ -15,6 +15,7 @@ @Component @RequiredArgsConstructor public class CachingAspect { + private final ApplicationContext applicationContext; private final RedisUtils redisUtils; @@ -47,7 +48,7 @@ public Object cacheEvict(ProceedingJoinPoint joinPoint, DefaultCacheOut defaultC if (usingPrefix) { cacheManager.evictUsingPrefix(cacheKey); - }else{ + } else { cacheManager.evict(cacheKey); } } diff --git a/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java b/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java index 6bcf01e03..48c36b28c 100644 --- a/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java +++ b/src/main/java/com/example/solidconnection/cache/CompletableFutureManager.java @@ -2,12 +2,13 @@ import org.springframework.stereotype.Component; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; -import java.util.Map; @Component public class CompletableFutureManager { + private final Map> waitingRequests = new ConcurrentHashMap<>(); public CompletableFuture getOrCreateFuture(String key) { diff --git a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java index 8dc1694db..a37e80f51 100644 --- a/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java +++ b/src/main/java/com/example/solidconnection/cache/ThunderingHerdCachingAspect.java @@ -16,14 +16,22 @@ import java.time.Duration; import java.util.UUID; -import java.util.concurrent.*; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; -import static com.example.solidconnection.type.RedisConstants.*; +import static com.example.solidconnection.type.RedisConstants.CREATE_CHANNEL; +import static com.example.solidconnection.type.RedisConstants.LOCK_TIMEOUT_MS; +import static com.example.solidconnection.type.RedisConstants.MAX_WAIT_TIME_MS; +import static com.example.solidconnection.type.RedisConstants.REFRESH_LIMIT_PERCENT; @Aspect @Component @Slf4j public class ThunderingHerdCachingAspect { + private final ApplicationContext applicationContext; private final RedisTemplate redisTemplate; private final CompletableFutureManager futureManager; diff --git a/src/main/java/com/example/solidconnection/cache/annotation/DefaultCacheOut.java b/src/main/java/com/example/solidconnection/cache/annotation/DefaultCacheOut.java index bb1d5b518..2b5c8aada 100644 --- a/src/main/java/com/example/solidconnection/cache/annotation/DefaultCacheOut.java +++ b/src/main/java/com/example/solidconnection/cache/annotation/DefaultCacheOut.java @@ -8,7 +8,10 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DefaultCacheOut { + String[] key(); + String cacheManager(); + boolean prefix() default false; } diff --git a/src/main/java/com/example/solidconnection/cache/annotation/DefaultCaching.java b/src/main/java/com/example/solidconnection/cache/annotation/DefaultCaching.java index 36c45a616..316daab0f 100644 --- a/src/main/java/com/example/solidconnection/cache/annotation/DefaultCaching.java +++ b/src/main/java/com/example/solidconnection/cache/annotation/DefaultCaching.java @@ -8,7 +8,10 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface DefaultCaching { + String key(); + String cacheManager(); + long ttlSec(); } diff --git a/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java b/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java index 6772a52e7..c5a9e0e9b 100644 --- a/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java +++ b/src/main/java/com/example/solidconnection/cache/annotation/ThunderingHerdCaching.java @@ -9,6 +9,8 @@ @Retention(RetentionPolicy.RUNTIME) public @interface ThunderingHerdCaching { String key(); + String cacheManager(); + long ttlSec(); } diff --git a/src/main/java/com/example/solidconnection/cache/manager/CacheManager.java b/src/main/java/com/example/solidconnection/cache/manager/CacheManager.java index 8c46324e1..3373d1563 100644 --- a/src/main/java/com/example/solidconnection/cache/manager/CacheManager.java +++ b/src/main/java/com/example/solidconnection/cache/manager/CacheManager.java @@ -1,8 +1,12 @@ package com.example.solidconnection.cache.manager; public interface CacheManager { + void put(String key, Object value, Long ttl); + Object get(String key); + void evict(String key); + void evictUsingPrefix(String key); } diff --git a/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java b/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java index 833ed00f7..2e489567c 100644 --- a/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java +++ b/src/main/java/com/example/solidconnection/cache/manager/CustomCacheManager.java @@ -11,6 +11,7 @@ @Component("customCacheManager") public class CustomCacheManager implements CacheManager { + private final RedisTemplate redisTemplate; @Autowired @@ -33,7 +34,7 @@ public void evict(String key) { } public void evictUsingPrefix(String key) { - Set keys = redisTemplate.keys(key+"*"); + Set keys = redisTemplate.keys(key + "*"); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); } diff --git a/src/main/java/com/example/solidconnection/comment/controller/CommentController.java b/src/main/java/com/example/solidconnection/comment/controller/CommentController.java index 61bae1036..a7eaab252 100644 --- a/src/main/java/com/example/solidconnection/comment/controller/CommentController.java +++ b/src/main/java/com/example/solidconnection/comment/controller/CommentController.java @@ -1,23 +1,27 @@ package com.example.solidconnection.comment.controller; -import com.example.solidconnection.comment.dto.*; +import com.example.solidconnection.comment.dto.CommentCreateRequest; +import com.example.solidconnection.comment.dto.CommentCreateResponse; +import com.example.solidconnection.comment.dto.CommentDeleteResponse; +import com.example.solidconnection.comment.dto.CommentUpdateRequest; +import com.example.solidconnection.comment.dto.CommentUpdateResponse; import com.example.solidconnection.comment.service.CommentService; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.security.Principal; -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - @RestController @RequiredArgsConstructor @RequestMapping("/posts") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) public class CommentController { private final CommentService commentService; @@ -28,7 +32,6 @@ public ResponseEntity createComment( @PathVariable("post_id") Long postId, @Valid @RequestBody CommentCreateRequest commentCreateRequest ) { - CommentCreateResponse commentCreateResponse = commentService.createComment( principal.getName(), postId, commentCreateRequest); return ResponseEntity.ok().body(commentCreateResponse); @@ -41,7 +44,6 @@ public ResponseEntity updateComment( @PathVariable("comment_id") Long commentId, @Valid @RequestBody CommentUpdateRequest commentUpdateRequest ) { - CommentUpdateResponse commentUpdateResponse = commentService.updateComment( principal.getName(), postId, commentId, commentUpdateRequest ); @@ -54,9 +56,7 @@ public ResponseEntity deleteCommentById( @PathVariable("post_id") Long postId, @PathVariable("comment_id") Long commentId ) { - CommentDeleteResponse commentDeleteResponse = commentService.deleteCommentById(principal.getName(), postId, commentId); return ResponseEntity.ok().body(commentDeleteResponse); } - } diff --git a/src/main/java/com/example/solidconnection/comment/domain/Comment.java b/src/main/java/com/example/solidconnection/comment/domain/Comment.java index 774c01123..a4d147a61 100644 --- a/src/main/java/com/example/solidconnection/comment/domain/Comment.java +++ b/src/main/java/com/example/solidconnection/comment/domain/Comment.java @@ -3,7 +3,17 @@ import com.example.solidconnection.entity.common.BaseEntity; import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.siteuser.domain.SiteUser; -import jakarta.persistence.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Transient; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/comment/dto/CommentCreateRequest.java b/src/main/java/com/example/solidconnection/comment/dto/CommentCreateRequest.java index 8cf57e360..c2065685b 100644 --- a/src/main/java/com/example/solidconnection/comment/dto/CommentCreateRequest.java +++ b/src/main/java/com/example/solidconnection/comment/dto/CommentCreateRequest.java @@ -10,6 +10,7 @@ public record CommentCreateRequest( @NotBlank(message = "댓글 내용은 빈 값일 수 없습니다.") @Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content, + Long parentId ) { public Comment toEntity(SiteUser siteUser, Post post, Comment parentComment) { diff --git a/src/main/java/com/example/solidconnection/comment/dto/CommentUpdateRequest.java b/src/main/java/com/example/solidconnection/comment/dto/CommentUpdateRequest.java index 23ae16118..d99429931 100644 --- a/src/main/java/com/example/solidconnection/comment/dto/CommentUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/comment/dto/CommentUpdateRequest.java @@ -8,5 +8,4 @@ public record CommentUpdateRequest( @Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content ) { - } diff --git a/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java b/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java index 2335b68ad..a0d68066a 100644 --- a/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java +++ b/src/main/java/com/example/solidconnection/comment/dto/PostFindCommentResponse.java @@ -13,8 +13,8 @@ public record PostFindCommentResponse( ZonedDateTime createdAt, ZonedDateTime updatedAt, PostFindSiteUserResponse postFindSiteUserResponse - ) { + public static PostFindCommentResponse from(Boolean isOwner, Comment comment) { return new PostFindCommentResponse( comment.getId(), diff --git a/src/main/java/com/example/solidconnection/comment/repository/CommentRepository.java b/src/main/java/com/example/solidconnection/comment/repository/CommentRepository.java index b78011903..ce37c42a1 100644 --- a/src/main/java/com/example/solidconnection/comment/repository/CommentRepository.java +++ b/src/main/java/com/example/solidconnection/comment/repository/CommentRepository.java @@ -9,6 +9,7 @@ import java.util.List; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_ID; + public interface CommentRepository extends JpaRepository { @Query(value = """ diff --git a/src/main/java/com/example/solidconnection/comment/service/CommentService.java b/src/main/java/com/example/solidconnection/comment/service/CommentService.java index 8c0b0458f..7d25ee5f6 100644 --- a/src/main/java/com/example/solidconnection/comment/service/CommentService.java +++ b/src/main/java/com/example/solidconnection/comment/service/CommentService.java @@ -1,8 +1,13 @@ package com.example.solidconnection.comment.service; -import com.example.solidconnection.comment.dto.*; -import com.example.solidconnection.comment.repository.CommentRepository; import com.example.solidconnection.comment.domain.Comment; +import com.example.solidconnection.comment.dto.CommentCreateRequest; +import com.example.solidconnection.comment.dto.CommentCreateResponse; +import com.example.solidconnection.comment.dto.CommentDeleteResponse; +import com.example.solidconnection.comment.dto.CommentUpdateRequest; +import com.example.solidconnection.comment.dto.CommentUpdateResponse; +import com.example.solidconnection.comment.dto.PostFindCommentResponse; +import com.example.solidconnection.comment.repository.CommentRepository; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.post.repository.PostRepository; @@ -15,7 +20,9 @@ import java.util.List; import java.util.stream.Collectors; -import static com.example.solidconnection.custom.exception.ErrorCode.*; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPDATE_DEPRECATED_COMMENT; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_COMMENT_LEVEL; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; @Service @RequiredArgsConstructor @@ -25,29 +32,6 @@ public class CommentService { private final SiteUserRepository siteUserRepository; private final PostRepository postRepository; - private Boolean isOwner(Comment comment, String email) { - return comment.getSiteUser().getEmail().equals(email); - } - - private void validateOwnership(Comment comment, String email) { - if (!comment.getSiteUser().getEmail().equals(email)) { - throw new CustomException(INVALID_POST_ACCESS); - } - } - - private void validateDeprecated(Comment comment) { - if (comment.getContent() == null) { - throw new CustomException(CAN_NOT_UPDATE_DEPRECATED_COMMENT); - } - } - - // 대대댓글부터 허용하지 않음 - private void validateCommentDepth(Comment parentComment) { - if (parentComment.getParentComment() != null) { - throw new CustomException(INVALID_COMMENT_LEVEL); - } - } - @Transactional(readOnly = true) public List findCommentsByPostId(String email, Long postId) { return commentRepository.findCommentTreeByPostId(postId) @@ -56,6 +40,10 @@ public List findCommentsByPostId(String email, Long pos .collect(Collectors.toList()); } + private Boolean isOwner(Comment comment, String email) { + return comment.getSiteUser().getEmail().equals(email); + } + @Transactional public CommentCreateResponse createComment(String email, Long postId, CommentCreateRequest commentCreateRequest) { @@ -72,6 +60,13 @@ public CommentCreateResponse createComment(String email, Long postId, CommentCre return CommentCreateResponse.from(createdComment); } + // 대대댓글부터 허용하지 않음 + private void validateCommentDepth(Comment parentComment) { + if (parentComment.getParentComment() != null) { + throw new CustomException(INVALID_COMMENT_LEVEL); + } + } + @Transactional public CommentUpdateResponse updateComment(String email, Long postId, Long commentId, CommentUpdateRequest commentUpdateRequest) { @@ -86,6 +81,12 @@ public CommentUpdateResponse updateComment(String email, Long postId, Long comme return CommentUpdateResponse.from(comment); } + private void validateDeprecated(Comment comment) { + if (comment.getContent() == null) { + throw new CustomException(CAN_NOT_UPDATE_DEPRECATED_COMMENT); + } + } + @Transactional public CommentDeleteResponse deleteCommentById(String email, Long postId, Long commentId) { SiteUser siteUser = siteUserRepository.getByEmail(email); @@ -117,4 +118,10 @@ public CommentDeleteResponse deleteCommentById(String email, Long postId, Long c } return new CommentDeleteResponse(commentId); } + + private void validateOwnership(Comment comment, String email) { + if (!comment.getSiteUser().getEmail().equals(email)) { + throw new CustomException(INVALID_POST_ACCESS); + } + } } diff --git a/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java b/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java new file mode 100644 index 000000000..55e47bd90 --- /dev/null +++ b/src/main/java/com/example/solidconnection/config/cors/CorsPropertiesConfig.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.config.cors; + +import lombok.Getter; +import lombok.Setter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Getter +@Setter +@ConfigurationProperties(prefix = "cors") +@Configuration +public class CorsPropertiesConfig { + + private List allowedOrigins; +} \ No newline at end of file diff --git a/src/main/java/com/example/solidconnection/config/cors/WebConfig.java b/src/main/java/com/example/solidconnection/config/cors/WebConfig.java index 9143d6558..00f3cf411 100644 --- a/src/main/java/com/example/solidconnection/config/cors/WebConfig.java +++ b/src/main/java/com/example/solidconnection/config/cors/WebConfig.java @@ -1,16 +1,20 @@ package com.example.solidconnection.config.cors; +import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration +@RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { + private final CorsPropertiesConfig corsProperties; + @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:8080", "http://localhost:3000", "https://www.solid-connect.net") + .allowedOrigins(corsProperties.getAllowedOrigins().toArray(new String[0])) .allowedMethods("*") .allowedHeaders("*") .allowCredentials(true); diff --git a/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java b/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java index 282c36e8c..22847dc6d 100644 --- a/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java +++ b/src/main/java/com/example/solidconnection/config/redis/RedisConfig.java @@ -23,7 +23,6 @@ public class RedisConfig { private final String redisHost; - private final int redisPort; public RedisConfig(@Value("${spring.data.redis.host}") final String redisHost, diff --git a/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java b/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java index a52bf281a..2a2cfa6a5 100644 --- a/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java +++ b/src/main/java/com/example/solidconnection/config/scheduler/SchedulerConfig.java @@ -7,6 +7,7 @@ @Configuration public class SchedulerConfig implements SchedulingConfigurer { + private final int POOL_SIZE = 5; @Override diff --git a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java index 7e103f911..a618bec04 100644 --- a/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/config/security/JwtAuthenticationFilter.java @@ -29,6 +29,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { public static final String TOKEN_HEADER = "Authorization"; public static final String TOKEN_PREFIX = "Bearer "; + private final TokenService tokenService; private final TokenValidator tokenValidator; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; @@ -107,10 +108,6 @@ private HashSet getPermitAllEndpoints() { // 대학교 정보 permitAllEndpoints.add("/university/search/**"); - // API 문서 - permitAllEndpoints.add("/swagger-ui/**"); - permitAllEndpoints.add("/v3/api-docs/**"); - return permitAllEndpoints; } } diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index 934eaf9f8..70bcf6c37 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -1,5 +1,6 @@ package com.example.solidconnection.config.security; +import com.example.solidconnection.config.cors.CorsPropertiesConfig; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -23,11 +24,12 @@ public class SecurityConfiguration { private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final CorsPropertiesConfig corsProperties; @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); - configuration.setAllowedOrigins(Arrays.asList("https://www.solid-connect.net", "http://localhost:8080", "https://www.api.solid-connect.net", "http://localhost:3000")); + configuration.setAllowedOrigins(corsProperties.getAllowedOrigins()); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); @@ -51,7 +53,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { "/file/profile/pre", "/auth/kakao", "/auth/sign-up", "/auth/reissue", "/university/detail/**", "/university/search/**", "/university/recommends", - "/swagger-ui/**", "/v3/api-docs/**", "/actuator/**" ) .permitAll() diff --git a/src/main/java/com/example/solidconnection/config/swagger/SwaggerConfig.java b/src/main/java/com/example/solidconnection/config/swagger/SwaggerConfig.java deleted file mode 100644 index f74a57fb9..000000000 --- a/src/main/java/com/example/solidconnection/config/swagger/SwaggerConfig.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.example.solidconnection.config.swagger; - -import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; -import io.swagger.v3.oas.annotations.security.SecurityScheme; -import io.swagger.v3.oas.models.Components; -import io.swagger.v3.oas.models.OpenAPI; -import io.swagger.v3.oas.models.info.Info; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import static io.swagger.v3.oas.annotations.enums.SecuritySchemeType.HTTP; - -@Configuration -@SecurityScheme( - name = "access_token", - type = HTTP, - in = SecuritySchemeIn.HEADER, - scheme = "bearer", - bearerFormat = "JWT", - paramName = "Authorization", - description = "엑세스 토큰을 입력하세요. (Bearer 포함 X)" -) -public class SwaggerConfig { - - public static final String ACCESS_TOKEN = "access_token"; - - @Bean - public OpenAPI customOpenAPI() { - return new OpenAPI() - .components(new Components()) - .info(apiInfo()); - } - - private Info apiInfo() { - return new Info() - .title("솔리드 커넥션 API 문서✈️") - .description("솔리드 커넥션의 API 문서입니다. \n\"Authorize\" 버튼을 눌러 인증을 하면 인증이 필요한 API를 호출할 수 있습니다.") - .version("1.0.0"); - } -} diff --git a/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java b/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java index 738d26e04..417b040b3 100644 --- a/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java +++ b/src/main/java/com/example/solidconnection/config/sync/AsyncConfig.java @@ -9,7 +9,6 @@ public class AsyncConfig { @Bean(name = "asyncExecutor") public ThreadPoolTaskExecutor asyncExecutor() { - ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); diff --git a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java index a95a504ed..9a63a21f5 100644 --- a/src/main/java/com/example/solidconnection/config/token/TokenValidator.java +++ b/src/main/java/com/example/solidconnection/config/token/TokenValidator.java @@ -36,30 +36,15 @@ public void validateAccessToken(String token) { validateRefreshToken(token); } - private void validateRefreshToken(String token) { - String email = getClaim(token).getSubject(); - if (redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)) == null) { - throw new CustomException(REFRESH_TOKEN_EXPIRED); - } - } - - private void validateNotSignOut(String token) { - String email = getClaim(token).getSubject(); - if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)))) { - throw new CustomException(USER_ALREADY_SIGN_OUT); - } - } - public void validateKakaoToken(String token) { validateTokenNotEmpty(token); validateTokenNotExpired(token, TokenType.KAKAO_OAUTH); validateKakaoTokenNotUsed(token); } - private void validateKakaoTokenNotUsed(String token) { - String email = getClaim(token).getSubject(); - if (!Objects.equals(redisTemplate.opsForValue().get(TokenType.KAKAO_OAUTH.addTokenPrefixToSubject(email)), token)) { - throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN); + private void validateTokenNotEmpty(String token) { + if (!StringUtils.hasText(token)) { + throw new CustomException(INVALID_TOKEN); } } @@ -76,9 +61,24 @@ private void validateTokenNotExpired(String token, TokenType tokenType) { } } - private void validateTokenNotEmpty(String token) { - if (!StringUtils.hasText(token)) { - throw new CustomException(INVALID_TOKEN); + private void validateNotSignOut(String token) { + String email = getClaim(token).getSubject(); + if (SIGN_OUT_VALUE.equals(redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)))) { + throw new CustomException(USER_ALREADY_SIGN_OUT); + } + } + + private void validateRefreshToken(String token) { + String email = getClaim(token).getSubject(); + if (redisTemplate.opsForValue().get(TokenType.REFRESH.addTokenPrefixToSubject(email)) == null) { + throw new CustomException(REFRESH_TOKEN_EXPIRED); + } + } + + private void validateKakaoTokenNotUsed(String token) { + String email = getClaim(token).getSubject(); + if (!Objects.equals(redisTemplate.opsForValue().get(TokenType.KAKAO_OAUTH.addTokenPrefixToSubject(email)), token)) { + throw new CustomException(INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN); } } diff --git a/src/main/java/com/example/solidconnection/custom/exception/CustomException.java b/src/main/java/com/example/solidconnection/custom/exception/CustomException.java index 367d704eb..2f1962fbf 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/CustomException.java +++ b/src/main/java/com/example/solidconnection/custom/exception/CustomException.java @@ -4,6 +4,7 @@ @Getter public class CustomException extends RuntimeException { + private final int code; private final String message; diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index f9e1e45b1..765013303 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -53,7 +53,7 @@ public enum ErrorCode { PROFILE_IMAGE_NEEDED(HttpStatus.BAD_REQUEST.value(), "프로필 이미지가 필요합니다."), // community - INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(),"잘못된 카테고리명입니다."), + INVALID_POST_CATEGORY(HttpStatus.BAD_REQUEST.value(), "잘못된 카테고리명입니다."), INVALID_BOARD_CODE(HttpStatus.BAD_REQUEST.value(), "잘못된 게시판 코드입니다."), INVALID_POST_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글입니다."), INVALID_POST_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 게시글만 제어할 수 있습니다."), @@ -62,10 +62,17 @@ public enum ErrorCode { INVALID_COMMENT_ID(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 댓글입니다."), INVALID_COMMENT_LEVEL(HttpStatus.BAD_REQUEST.value(), "최대 대댓글까지만 작성할 수 있습니다."), INVALID_COMMENT_ACCESS(HttpStatus.BAD_REQUEST.value(), "자신의 댓글만 제어할 수 있습니다."), - CAN_NOT_UPDATE_DEPRECATED_COMMENT(HttpStatus.BAD_REQUEST.value(),"이미 삭제된 댓글을 수정할 수 없습니다."), + CAN_NOT_UPDATE_DEPRECATED_COMMENT(HttpStatus.BAD_REQUEST.value(), "이미 삭제된 댓글을 수정할 수 없습니다."), INVALID_POST_LIKE(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 게시글 좋아요입니다."), DUPLICATE_POST_LIKE(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(), "해당 유저의 학점을 찾을 수 없음"), + // general JSON_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "JSON 파싱을 할 수 없습니다."), JWT_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "JWT 토큰을 처리할 수 없습니다."), diff --git a/src/main/java/com/example/solidconnection/entity/PostImage.java b/src/main/java/com/example/solidconnection/entity/PostImage.java index 9ab87853c..653beecc4 100644 --- a/src/main/java/com/example/solidconnection/entity/PostImage.java +++ b/src/main/java/com/example/solidconnection/entity/PostImage.java @@ -1,7 +1,14 @@ package com.example.solidconnection.entity; import com.example.solidconnection.post.domain.Post; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.Getter; import lombok.NoArgsConstructor; @@ -9,6 +16,7 @@ @Getter @NoArgsConstructor public class PostImage { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/com/example/solidconnection/post/controller/PostController.java b/src/main/java/com/example/solidconnection/post/controller/PostController.java index c5b974082..05cdfc574 100644 --- a/src/main/java/com/example/solidconnection/post/controller/PostController.java +++ b/src/main/java/com/example/solidconnection/post/controller/PostController.java @@ -1,26 +1,35 @@ package com.example.solidconnection.post.controller; -import com.example.solidconnection.post.dto.*; +import com.example.solidconnection.post.dto.PostCreateRequest; +import com.example.solidconnection.post.dto.PostCreateResponse; +import com.example.solidconnection.post.dto.PostDeleteResponse; +import com.example.solidconnection.post.dto.PostDislikeResponse; +import com.example.solidconnection.post.dto.PostFindResponse; +import com.example.solidconnection.post.dto.PostLikeResponse; +import com.example.solidconnection.post.dto.PostUpdateRequest; +import com.example.solidconnection.post.dto.PostUpdateResponse; import com.example.solidconnection.post.service.PostService; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.security.Principal; import java.util.Collections; import java.util.List; -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - @RestController @RequiredArgsConstructor @RequestMapping("/communities") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) public class PostController { private final PostService postService; diff --git a/src/main/java/com/example/solidconnection/post/domain/Post.java b/src/main/java/com/example/solidconnection/post/domain/Post.java index 203feb5a9..31125f8bd 100644 --- a/src/main/java/com/example/solidconnection/post/domain/Post.java +++ b/src/main/java/com/example/solidconnection/post/domain/Post.java @@ -7,8 +7,21 @@ import com.example.solidconnection.post.dto.PostUpdateRequest; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.type.PostCategory; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; import org.hibernate.annotations.BatchSize; import java.util.ArrayList; diff --git a/src/main/java/com/example/solidconnection/post/domain/PostLike.java b/src/main/java/com/example/solidconnection/post/domain/PostLike.java index 0af621370..9edf4052e 100644 --- a/src/main/java/com/example/solidconnection/post/domain/PostLike.java +++ b/src/main/java/com/example/solidconnection/post/domain/PostLike.java @@ -1,7 +1,13 @@ package com.example.solidconnection.post.domain; import com.example.solidconnection.siteuser.domain.SiteUser; -import jakarta.persistence.*; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; @@ -11,6 +17,7 @@ @NoArgsConstructor @EqualsAndHashCode(of = "id") public class PostLike { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -24,7 +31,6 @@ public class PostLike { private SiteUser siteUser; public void setPostAndSiteUser(Post post, SiteUser siteUser) { - if (this.post != null) { this.post.getPostLikeList().remove(this); } @@ -39,7 +45,6 @@ public void setPostAndSiteUser(Post post, SiteUser siteUser) { } public void resetPostAndSiteUser() { - if (this.post != null) { this.post.getPostLikeList().remove(this); } diff --git a/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java b/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java index 8e6d3202a..4f475824c 100644 --- a/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/BoardFindPostResponse.java @@ -33,6 +33,12 @@ public static BoardFindPostResponse from(Post post) { ); } + public static List from(List postList) { + return postList.stream() + .map(BoardFindPostResponse::from) + .collect(Collectors.toList()); + } + private static int getCommentCount(Post post) { return post.getCommentList().size(); } @@ -43,10 +49,4 @@ private static String getFirstImageUrl(Post post) { .map(PostImage::getUrl) .orElse(null); } - - public static List from(List postList) { - return postList.stream() - .map(BoardFindPostResponse::from) - .collect(Collectors.toList()); - } } diff --git a/src/main/java/com/example/solidconnection/post/dto/PostCreateRequest.java b/src/main/java/com/example/solidconnection/post/dto/PostCreateRequest.java index 03ab79686..a1ba1c696 100644 --- a/src/main/java/com/example/solidconnection/post/dto/PostCreateRequest.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostCreateRequest.java @@ -11,12 +11,15 @@ public record PostCreateRequest( @NotNull(message = "게시글 카테고리를 설정해주세요.") String postCategory, + @NotBlank(message = "게시글 제목은 빈 값일 수 없습니다.") @Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String title, + @NotBlank(message = "게시글 내용은 빈 값일 수 없습니다.") @Size(min = 1, max = 1000, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content, + @NotNull(message = "게시글 질문여부를 설정해주세요.") Boolean isQuestion ) { diff --git a/src/main/java/com/example/solidconnection/dto/PostFindPostImageResponse.java b/src/main/java/com/example/solidconnection/post/dto/PostFindPostImageResponse.java similarity index 93% rename from src/main/java/com/example/solidconnection/dto/PostFindPostImageResponse.java rename to src/main/java/com/example/solidconnection/post/dto/PostFindPostImageResponse.java index 22a6a4af0..63adf0020 100644 --- a/src/main/java/com/example/solidconnection/dto/PostFindPostImageResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostFindPostImageResponse.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.dto; +package com.example.solidconnection.post.dto; import com.example.solidconnection.entity.PostImage; diff --git a/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java b/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java index 45e4e5dc7..1562dd5bc 100644 --- a/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostFindResponse.java @@ -2,7 +2,6 @@ import com.example.solidconnection.board.dto.PostFindBoardResponse; import com.example.solidconnection.comment.dto.PostFindCommentResponse; -import com.example.solidconnection.dto.*; import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.siteuser.dto.PostFindSiteUserResponse; diff --git a/src/main/java/com/example/solidconnection/post/dto/PostLikeResponse.java b/src/main/java/com/example/solidconnection/post/dto/PostLikeResponse.java index 0ce14b175..35d7d58c9 100644 --- a/src/main/java/com/example/solidconnection/post/dto/PostLikeResponse.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostLikeResponse.java @@ -5,8 +5,6 @@ public record PostLikeResponse( Long likeCount, Boolean isLiked - - ) { public static PostLikeResponse from(Post post) { return new PostLikeResponse( diff --git a/src/main/java/com/example/solidconnection/post/dto/PostUpdateRequest.java b/src/main/java/com/example/solidconnection/post/dto/PostUpdateRequest.java index b82b73685..b9bdc6f54 100644 --- a/src/main/java/com/example/solidconnection/post/dto/PostUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/post/dto/PostUpdateRequest.java @@ -7,9 +7,11 @@ public record PostUpdateRequest( @NotNull(message = "게시글 카테고리를 설정해주세요.") String postCategory, + @NotBlank(message = "게시글 제목은 빈 값일 수 없습니다.") @Size(min = 1, max = 255, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String title, + @NotBlank(message = "게시글 내용은 빈 값일 수 없습니다.") @Size(min = 1, max = 1000, message = "댓글 내용은 최소 1자 이상, 최대 255자 이하여야 합니다.") String content diff --git a/src/main/java/com/example/solidconnection/post/repository/PostLikeRepository.java b/src/main/java/com/example/solidconnection/post/repository/PostLikeRepository.java index 398157c73..bebde7a92 100644 --- a/src/main/java/com/example/solidconnection/post/repository/PostLikeRepository.java +++ b/src/main/java/com/example/solidconnection/post/repository/PostLikeRepository.java @@ -1,8 +1,8 @@ package com.example.solidconnection.post.repository; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.post.domain.PostLike; import com.example.solidconnection.post.domain.Post; +import com.example.solidconnection.post.domain.PostLike; import com.example.solidconnection.siteuser.domain.SiteUser; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; diff --git a/src/main/java/com/example/solidconnection/post/repository/PostRepository.java b/src/main/java/com/example/solidconnection/post/repository/PostRepository.java index b819cc45a..e96881147 100644 --- a/src/main/java/com/example/solidconnection/post/repository/PostRepository.java +++ b/src/main/java/com/example/solidconnection/post/repository/PostRepository.java @@ -19,6 +19,27 @@ public interface PostRepository extends JpaRepository { @EntityGraph(attributePaths = {"postImageList", "board", "siteUser"}) Optional findPostById(Long id); + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(""" + UPDATE Post p SET p.likeCount = p.likeCount - 1 + WHERE p.id = :postId AND p.likeCount > 0 + """) + void decreaseLikeCount(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(""" + UPDATE Post p SET p.likeCount = p.likeCount + 1 + WHERE p.id = :postId + """) + void increaseLikeCount(@Param("postId") Long postId); + + @Modifying(clearAutomatically = true, flushAutomatically = true) + @Query(""" + UPDATE Post p SET p.viewCount = p.viewCount + :count + WHERE p.id = :postId + """) + void increaseViewCount(@Param("postId") Long postId, @Param("count") Long count); + default Post getByIdUsingEntityGraph(Long id) { return findPostById(id) .orElseThrow(() -> new CustomException(INVALID_POST_ID)); @@ -28,19 +49,4 @@ default Post getById(Long id) { return findById(id) .orElseThrow(() -> new CustomException(INVALID_POST_ID)); } - - @Modifying(clearAutomatically = true, flushAutomatically = true) - @Query("UPDATE Post p SET p.likeCount = p.likeCount - 1 " + - "WHERE p.id = :postId AND p.likeCount > 0") - void decreaseLikeCount(@Param("postId") Long postId); - - @Modifying(clearAutomatically = true, flushAutomatically = true) - @Query("UPDATE Post p SET p.likeCount = p.likeCount + 1 " + - "WHERE p.id = :postId") - void increaseLikeCount(@Param("postId") Long postId); - - @Modifying(clearAutomatically = true, flushAutomatically = true) - @Query("UPDATE Post p SET p.viewCount = p.viewCount + :count " + - "WHERE p.id = :postId") - void increaseViewCount(@Param("postId") Long postId, @Param("count") Long count); } diff --git a/src/main/java/com/example/solidconnection/post/service/PostService.java b/src/main/java/com/example/solidconnection/post/service/PostService.java index 4f9f77a73..d31cfb97a 100644 --- a/src/main/java/com/example/solidconnection/post/service/PostService.java +++ b/src/main/java/com/example/solidconnection/post/service/PostService.java @@ -1,17 +1,24 @@ package com.example.solidconnection.post.service; +import com.example.solidconnection.board.domain.Board; import com.example.solidconnection.board.dto.PostFindBoardResponse; import com.example.solidconnection.board.repository.BoardRepository; import com.example.solidconnection.comment.dto.PostFindCommentResponse; import com.example.solidconnection.comment.service.CommentService; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.dto.*; -import com.example.solidconnection.board.domain.Board; import com.example.solidconnection.entity.PostImage; +import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.post.domain.PostLike; +import com.example.solidconnection.post.dto.PostCreateRequest; +import com.example.solidconnection.post.dto.PostCreateResponse; +import com.example.solidconnection.post.dto.PostDeleteResponse; +import com.example.solidconnection.post.dto.PostDislikeResponse; +import com.example.solidconnection.post.dto.PostFindPostImageResponse; +import com.example.solidconnection.post.dto.PostFindResponse; +import com.example.solidconnection.post.dto.PostLikeResponse; +import com.example.solidconnection.post.dto.PostUpdateRequest; +import com.example.solidconnection.post.dto.PostUpdateResponse; import com.example.solidconnection.post.repository.PostLikeRepository; -import com.example.solidconnection.post.domain.Post; -import com.example.solidconnection.post.dto.*; import com.example.solidconnection.post.repository.PostRepository; import com.example.solidconnection.s3.S3Service; import com.example.solidconnection.s3.UploadedFileUrlResponse; @@ -32,11 +39,17 @@ import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.*; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_DELETE_OR_UPDATE_QUESTION; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_UPLOAD_MORE_THAN_FIVE_IMAGES; +import static com.example.solidconnection.custom.exception.ErrorCode.DUPLICATE_POST_LIKE; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_BOARD_CODE; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_ACCESS; +import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_POST_CATEGORY; @Service @RequiredArgsConstructor public class PostService { + private final PostRepository postRepository; private final SiteUserRepository siteUserRepository; private final BoardRepository boardRepository; diff --git a/src/main/java/com/example/solidconnection/repositories/CountryRepository.java b/src/main/java/com/example/solidconnection/repositories/CountryRepository.java index aff9470b4..d9ba75555 100644 --- a/src/main/java/com/example/solidconnection/repositories/CountryRepository.java +++ b/src/main/java/com/example/solidconnection/repositories/CountryRepository.java @@ -7,21 +7,10 @@ import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; @Repository public interface CountryRepository extends JpaRepository { - Optional findByKoreanName(String koreanName); - -/* default Country getByKoreanName(String koreanName) { - return findByKoreanName(koreanName) - .orElseThrow(() -> new CustomException(COUNTRY_NOT_FOUND_BY_KOREAN_NAME)); - }*/ - @Query("SELECT c FROM Country c WHERE c.koreanName IN :names") List findByKoreanNames(@Param(value = "names") List names); - - @Query("SELECT c FROM Country c WHERE c.koreanName LIKE %:keyword%") - List findByKoreanNameContaining(@Param("keyword") String keyword); } diff --git a/src/main/java/com/example/solidconnection/repositories/RegionRepository.java b/src/main/java/com/example/solidconnection/repositories/RegionRepository.java index 9a26233bc..0dc99fb08 100644 --- a/src/main/java/com/example/solidconnection/repositories/RegionRepository.java +++ b/src/main/java/com/example/solidconnection/repositories/RegionRepository.java @@ -7,18 +7,10 @@ import org.springframework.stereotype.Repository; import java.util.List; -import java.util.Optional; @Repository public interface RegionRepository extends JpaRepository { - Optional findByKoreanName(String koreanName); - -/* default Region getByKoreanName(String koreanName) { - return findByKoreanName(koreanName) - .orElseThrow(() -> new CustomException(REGION_NOT_FOUND_BY_KOREAN_NAME)); - }*/ - @Query("SELECT r FROM Region r WHERE r.koreanName IN :names") List findByKoreanNames(@Param(value = "names") List names); } diff --git a/src/main/java/com/example/solidconnection/s3/FileUploadService.java b/src/main/java/com/example/solidconnection/s3/FileUploadService.java index 5e31c5475..71d9f9c7a 100644 --- a/src/main/java/com/example/solidconnection/s3/FileUploadService.java +++ b/src/main/java/com/example/solidconnection/s3/FileUploadService.java @@ -22,6 +22,7 @@ @EnableAsync @Slf4j public class FileUploadService { + private final AmazonS3Client amazonS3; public FileUploadService(AmazonS3Client amazonS3) { diff --git a/src/main/java/com/example/solidconnection/s3/S3Controller.java b/src/main/java/com/example/solidconnection/s3/S3Controller.java index 7a1c1fc7c..0f32a4ab6 100644 --- a/src/main/java/com/example/solidconnection/s3/S3Controller.java +++ b/src/main/java/com/example/solidconnection/s3/S3Controller.java @@ -4,7 +4,11 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.security.Principal; @@ -12,15 +16,19 @@ @RequiredArgsConstructor @RequestMapping("/file") @RestController -public class S3Controller implements S3ControllerSwagger { +public class S3Controller { private final S3Service s3Service; + @Value("${cloud.aws.s3.url.default}") private String s3Default; + @Value("${cloud.aws.s3.url.uploaded}") private String s3Uploaded; + @Value("${cloud.aws.cloudFront.url.default}") private String cloudFrontDefault; + @Value("${cloud.aws.cloudFront.url.uploaded}") private String cloudFrontUploaded; diff --git a/src/main/java/com/example/solidconnection/s3/S3ControllerSwagger.java b/src/main/java/com/example/solidconnection/s3/S3ControllerSwagger.java deleted file mode 100644 index bce6bfe15..000000000 --- a/src/main/java/com/example/solidconnection/s3/S3ControllerSwagger.java +++ /dev/null @@ -1,136 +0,0 @@ -package com.example.solidconnection.s3; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.multipart.MultipartFile; - -import java.security.Principal; - -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - -@Tag(name = "ImageUpload", description = "S3 파일 업로드 API") -public interface S3ControllerSwagger { - - @Operation( - summary = "회원가입 전 프로필 이미지 업로드 - 프로필 이미지 설정", - requestBody = @RequestBody( - description = "업로드할 프로필 이미지 파일", - required = true, - content = @Content( - mediaType = "multipart/form-data", - schema = @Schema(implementation = MultipartFile.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "프로필 이미지 업로드 성공, URL 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadedFileUrlResponse.class) - ) - ) - } - ) - ResponseEntity uploadPreProfileImage(@RequestParam("file") MultipartFile imageFile); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "회원가입 후 프로필 이미지 업로드 - 프로필 이미지 수정", - requestBody = @RequestBody( - description = "업로드할 프로필 이미지 파일", - required = true, - content = @Content( - mediaType = "multipart/form-data", - schema = @Schema(implementation = MultipartFile.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "프로필 이미지 업로드 성공 후 기존 이미지 삭제, URL 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadedFileUrlResponse.class) - ) - ) - } - ) - ResponseEntity uploadPostProfileImage(@RequestParam("file") MultipartFile imageFile, Principal principal); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "GPA 증명서 이미지 업로드", - requestBody = @RequestBody( - description = "업로드할 GPA 증명서 이미지 파일", - required = true, - content = @Content( - mediaType = "multipart/form-data", - schema = @Schema(implementation = MultipartFile.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "GPA 증명서 이미지 업로드 성공, URL 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadedFileUrlResponse.class) - ) - ) - } - ) - ResponseEntity uploadGpaImage(@RequestParam("file") MultipartFile imageFile); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "어학 시험 증명서 이미지 업로드", - requestBody = @RequestBody( - description = "업로드할 어학 시험 증명서 이미지 파일", - required = true, - content = @Content( - mediaType = "multipart/form-data", - schema = @Schema(implementation = MultipartFile.class) - ) - ), - responses = { - @ApiResponse( - responseCode = "200", - description = "어학 시험 증명서 이미지 업로드 성공, URL 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UploadedFileUrlResponse.class) - ) - ) - } - ) - ResponseEntity uploadLanguageImage(@RequestParam("file") MultipartFile imageFile); - - @SecurityRequirements - @SecurityRequirement(name = ACCESS_TOKEN) - @Operation( - summary = "S3 url prefix 확인", - responses = { - @ApiResponse( - responseCode = "200", - description = "S3 url prefix 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = urlPrefixResponse.class) - ) - ) - } - ) - ResponseEntity getS3UrlPrefix(); -} diff --git a/src/main/java/com/example/solidconnection/s3/S3Service.java b/src/main/java/com/example/solidconnection/s3/S3Service.java index 534c4a935..049be9fa3 100644 --- a/src/main/java/com/example/solidconnection/s3/S3Service.java +++ b/src/main/java/com/example/solidconnection/s3/S3Service.java @@ -16,7 +16,11 @@ import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.UUID; import static com.example.solidconnection.custom.exception.ErrorCode.FILE_NOT_EXIST; import static com.example.solidconnection.custom.exception.ErrorCode.INVALID_FILE_EXTENSIONS; @@ -29,13 +33,15 @@ public class S3Service { private static final Logger log = LoggerFactory.getLogger(S3Service.class); + private static final long MAX_FILE_SIZE_MB = 1024 * 1024 * 3; + private final AmazonS3Client amazonS3; private final SiteUserRepository siteUserRepository; private final FileUploadService fileUploadService; private final ThreadPoolTaskExecutor asyncExecutor; + @Value("${cloud.aws.s3.bucket}") private String bucket; - private final long MAX_FILE_SIZE_MB = 1024 * 1024 * 3; /* * 파일을 S3에 업로드한다. diff --git a/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java b/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java index 090f206f7..6d9b690fa 100644 --- a/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java +++ b/src/main/java/com/example/solidconnection/s3/UploadedFileUrlResponse.java @@ -1,9 +1,5 @@ package com.example.solidconnection.s3; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "업로드된 파일의 URL 응답") public record UploadedFileUrlResponse( - @Schema(description = "파일 URL", example = "http://example.com/uploads/profile.jpg") String fileUrl) { } diff --git a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java index 41ba66398..8da1fe1ca 100644 --- a/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java +++ b/src/main/java/com/example/solidconnection/scheduler/UpdateViewCountScheduler.java @@ -13,7 +13,7 @@ import java.util.List; -import static com.example.solidconnection.type.RedisConstants.*; +import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_KEY_PATTERN; @RequiredArgsConstructor @Component diff --git a/src/main/java/com/example/solidconnection/score/controller/ScoreController.java b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java new file mode 100644 index 000000000..42ee7b009 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/controller/ScoreController.java @@ -0,0 +1,57 @@ +package com.example.solidconnection.score.controller; + +import com.example.solidconnection.score.dto.GpaScoreRequest; +import com.example.solidconnection.score.dto.GpaScoreStatusResponse; +import com.example.solidconnection.score.dto.LanguageTestScoreRequest; +import com.example.solidconnection.score.dto.LanguageTestScoreStatusResponse; +import com.example.solidconnection.score.service.ScoreService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.security.Principal; + +@RestController +@RequestMapping("/score") +@RequiredArgsConstructor +public class ScoreController { + + private final ScoreService scoreService; + + // 학점을 등록하는 api + @PostMapping("/gpa") + public ResponseEntity submitGpaScore( + Principal principal, + @Valid @RequestBody GpaScoreRequest gpaScoreRequest) { + Long id = scoreService.submitGpaScore(principal.getName(), gpaScoreRequest); + return ResponseEntity.ok(id); + } + + // 어학성적을 등록하는 api + @PostMapping("/languageTest") + public ResponseEntity submitLanguageTestScore( + Principal principal, + @Valid @RequestBody LanguageTestScoreRequest languageTestScoreRequest) { + Long id = scoreService.submitLanguageTestScore(principal.getName(), languageTestScoreRequest); + return ResponseEntity.ok(id); + } + + // 학점 상태를 확인하는 api + @GetMapping("/gpa") + public ResponseEntity getGpaScoreStatus(Principal principal) { + GpaScoreStatusResponse gpaScoreStatus = scoreService.getGpaScoreStatus(principal.getName()); + return ResponseEntity.ok(gpaScoreStatus); + } + + // 어학 성적 상태를 확인하는 api + @GetMapping("/languageTest") + public ResponseEntity getLanguageTestScoreStatus(Principal principal) { + LanguageTestScoreStatusResponse languageTestScoreStatus = scoreService.getLanguageTestScoreStatus(principal.getName()); + return ResponseEntity.ok(languageTestScoreStatus); + } +} diff --git a/src/main/java/com/example/solidconnection/score/domain/GpaScore.java b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java new file mode 100644 index 000000000..17b5cca48 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/domain/GpaScore.java @@ -0,0 +1,63 @@ +package com.example.solidconnection.score.domain; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.type.VerifyStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; + +@Getter +@Entity +@NoArgsConstructor +@EqualsAndHashCode +public class GpaScore extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Embedded + private Gpa gpa; + + private LocalDate issueDate; + + @Setter + @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Enumerated(EnumType.STRING) + private VerifyStatus verifyStatus; + + private String rejectedReason; + + @ManyToOne + private SiteUser siteUser; + + public GpaScore(Gpa gpa, SiteUser siteUser, LocalDate issueDate) { + this.gpa = gpa; + this.siteUser = siteUser; + this.issueDate = issueDate; + this.verifyStatus = VerifyStatus.PENDING; + this.rejectedReason = null; + } + + public void setSiteUser(SiteUser siteUser) { + if (this.siteUser != null) { + this.siteUser.getGpaScoreList().remove(this); + } + this.siteUser = siteUser; + siteUser.getGpaScoreList().add(this); + } +} diff --git a/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java new file mode 100644 index 000000000..88501f686 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/domain/LanguageTestScore.java @@ -0,0 +1,62 @@ +package com.example.solidconnection.score.domain; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.entity.common.BaseEntity; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.type.VerifyStatus; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.time.LocalDate; + +@Getter +@Entity +@NoArgsConstructor +@AllArgsConstructor +public class LanguageTestScore extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Embedded + private LanguageTest languageTest; + + private LocalDate issueDate; + + @Setter + @Column(columnDefinition = "varchar(50) not null default 'PENDING'") + @Enumerated(EnumType.STRING) + private VerifyStatus verifyStatus; + + private String rejectedReason; + + @ManyToOne + private SiteUser siteUser; + + public LanguageTestScore(LanguageTest languageTest, LocalDate issueDate, SiteUser siteUser) { + this.languageTest = languageTest; + this.issueDate = issueDate; + this.verifyStatus = VerifyStatus.PENDING; + this.siteUser = siteUser; + } + + public void setSiteUser(SiteUser siteUser) { + if (this.siteUser != null) { + this.siteUser.getLanguageTestScoreList().remove(this); + } + this.siteUser = siteUser; + siteUser.getLanguageTestScoreList().add(this); + } +} diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java new file mode 100644 index 000000000..5227ba9ed --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreRequest.java @@ -0,0 +1,28 @@ +package com.example.solidconnection.score.dto; + +import com.example.solidconnection.application.domain.Gpa; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + +public record GpaScoreRequest( + @NotNull(message = "학점을 입력해주세요.") + Double gpa, + + @NotNull(message = "학점 기준을 입력해주세요.") + Double gpaCriteria, + + @NotNull(message = "발급일자를 입력해주세요.") + LocalDate issueDate, + + @NotBlank(message = "대학 성적 증명서를 첨부해주세요.") + String gpaReportUrl) { + + public Gpa toGpa() { + return new Gpa( + this.gpa, + this.gpaCriteria, + this.gpaReportUrl); + } +} diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatus.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatus.java new file mode 100644 index 000000000..0361cf0e7 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatus.java @@ -0,0 +1,25 @@ +package com.example.solidconnection.score.dto; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.type.VerifyStatus; + +import java.time.LocalDate; + +public record GpaScoreStatus( + Long id, + Gpa gpa, + LocalDate issueDate, + VerifyStatus verifyStatus, + String rejectedReason +) { + public static GpaScoreStatus from(GpaScore gpaScore) { + return new GpaScoreStatus( + gpaScore.getId(), + gpaScore.getGpa(), + gpaScore.getIssueDate(), + gpaScore.getVerifyStatus(), + gpaScore.getRejectedReason() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java new file mode 100644 index 000000000..06fdba0d3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/GpaScoreStatusResponse.java @@ -0,0 +1,8 @@ +package com.example.solidconnection.score.dto; + +import java.util.List; + +public record GpaScoreStatusResponse( + List gpaScoreStatusList +) { +} diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java new file mode 100644 index 000000000..c39e5fcb9 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreRequest.java @@ -0,0 +1,31 @@ +package com.example.solidconnection.score.dto; + + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.type.LanguageTestType; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +import java.time.LocalDate; + +public record LanguageTestScoreRequest( + @NotNull(message = "어학 종류를 입력해주세요.") + LanguageTestType languageTestType, + + @NotBlank(message = "어학 점수를 입력해주세요.") + String languageTestScore, + + @NotNull(message = "발급일자를 입력해주세요.") + LocalDate issueDate, + + @NotBlank(message = "어학 증명서를 첨부해주세요.") + String languageTestReportUrl) { + + public LanguageTest toLanguageTest() { + return new LanguageTest( + this.languageTestType, + this.languageTestScore, + this.languageTestReportUrl + ); + } +} diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatus.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatus.java new file mode 100644 index 000000000..2d1d8fcb1 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatus.java @@ -0,0 +1,25 @@ +package com.example.solidconnection.score.dto; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.type.VerifyStatus; + +import java.time.LocalDate; + +public record LanguageTestScoreStatus( + Long id, + LanguageTest languageTest, + LocalDate issueDate, + VerifyStatus verifyStatus, + String rejectedReason +) { + public static LanguageTestScoreStatus from(LanguageTestScore languageTestScore) { + return new LanguageTestScoreStatus( + languageTestScore.getId(), + languageTestScore.getLanguageTest(), + languageTestScore.getIssueDate(), + languageTestScore.getVerifyStatus(), + languageTestScore.getRejectedReason() + ); + } +} diff --git a/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java new file mode 100644 index 000000000..3d4f74894 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/dto/LanguageTestScoreStatusResponse.java @@ -0,0 +1,9 @@ +package com.example.solidconnection.score.dto; + + +import java.util.List; + +public record LanguageTestScoreStatusResponse( + List languageTestScoreStatusList +) { +} diff --git a/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java new file mode 100644 index 000000000..e3c26665b --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/repository/GpaScoreRepository.java @@ -0,0 +1,16 @@ +package com.example.solidconnection.score.repository; + +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.siteuser.domain.SiteUser; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface GpaScoreRepository extends JpaRepository { + + Optional findGpaScoreBySiteUser(SiteUser siteUser); + + Optional findGpaScoreBySiteUserAndId(SiteUser siteUser, Long id); +} diff --git a/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java new file mode 100644 index 000000000..8e6ca3967 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/repository/LanguageTestScoreRepository.java @@ -0,0 +1,17 @@ +package com.example.solidconnection.score.repository; + +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.type.LanguageTestType; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface LanguageTestScoreRepository extends JpaRepository { + + Optional findLanguageTestScoreBySiteUserAndLanguageTest_LanguageTestType(SiteUser siteUser, LanguageTestType languageTestType); + + Optional findLanguageTestScoreBySiteUserAndId(SiteUser siteUser, Long id); +} diff --git a/src/main/java/com/example/solidconnection/score/service/ScoreService.java b/src/main/java/com/example/solidconnection/score/service/ScoreService.java new file mode 100644 index 000000000..d09038fa5 --- /dev/null +++ b/src/main/java/com/example/solidconnection/score/service/ScoreService.java @@ -0,0 +1,78 @@ +package com.example.solidconnection.score.service; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.dto.GpaScoreRequest; +import com.example.solidconnection.score.dto.GpaScoreStatus; +import com.example.solidconnection.score.dto.GpaScoreStatusResponse; +import com.example.solidconnection.score.dto.LanguageTestScoreRequest; +import com.example.solidconnection.score.dto.LanguageTestScoreStatus; +import com.example.solidconnection.score.dto.LanguageTestScoreStatusResponse; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class ScoreService { + + private final GpaScoreRepository gpaScoreRepository; + private final LanguageTestScoreRepository languageTestScoreRepository; + private final SiteUserRepository siteUserRepository; + + @Transactional + public Long submitGpaScore(String email, GpaScoreRequest gpaScoreRequest) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + + GpaScore newGpaScore = new GpaScore(gpaScoreRequest.toGpa(), siteUser, gpaScoreRequest.issueDate()); + newGpaScore.setSiteUser(siteUser); + GpaScore savedNewGpaScore = gpaScoreRepository.save(newGpaScore); // 저장 후 반환된 객체 + return savedNewGpaScore.getId(); // 저장된 GPA Score의 ID 반환 + } + + @Transactional + public Long submitLanguageTestScore(String email, LanguageTestScoreRequest languageTestScoreRequest) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + LanguageTest languageTest = languageTestScoreRequest.toLanguageTest(); + + LanguageTestScore newScore = new LanguageTestScore( + languageTest, languageTestScoreRequest.issueDate(), siteUser); + newScore.setSiteUser(siteUser); + LanguageTestScore savedNewScore = languageTestScoreRepository.save(newScore); // 새로 저장한 객체 + return savedNewScore.getId(); // 저장된 객체의 ID 반환 + } + + @Transactional(readOnly = true) + public GpaScoreStatusResponse getGpaScoreStatus(String email) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + List gpaScoreStatusList = + Optional.ofNullable(siteUser.getGpaScoreList()) + .map(scores -> scores.stream() + .map(GpaScoreStatus::from) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + return new GpaScoreStatusResponse(gpaScoreStatusList); + } + + @Transactional(readOnly = true) + public LanguageTestScoreStatusResponse getLanguageTestScoreStatus(String email) { + SiteUser siteUser = siteUserRepository.getByEmail(email); + List languageTestScoreStatusList = + Optional.ofNullable(siteUser.getLanguageTestScoreList()) + .map(scores -> scores.stream() + .map(LanguageTestScoreStatus::from) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); + return new LanguageTestScoreStatusResponse(languageTestScoreStatusList); + } +} diff --git a/src/main/java/com/example/solidconnection/service/RedisService.java b/src/main/java/com/example/solidconnection/service/RedisService.java index 9816a264e..93a9de74f 100644 --- a/src/main/java/com/example/solidconnection/service/RedisService.java +++ b/src/main/java/com/example/solidconnection/service/RedisService.java @@ -9,10 +9,12 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; -import static com.example.solidconnection.type.RedisConstants.*; +import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_TTL; +import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_TTL; @Service public class RedisService { + private final RedisTemplate redisTemplate; private final RedisScript incrViewCountLuaScript; @@ -28,7 +30,7 @@ public void increaseViewCount(String key) { redisTemplate.execute(incrViewCountLuaScript, Collections.singletonList(key), VIEW_COUNT_TTL.getValue()); } - public void deleteKey(String key){ + public void deleteKey(String key) { redisTemplate.opsForValue().getAndDelete(key); } diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java index 443404def..c0d58356f 100644 --- a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java +++ b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserController.java @@ -1,11 +1,20 @@ package com.example.solidconnection.siteuser.controller; -import com.example.solidconnection.siteuser.dto.*; +import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; +import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest; +import com.example.solidconnection.siteuser.dto.NicknameUpdateResponse; +import com.example.solidconnection.siteuser.dto.ProfileImageUpdateResponse; import com.example.solidconnection.siteuser.service.SiteUserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.security.Principal; @@ -13,7 +22,7 @@ @RequiredArgsConstructor @RequestMapping("/my-page") @RestController -class SiteUserController implements SiteUserControllerSwagger { +class SiteUserController { private final SiteUserService siteUserService; diff --git a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java b/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java deleted file mode 100644 index 6f479f67e..000000000 --- a/src/main/java/com/example/solidconnection/siteuser/controller/SiteUserControllerSwagger.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.example.solidconnection.siteuser.controller; - -import com.example.solidconnection.siteuser.dto.MyPageResponse; -import com.example.solidconnection.siteuser.dto.MyPageUpdateRequest; -import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.parameters.RequestBody; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import org.springframework.http.ResponseEntity; - -import java.security.Principal; - -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - -@Tag(name = "SiteUser", description = "사용자 API") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) -public interface SiteUserControllerSwagger { - - @Operation( - summary = "마이 페이지 페이지 정보 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "마이 페이지 정보 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = MyPageResponse.class) - ) - ) - } - ) - ResponseEntity getMyPageInfo(Principal principal); - - @Operation( - summary = "마이 페이지 정보 수정을 위한 데이터 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "수정 가능한 정보 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = MyPageUpdateResponse.class) - ) - ) - } - ) - ResponseEntity getMyPageInfoToUpdate(Principal principal); -} diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index f3c870ceb..d548ff852 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -3,11 +3,25 @@ import com.example.solidconnection.comment.domain.Comment; import com.example.solidconnection.post.domain.Post; import com.example.solidconnection.post.domain.PostLike; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.domain.LanguageTestScore; import com.example.solidconnection.type.Gender; import com.example.solidconnection.type.PreparationStatus; import com.example.solidconnection.type.Role; -import jakarta.persistence.*; -import lombok.*; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToMany; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; import java.time.LocalDate; import java.time.LocalDateTime; @@ -65,6 +79,13 @@ public class SiteUser { @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) private List postLikeList = new ArrayList<>(); + @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) + private List languageTestScoreList = new ArrayList<>(); + + @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) + private List gpaScoreList = new ArrayList<>(); + + public SiteUser( String email, String nickname, diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java index 892094394..66a7dbef2 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageResponse.java @@ -3,32 +3,14 @@ import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.type.Role; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "마이페이지 페이지 정보 응답") public record MyPageResponse( - @Schema(description = "닉네임", example = "nickname") String nickname, - - @Schema(description = "프로필 이미지 URL", example = "http://example.com/profile.jpg") String profileImageUrl, - - @Schema(description = "역할", example = "MENTEE") Role role, - - @Schema(description = "생년월일", example = "1990-01-01") String birth, - - @Schema(description = "이메일", example = "example@solid-conenct.net") String email, - - @Schema(description = "좋아요 누른 게시물 수", example = "0") int likedPostCount, - - @Schema(description = "좋아요 누른 멘토 수", example = "0") int likedMentorCount, - - @Schema(description = "좋아요 누른 대학 수", example = "3") int likedUniversityCount) { public static MyPageResponse of(SiteUser siteUser, int likedUniversityCount) { diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateRequest.java index bb40e075d..11584d163 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateRequest.java @@ -1,16 +1,12 @@ package com.example.solidconnection.siteuser.dto; import com.example.solidconnection.siteuser.domain.SiteUser; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -@Schema(description = "마이 페이지 정보 수정 요청") public record MyPageUpdateRequest( @NotBlank(message = "닉네임을 입력해주세요.") - @Schema(description = "변경할 닉네임", example = "NewNickname") String nickname, - @Schema(description = "변경할 프로필 이미지 URL", example = "http://example.com/new-profile.jpg", nullable = true) String profileImageUrl) { public static MyPageUpdateRequest from(SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateResponse.java index d186b08af..78583405b 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/MyPageUpdateResponse.java @@ -1,20 +1,15 @@ package com.example.solidconnection.siteuser.dto; import com.example.solidconnection.siteuser.domain.SiteUser; -import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "마이 페이지 정보 수정 응답") public record MyPageUpdateResponse( - @Schema(description = "업데이트된 사용자 닉네임", example = "UpdatedNickname") String nickname, - - @Schema(description = "업데이트된 프로필 이미지 URL", example = "http://example.com/updated-profile.jpg") String profileImageUrl) { - public static MyPageUpdateResponse from(SiteUser siteUser) { - return new MyPageUpdateResponse( - siteUser.getNickname(), - siteUser.getProfileImageUrl() - ); - } + public static MyPageUpdateResponse from(SiteUser siteUser) { + return new MyPageUpdateResponse( + siteUser.getNickname(), + siteUser.getProfileImageUrl() + ); + } } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java index 4627a7451..9b83969b4 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateRequest.java @@ -1,11 +1,9 @@ package com.example.solidconnection.siteuser.dto; -import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; public record NicknameUpdateRequest( @NotBlank(message = "닉네임을 입력해주세요.") - @Schema(description = "변경할 닉네임", example = "NewNickname") String nickname ) { } diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java index 5a967fa6e..a59e71824 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/NicknameUpdateResponse.java @@ -1,10 +1,8 @@ package com.example.solidconnection.siteuser.dto; import com.example.solidconnection.siteuser.domain.SiteUser; -import io.swagger.v3.oas.annotations.media.Schema; public record NicknameUpdateResponse( - @Schema(description = "업데이트된 사용자 닉네임", example = "UpdatedNickname") String nickname ) { public static NicknameUpdateResponse from(SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java index 127578e2f..d806fde20 100644 --- a/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java +++ b/src/main/java/com/example/solidconnection/siteuser/dto/ProfileImageUpdateResponse.java @@ -1,10 +1,8 @@ package com.example.solidconnection.siteuser.dto; import com.example.solidconnection.siteuser.domain.SiteUser; -import io.swagger.v3.oas.annotations.media.Schema; public record ProfileImageUpdateResponse( - @Schema(description = "업데이트된 프로필 이미지 URL", example = "http://example.com/updated-profile.jpg") String profileImageUrl ) { public static ProfileImageUpdateResponse from(SiteUser siteUser) { diff --git a/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java b/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java index 793627adf..a7a2e5d71 100644 --- a/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java +++ b/src/main/java/com/example/solidconnection/siteuser/service/SiteUserService.java @@ -4,7 +4,11 @@ import com.example.solidconnection.s3.S3Service; import com.example.solidconnection.s3.UploadedFileUrlResponse; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.dto.*; +import com.example.solidconnection.siteuser.dto.MyPageResponse; +import com.example.solidconnection.siteuser.dto.MyPageUpdateResponse; +import com.example.solidconnection.siteuser.dto.NicknameUpdateRequest; +import com.example.solidconnection.siteuser.dto.NicknameUpdateResponse; +import com.example.solidconnection.siteuser.dto.ProfileImageUpdateResponse; import com.example.solidconnection.siteuser.repository.LikedUniversityRepository; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.type.ImgType; @@ -19,7 +23,9 @@ import java.time.format.DateTimeFormatter; import java.util.List; -import static com.example.solidconnection.custom.exception.ErrorCode.*; +import static com.example.solidconnection.custom.exception.ErrorCode.CAN_NOT_CHANGE_NICKNAME_YET; +import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; +import static com.example.solidconnection.custom.exception.ErrorCode.PROFILE_IMAGE_NEEDED; @RequiredArgsConstructor @Service @@ -51,22 +57,6 @@ public MyPageUpdateResponse getMyPageInfoToUpdate(String email) { return MyPageUpdateResponse.from(siteUser); } - private void validateNicknameDuplicated(String nickname) { - if (siteUserRepository.existsByNickname(nickname)) { - throw new CustomException(NICKNAME_ALREADY_EXISTED); - } - } - private void validateNicknameNotChangedRecently(LocalDateTime lastModifiedAt) { - if (lastModifiedAt == null) { - return; - } - if (LocalDateTime.now().isBefore(lastModifiedAt.plusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES))) { - String formatLastModifiedAt - = String.format("(마지막 수정 시간 : %s)", NICKNAME_LAST_CHANGE_DATE_FORMAT.format(lastModifiedAt)); - throw new CustomException(CAN_NOT_CHANGE_NICKNAME_YET, formatLastModifiedAt); - } - } - /* * 관심 대학교 목록을 조회한다. * */ @@ -79,7 +69,6 @@ public List getWishUniversity(String emai .toList(); } - /* * 프로필 이미지를 수정한다. * */ @@ -89,7 +78,7 @@ public ProfileImageUpdateResponse updateProfileImage(String email, MultipartFile validateProfileImage(imageFile); // 프로필 이미지를 처음 수정하는 경우에는 deleteExProfile 수행하지 않음 - if(!isDefaultProfileImage(siteUser.getProfileImageUrl())){ + if (!isDefaultProfileImage(siteUser.getProfileImageUrl())) { s3Service.deleteExProfile(email); } UploadedFileUrlResponse uploadedFileUrlResponse = s3Service.uploadFile(imageFile, ImgType.PROFILE); @@ -104,7 +93,6 @@ private void validateProfileImage(MultipartFile imageFile) { throw new CustomException(PROFILE_IMAGE_NEEDED); } } - private boolean isDefaultProfileImage(String profileImageUrl) { String prefix = "profile/"; return profileImageUrl == null || !profileImageUrl.startsWith(prefix); @@ -126,4 +114,21 @@ public NicknameUpdateResponse updateNickname(String email, NicknameUpdateRequest return NicknameUpdateResponse.from(siteUser); } + + private void validateNicknameDuplicated(String nickname) { + if (siteUserRepository.existsByNickname(nickname)) { + throw new CustomException(NICKNAME_ALREADY_EXISTED); + } + } + + private void validateNicknameNotChangedRecently(LocalDateTime lastModifiedAt) { + if (lastModifiedAt == null) { + return; + } + if (LocalDateTime.now().isBefore(lastModifiedAt.plusDays(MIN_DAYS_BETWEEN_NICKNAME_CHANGES))) { + String formatLastModifiedAt + = String.format("(마지막 수정 시간 : %s)", NICKNAME_LAST_CHANGE_DATE_FORMAT.format(lastModifiedAt)); + throw new CustomException(CAN_NOT_CHANGE_NICKNAME_YET, formatLastModifiedAt); + } + } } diff --git a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java b/src/main/java/com/example/solidconnection/university/controller/UniversityController.java index 4d0609549..2bab9da1a 100644 --- a/src/main/java/com/example/solidconnection/university/controller/UniversityController.java +++ b/src/main/java/com/example/solidconnection/university/controller/UniversityController.java @@ -24,7 +24,7 @@ @RequiredArgsConstructor @RequestMapping("/university") @RestController -public class UniversityController implements UniversityControllerSwagger { +public class UniversityController { private final UniversityService universityService; private final UniversityRecommendService universityRecommendService; diff --git a/src/main/java/com/example/solidconnection/university/controller/UniversityControllerSwagger.java b/src/main/java/com/example/solidconnection/university/controller/UniversityControllerSwagger.java deleted file mode 100644 index e140917f2..000000000 --- a/src/main/java/com/example/solidconnection/university/controller/UniversityControllerSwagger.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.example.solidconnection.university.controller; - -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.university.dto.IsLikeResponse; -import com.example.solidconnection.university.dto.LikeResultResponse; -import com.example.solidconnection.university.dto.UniversityDetailResponse; -import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; -import com.example.solidconnection.university.dto.UniversityRecommendsResponse; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.security.SecurityRequirements; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; - -import java.security.Principal; -import java.util.List; - -import static com.example.solidconnection.config.swagger.SwaggerConfig.ACCESS_TOKEN; - -@Tag(name = "University", description = "대학 및 대학 지원을 위한 정보 API") -@SecurityRequirements -@SecurityRequirement(name = ACCESS_TOKEN) -public interface UniversityControllerSwagger { - - @Operation( - summary = "대학 추천 목록 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "사용자별 개인화 된 대학 추천 목록 또는 일반 추천 목록 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UniversityRecommendsResponse.class) - ) - ) - } - ) - ResponseEntity getUniversityRecommends(Principal principal); - - @Operation( - summary = "좋아요한 대학 목록 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "사용자가 좋아요한 대학 목록 반환", - content = @Content( - mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = UniversityInfoForApplyPreviewResponse.class)) - ) - ) - } - ) - ResponseEntity> getMyWishUniversity(Principal principal); - - @Operation( - summary = "대학 좋아요 여부 확인", - responses = { - @ApiResponse( - responseCode = "200", - description = "대학 좋아요 여부 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = IsLikeResponse.class) - ) - ) - } - ) - ResponseEntity getIsLiked(Principal principal, @PathVariable Long universityInfoForApplyId); - - @Operation( - summary = "대학 좋아요 하기", - responses = { - @ApiResponse( - responseCode = "200", - description = "대학 좋아요 결과 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = LikeResultResponse.class) - ) - ) - } - ) - ResponseEntity addWishUniversity(Principal principal, @PathVariable Long universityInfoForApplyId); - - @Operation( - summary = "대학 상세 정보 조회", - responses = { - @ApiResponse( - responseCode = "200", - description = "대학 상세 정보 반환", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = UniversityDetailResponse.class) - ) - ) - } - ) - ResponseEntity getUniversityDetails(@PathVariable Long universityInfoForApplyId); - - @Operation( - summary = "대학 검색", - responses = { - @ApiResponse( - responseCode = "200", - description = "검색 조건에 맞는 대학 목록 반환", - content = @Content( - mediaType = "application/json", - array = @ArraySchema(schema = @Schema(implementation = UniversityInfoForApplyPreviewResponse.class)) - ) - ) - } - ) - ResponseEntity> searchUniversity( - @RequestParam(required = false, defaultValue = "") String region, - @RequestParam(required = false, defaultValue = "") List keyword, - @RequestParam(required = false, defaultValue = "") LanguageTestType testType, - @RequestParam(required = false, defaultValue = "") String testScore); -} diff --git a/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java b/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java index bd62505bd..7d4aebbf9 100644 --- a/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/IsLikeResponse.java @@ -1,9 +1,5 @@ package com.example.solidconnection.university.dto; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "대학교 '좋아요' 여부") public record IsLikeResponse( - @Schema(description = "대학교 '좋아요' 여부", example = "true") boolean isLike) { } diff --git a/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java b/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java index 7b02108a2..8cc7b9733 100644 --- a/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/LanguageRequirementResponse.java @@ -2,14 +2,9 @@ import com.example.solidconnection.type.LanguageTestType; import com.example.solidconnection.university.domain.LanguageRequirement; -import io.swagger.v3.oas.annotations.media.Schema; -@Schema(description = "어학 성적 요구사항 응답") public record LanguageRequirementResponse( - @Schema(description = "어학 시험 유형", example = "TOEFL_IBT") LanguageTestType languageTestType, - - @Schema(description = "최소 점수 요구사항", example = "100") String minScore) implements Comparable { public static LanguageRequirementResponse from(LanguageRequirement languageRequirement) { diff --git a/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java b/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java index 29e81bc95..c67f2e408 100644 --- a/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/LikeResultResponse.java @@ -1,9 +1,5 @@ package com.example.solidconnection.university.dto; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "좋아요 결과 응답 데이터") public record LikeResultResponse( - @Schema(description = "좋아요 결과", example = "LIKE_SUCCESS") String result) { } diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java index 118bfdaa6..4121654a3 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UniversityDetailResponse.java @@ -2,90 +2,35 @@ import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; -@Schema(description = "대학 세부 사항 응답 데이터") public record UniversityDetailResponse( - - @Schema(description = "대학 지원을 위한 정보 id", example = "1") long id, - - @Schema(description = "모집 시기", example = "2024-2") String term, - - @Schema(description = "국문 이름", example = "그라츠 대학") String koreanName, - - @Schema(description = "영문 이름", example = "University of Graz") String englishName, - - @Schema(description = "서브스에서 사용되는 이름", example = "university_of_graz") String formatName, - - @Schema(description = "지역", example = "유럽") String region, - - @Schema(description = "국가", example = "오스트리아") String country, - - @Schema(description = "대학 홈페이지 URL", example = "http://www.graz.ac.kr") String homepageUrl, - - @Schema(description = "대학 로고 이미지 URL", example = "http://example.com/logo.jpg") String logoImageUrl, - - @Schema(description = "대학 배경 이미지 URL", example = "http://example.com/background.jpg") String backgroundImageUrl, - - @Schema(description = "현지에 대한 세부 사항", example = "Detailed information about local conditions.") String detailsForLocal, - - @Schema(description = "모집 인원", example = "2") int studentCapacity, - - @Schema(description = "등록금 유형", example = "본교납부형") String tuitionFeeType, - - @Schema(description = "파견 가능 학기", example = "1") String semesterAvailableForDispatch, - - @ArraySchema(arraySchema = @Schema(description = "어학 성적 요구사항")) List languageRequirements, - - @Schema(description = "어학 성적 세부 사항", example = "Minimum TOEFL score required is 80.") String detailsForLanguage, - - @Schema(description = "GPA", example = "3.5") String gpaRequirement, - - @Schema(description = "GPA 계산 기준", example = "4.0") String gpaRequirementCriteria, - - @Schema(description = "필요 학기", example = "2") String semesterRequirement, - - @Schema(description = "지원에 대한 세부 사항", example = "Application process detailed here.") String detailsForApply, - - @Schema(description = "전공에 대한 세부 사항", example = "Major requirements detailed here.") String detailsForMajor, - - @Schema(description = "숙박에 대한 세부 사항", example = "Accommodation details provided.") String detailsForAccommodation, - - @Schema(description = "영어 과정 세부 사항", example = "English courses available for international students.") String detailsForEnglishCourse, - - @Schema(description = "기타 세부 사항", example = "Additional university details.") String details, - - @Schema(description = "숙박 시설 URL", example = "http://example.com/accommodation") String accommodationUrl, - - @Schema(description = "영어 수업 정보 URL", example = "http://example.com/englishCourses") String englishCourseUrl) { public static UniversityDetailResponse of( diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java index fe5713c67..93214b056 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UniversityInfoForApplyPreviewResponse.java @@ -1,43 +1,25 @@ package com.example.solidconnection.university.dto; import com.example.solidconnection.university.domain.UniversityInfoForApply; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; import java.util.Collections; import java.util.List; -@Schema(description = "대학 미리보기 응답") public record UniversityInfoForApplyPreviewResponse( - @Schema(description = "대학 지원을 위한 정보 id", example = "1") long id, - - @Schema(description = "모집 시기", example = "2024-2") String term, - - @Schema(description = "국문 이름", example = "그라츠대학") String koreanName, - - @Schema(description = "지역", example = "유럽") String region, - - @Schema(description = "국가", example = "오스트리아") String country, - - @Schema(description = "대학 로고 이미지 URL", example = "http://example.com/logo.jpg") String logoImageUrl, - - @Schema(description = "모집 인원", example = "2") int studentCapacity, - - @ArraySchema(arraySchema = @Schema(description = "어학시험 요구사항")) List languageRequirements) { public static UniversityInfoForApplyPreviewResponse from(UniversityInfoForApply universityInfoForApply) { List languageRequirementResponses = new java.util.ArrayList<>( universityInfoForApply.getLanguageRequirements().stream() - .map(LanguageRequirementResponse::from) - .toList()); + .map(LanguageRequirementResponse::from) + .toList()); Collections.sort(languageRequirementResponses); return new UniversityInfoForApplyPreviewResponse( diff --git a/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java b/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java index 947a7e78c..057061f3e 100644 --- a/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java +++ b/src/main/java/com/example/solidconnection/university/dto/UniversityRecommendsResponse.java @@ -1,12 +1,7 @@ package com.example.solidconnection.university.dto; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Schema; - import java.util.List; -@Schema(description = "추천 대학 목록 응답") public record UniversityRecommendsResponse( - @ArraySchema(arraySchema = @Schema(description = "추천된 대학 목록", implementation = UniversityInfoForApplyPreviewResponse.class)) List recommendedUniversities) { } diff --git a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java index 0beac6763..dd84cfbf5 100644 --- a/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java +++ b/src/main/java/com/example/solidconnection/university/repository/custom/UniversityFilterRepositoryImpl.java @@ -85,8 +85,8 @@ public List findByRegionCodeAndKeywordsAndLanguageTestTy .and(universityInfoForApply.term.eq(term))) .fetch(); - if(testScore == null || testScore.isEmpty()) { - if(testType != null) { + if (testScore == null || testScore.isEmpty()) { + if (testType != null) { return filteredUniversityInfoForApply.stream() .filter(uifa -> uifa.getLanguageRequirements().stream() .anyMatch(lr -> lr.getLanguageTestType().equals(testType))) diff --git a/src/main/java/com/example/solidconnection/university/service/UniversityService.java b/src/main/java/com/example/solidconnection/university/service/UniversityService.java index c0cbe2c05..708374e96 100644 --- a/src/main/java/com/example/solidconnection/university/service/UniversityService.java +++ b/src/main/java/com/example/solidconnection/university/service/UniversityService.java @@ -8,7 +8,11 @@ import com.example.solidconnection.university.domain.LikedUniversity; import com.example.solidconnection.university.domain.University; import com.example.solidconnection.university.domain.UniversityInfoForApply; -import com.example.solidconnection.university.dto.*; +import com.example.solidconnection.university.dto.IsLikeResponse; +import com.example.solidconnection.university.dto.LikeResultResponse; +import com.example.solidconnection.university.dto.UniversityDetailResponse; +import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponse; +import com.example.solidconnection.university.dto.UniversityInfoForApplyPreviewResponses; import com.example.solidconnection.university.repository.UniversityInfoForApplyRepository; import com.example.solidconnection.university.repository.custom.UniversityFilterRepositoryImpl; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/example/solidconnection/util/RedisUtils.java b/src/main/java/com/example/solidconnection/util/RedisUtils.java index ef91dfc3d..6c56fa73f 100644 --- a/src/main/java/com/example/solidconnection/util/RedisUtils.java +++ b/src/main/java/com/example/solidconnection/util/RedisUtils.java @@ -11,7 +11,10 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static com.example.solidconnection.type.RedisConstants.*; +import static com.example.solidconnection.type.RedisConstants.CREATE_LOCK_PREFIX; +import static com.example.solidconnection.type.RedisConstants.REFRESH_LOCK_PREFIX; +import static com.example.solidconnection.type.RedisConstants.VALIDATE_VIEW_COUNT_KEY_PREFIX; +import static com.example.solidconnection.type.RedisConstants.VIEW_COUNT_KEY_PREFIX; @Component public class RedisUtils { @@ -70,6 +73,6 @@ public String getRefreshLockKey(String key) { public boolean isCacheExpiringSoon(String key, Long defaultTtl, Double percent) { Long leftTtl = redisTemplate.getExpire(key); - return defaultTtl != null && ((double) leftTtl /defaultTtl)*100 < percent; + return defaultTtl != null && ((double) leftTtl / defaultTtl) * 100 < percent; } } diff --git a/src/main/resources/application-local.yml b/src/main/resources/application-local.yml deleted file mode 100644 index f0baec690..000000000 --- a/src/main/resources/application-local.yml +++ /dev/null @@ -1,33 +0,0 @@ -spring: - config: - activate: - on-profile: local - - jpa: - hibernate: - ddl-auto: create - generate-ddl: true - show-sql: true - database: mysql - defer-datasource-initialization: true - properties: - hibernate: - format_sql: true - - sql: - init: - mode: always - - datasource: - driverClassName: com.mysql.cj.jdbc.Driver - url: jdbc:mysql://localhost/solid_connection?serverTimezone=Asia/Seoul&characterEncoding=UTF-8 - username: solid_connection_local_username - password: solid_connection_local_password - - data: - redis: - host: localhost - port: 6379 - -kakao: - redirect_uri: "http://localhost:8080/auth/kakao" diff --git a/src/main/resources/application-test.yml b/src/main/resources/application-test.yml deleted file mode 100644 index bf25bb62f..000000000 --- a/src/main/resources/application-test.yml +++ /dev/null @@ -1,29 +0,0 @@ -spring: - config: - activate: - on-profile: test - - jpa: - database-platform: org.hibernate.dialect.H2Dialect - hibernate: - ddl-auto: create - generate-ddl: true - show-sql: true - database: mysql - defer-datasource-initialization: true - properties: - hibernate: - format_sql: true - - datasource: - url: jdbc:h2:mem:testdb - username: sa - password: - - data: - redis: - host: localhost - port: 6379 - -kakao: - redirect_uri: "http://localhost:8080/auth/kakao" diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b114af84e..a644c7e9f 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,15 +1,9 @@ spring: - profiles: - group: - prod: - - prod - - secret - local: - - local - - secret - test: - - test - - secret + config: + import: + - classpath:/secret/application-cloud.yml + - classpath:/secret/application-db.yml + - classpath:/secret/application-variable.yml tomcat: threads: @@ -20,35 +14,10 @@ spring: max-file-size: 10MB max-request-size: 10MB - jpa: - hibernate: - ddl-auto: none - generate-ddl: false - show-sql: false - database: mysql - defer-datasource-initialization: true - mvc: path match: matching-strategy: ANT_PATH_MATCHER - sql: - init: - mode: never - -jwt: - secret: - aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee - -kakao: - token_url: "https://kauth.kakao.com/oauth/token" - user_info_url: "https://kapi.kakao.com/v2/user/me" - -view: - count: - scheduling: - delay: 3000 - management: endpoints: web: diff --git a/src/main/resources/secret b/src/main/resources/secret new file mode 160000 index 000000000..82f7ec98b --- /dev/null +++ b/src/main/resources/secret @@ -0,0 +1 @@ +Subproject commit 82f7ec98b47aa3da3874d0ac2d94f91784078b13 diff --git a/src/main/resources/static/index.html b/src/main/resources/static/index.html deleted file mode 100644 index c3dca01d5..000000000 --- a/src/main/resources/static/index.html +++ /dev/null @@ -1,3 +0,0 @@ - -

Solid Connection Backend Page

-이 페이지가 보이면 백엔드 배포 서버가 잘 작동하고 있다는 것입니다. \ No newline at end of file diff --git a/src/test/java/com/example/solidconnection/e2e/ApplicationSubmissionTest.java b/src/test/java/com/example/solidconnection/e2e/ApplicationSubmissionTest.java deleted file mode 100644 index a12612071..000000000 --- a/src/test/java/com/example/solidconnection/e2e/ApplicationSubmissionTest.java +++ /dev/null @@ -1,254 +0,0 @@ -package com.example.solidconnection.e2e; - -import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.dto.ScoreRequest; -import com.example.solidconnection.application.dto.UniversityChoiceRequest; -import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.config.token.TokenService; -import com.example.solidconnection.config.token.TokenType; -import com.example.solidconnection.custom.response.ErrorResponse; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.LanguageTestType; -import com.example.solidconnection.type.VerifyStatus; -import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; - -import static com.example.solidconnection.application.service.ApplicationSubmissionService.APPLICATION_UPDATE_COUNT_LIMIT; -import static com.example.solidconnection.custom.exception.ErrorCode.APPLY_UPDATE_LIMIT_EXCEED; -import static com.example.solidconnection.custom.exception.ErrorCode.CANT_APPLY_FOR_SAME_UNIVERSITY; -import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -@DisplayName("지원 정보 제출 테스트") -class ApplicationSubmissionTest extends UniversityDataSetUpEndToEndTest { - - @Autowired - private ApplicationRepository applicationRepository; - - @Autowired - private SiteUserRepository siteUserRepository; - - @Autowired - private TokenService tokenService; - - private final String email = "email@email.com"; - private String accessToken; - private SiteUser siteUser; - - @BeforeEach - public void setUpUserAndToken() { - // setUp - 회원 정보 저장 - siteUser = siteUserRepository.save(createSiteUserByEmail(email)); - - // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); - } - - @Test - void 대학교_성적과_어학성적을_처음으로_제출한다() { - // request - body 생성 및 요청 - ScoreRequest request = new ScoreRequest(LanguageTestType.TOEFL_IBT, "80", - "languageTestReportUrl", 4.0, 4.5, "gpaReportUrl"); - RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/score") - .then().log().all() - .statusCode(HttpStatus.OK.value()); - - Application application = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - assertAll("대학교 성적과 어학 성적을 저장한다.", - () -> assertThat(application.getId()).isNotNull(), - () -> assertThat(application.getSiteUser().getId()).isEqualTo(siteUser.getId()), - () -> assertThat(application.getLanguageTest().getLanguageTestType()).isEqualTo(request.languageTestType()), - () -> assertThat(application.getLanguageTest().getLanguageTestScore()).isEqualTo(request.languageTestScore()), - () -> assertThat(application.getLanguageTest().getLanguageTestReportUrl()).isEqualTo(request.languageTestReportUrl()), - () -> assertThat(application.getGpa().getGpa()).isEqualTo(request.gpa()), - () -> assertThat(application.getGpa().getGpaReportUrl()).isEqualTo(request.gpaReportUrl()), - () -> assertThat(application.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING), - () -> assertThat(application.getUpdateCount()).isZero()); - } - - @Test - void 대학교_성적과_어학성적을_다시_제출한다() { - // setUp - 성적 정보 저장 - ScoreRequest firstRequest = new ScoreRequest(LanguageTestType.TOEFL_IBT, "80", - "languageTestReportUrl", 4.0, 4.5, "gpaReportUrl"); - applicationRepository.save(new Application(siteUser, firstRequest.toGpa(), firstRequest.toLanguageTest(),term)); - - // request - body 생성 및 요청 - ScoreRequest secondRequest = new ScoreRequest(LanguageTestType.TOEFL_IBT, "90", - "languageTestReportUrl", 4.1, 4.5, "gpaReportUrl"); - RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(secondRequest) - .contentType("application/json") - .log().all() - .post("/application/score") - .then().log().all() - .statusCode(HttpStatus.OK.value()); - - Application updatedApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - assertAll("대학교 성적과 어학 성적을 수정한다. 이때 수정 횟수는 증가하지 않고, 성적 승인 상태는 PENDING 으로 바뀐다.", - () -> assertThat(updatedApplication.getId()).isNotNull(), - () -> assertThat(updatedApplication.getSiteUser().getId()).isEqualTo(siteUser.getId()), - () -> assertThat(updatedApplication.getLanguageTest().getLanguageTestType()).isEqualTo(secondRequest.languageTestType()), - () -> assertThat(updatedApplication.getLanguageTest().getLanguageTestScore()).isEqualTo(secondRequest.languageTestScore()), - () -> assertThat(updatedApplication.getLanguageTest().getLanguageTestReportUrl()).isEqualTo(secondRequest.languageTestReportUrl()), - () -> assertThat(updatedApplication.getGpa().getGpa()).isEqualTo(secondRequest.gpa()), - () -> assertThat(updatedApplication.getGpa().getGpaReportUrl()).isEqualTo(secondRequest.gpaReportUrl()), - () -> assertThat(updatedApplication.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING), - () -> assertThat(updatedApplication.getUpdateCount()).isZero()); - } - - @Test - void 성적_제출_후_지망_대학을_제출한다() { - // setUp - 성적 정보 저장 - ScoreRequest firstRequest = new ScoreRequest(LanguageTestType.TOEFL_IBT, "80", - "languageTestReportUrl", 4.0, 4.5, "gpaReportUrl"); - applicationRepository.save(new Application(siteUser, firstRequest.toGpa(), firstRequest.toLanguageTest(),term)); - - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId(), 메이지대학_지원_정보.getId()); - RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.OK.value()); - - Application application = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - assertAll("지망 대학교를 저장한다.", - () -> assertThat(application.getId()).isNotNull(), - () -> assertThat(application.getSiteUser().getId()).isEqualTo(siteUser.getId()), - () -> assertThat(application.getFirstChoiceUniversity().getId()).isEqualTo(request.firstChoiceUniversityId()), - () -> assertThat(application.getSecondChoiceUniversity().getId()).isEqualTo(request.secondChoiceUniversityId()), - () -> assertThat(application.getThirdChoiceUniversity().getId()).isEqualTo(request.thirdChoiceUniversityId()), - () -> assertThat(application.getNicknameForApply()).isNotNull(), - () -> assertThat(application.getVerifyStatus()).isEqualTo(VerifyStatus.PENDING), - () -> assertThat(application.getUpdateCount()).isZero()); - } - - @Test - void 지망_대학을_수정한다() { - // setUp - 성적 정보와 지망 대학 저장 - ScoreRequest firstRequest = new ScoreRequest(LanguageTestType.TOEFL_IBT, "80", - "languageTestReportUrl", 4.0, 4.5, "gpaReportUrl"); - applicationRepository.save(new Application(siteUser, firstRequest.toGpa(), firstRequest.toLanguageTest(),term)) - .updateUniversityChoice(괌대학_A_지원_정보, 괌대학_B_지원_정보, 네바다주립대학_라스베이거스_지원_정보, "nickname"); - Application initialApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId(), 메이지대학_지원_정보.getId()); - RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.OK.value()); - - Application updatedApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - assertAll("지망 대학교를 수정한다. 이때 수정 횟수는 증가하고, 성적 승인 상태는 바뀌지 않는다.", - () -> assertThat(updatedApplication.getId()).isNotNull(), - () -> assertThat(updatedApplication.getSiteUser().getId()).isEqualTo(siteUser.getId()), - () -> assertThat(updatedApplication.getFirstChoiceUniversity().getId()).isEqualTo(request.firstChoiceUniversityId()), - () -> assertThat(updatedApplication.getSecondChoiceUniversity().getId()).isEqualTo(request.secondChoiceUniversityId()), - () -> assertThat(updatedApplication.getThirdChoiceUniversity().getId()).isEqualTo(request.thirdChoiceUniversityId()), - () -> assertThat(updatedApplication.getNicknameForApply()).isNotNull(), - () -> assertThat(updatedApplication.getVerifyStatus()).isEqualTo(initialApplication.getVerifyStatus()), - () -> assertThat(updatedApplication.getUpdateCount()).isEqualTo(initialApplication.getUpdateCount())); - } - - @Test - void 지망_대학을_최대_수정_가능_횟수보다_더_수정하려고하면_예외_응답을_반환한다() { - // setUp - 성적 정보와 지망 대학 저장 - ScoreRequest firstRequest = new ScoreRequest(LanguageTestType.TOEFL_IBT, "80", - "languageTestReportUrl", 4.0, 4.5, "gpaReportUrl"); - applicationRepository.save(new Application(siteUser, firstRequest.toGpa(), firstRequest.toLanguageTest(),term)); - Application initialApplication = applicationRepository.getApplicationBySiteUserAndTerm(siteUser,term); - - // setUp - 지망 대학을 한계까지 수정 - for (int i = 0; i <= APPLICATION_UPDATE_COUNT_LIMIT; i++) { - initialApplication.updateUniversityChoice(괌대학_A_지원_정보, 괌대학_B_지원_정보, 네바다주립대학_라스베이거스_지원_정보, "nickname"); - applicationRepository.save(initialApplication); - } - - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId(), 메이지대학_지원_정보.getId()); - ErrorResponse errorResponse = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract().as(ErrorResponse.class); - - assertThat(errorResponse.message()).isEqualTo(APPLY_UPDATE_LIMIT_EXCEED.getMessage()); - } - - @Test - void 일지망_대학과_이지망_대학이_같으면_예외_응답을_반환한다() { - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 그라츠대학_지원_정보.getId(), 메이지대학_지원_정보.getId()); - ErrorResponse errorResponse = RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract().as(ErrorResponse.class); - - assertThat(errorResponse.message()).isEqualTo(CANT_APPLY_FOR_SAME_UNIVERSITY.getMessage()); - } - - @Test - void 일지망_대학과_삼지망_대학이_같으면_예외_응답을_반환한다() { - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId(), 그라츠대학_지원_정보.getId()); - ErrorResponse errorResponse = RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract().as(ErrorResponse.class); - - assertThat(errorResponse.message()).isEqualTo(CANT_APPLY_FOR_SAME_UNIVERSITY.getMessage()); - } - - @Test - void 이지망_대학과_삼지망_대학이_같으면_예외_응답을_반환한다() { - // request - body 생성 및 요청 - UniversityChoiceRequest request = new UniversityChoiceRequest(그라츠대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId(), 코펜하겐IT대학_지원_정보.getId()); - ErrorResponse errorResponse = RestAssured.given() - .header("Authorization", "Bearer " + accessToken) - .body(request) - .contentType("application/json") - .log().all() - .post("/application/university") - .then().log().all() - .statusCode(HttpStatus.BAD_REQUEST.value()) - .extract().as(ErrorResponse.class); - - assertThat(errorResponse.message()).isEqualTo(CANT_APPLY_FOR_SAME_UNIVERSITY.getMessage()); - } -} diff --git a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java index 7472616f6..62fae2731 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java @@ -76,7 +76,7 @@ class SignUpTest extends BaseEndToEndTest { List interestedRegionNames = List.of("유럽"); List interestedCountryNames = List.of("프랑스", "독일"); SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, interestedRegionNames, interestedCountryNames, - PreparationStatus.CONSIDERING, "nickname", "profile", Gender.FEMALE, "2000-01-01"); + PreparationStatus.CONSIDERING, "nickname", Gender.FEMALE, "profile", "2000-01-01"); SignUpResponse response = RestAssured.given().log().all() .contentType(ContentType.JSON) .body(signUpRequest) @@ -127,7 +127,7 @@ class SignUpTest extends BaseEndToEndTest { // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, - PreparationStatus.CONSIDERING, alreadyExistNickname, "profile", Gender.FEMALE, "2000-01-01"); + PreparationStatus.CONSIDERING, alreadyExistNickname, Gender.FEMALE, "profile", "2000-01-01"); ErrorResponse errorResponse = RestAssured.given().log().all() .contentType(ContentType.JSON) .body(signUpRequest) @@ -153,7 +153,7 @@ class SignUpTest extends BaseEndToEndTest { // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, - PreparationStatus.CONSIDERING, "nickname0", "profile", Gender.FEMALE, "2000-01-01"); + PreparationStatus.CONSIDERING, "nickname0", Gender.FEMALE, "profile", "2000-01-01"); ErrorResponse errorResponse = RestAssured.given().log().all() .contentType(ContentType.JSON) .body(signUpRequest) @@ -169,8 +169,7 @@ class SignUpTest extends BaseEndToEndTest { @Test void 유효하지_않은_카카오_토큰으로_회원가입을_하면_예외를_응답한다() { SignUpRequest signUpRequest = new SignUpRequest("invalid", null, null, - PreparationStatus.CONSIDERING, "nickname", "profile", Gender.FEMALE, "2000-01-01"); - + PreparationStatus.CONSIDERING, "nickname", Gender.FEMALE, "profile", "2000-01-01"); ErrorResponse errorResponse = RestAssured.given().log().all() .contentType(ContentType.JSON) .body(signUpRequest) diff --git a/src/test/java/com/example/solidconnection/e2e/VerifyStatusQueryTest.java b/src/test/java/com/example/solidconnection/e2e/VerifyStatusQueryTest.java deleted file mode 100644 index 856c4cdca..000000000 --- a/src/test/java/com/example/solidconnection/e2e/VerifyStatusQueryTest.java +++ /dev/null @@ -1,150 +0,0 @@ -package com.example.solidconnection.e2e; - -import com.example.solidconnection.application.domain.Application; -import com.example.solidconnection.application.dto.VerifyStatusResponse; -import com.example.solidconnection.application.repository.ApplicationRepository; -import com.example.solidconnection.config.token.TokenService; -import com.example.solidconnection.config.token.TokenType; -import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import com.example.solidconnection.type.VerifyStatus; -import io.restassured.RestAssured; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -import static com.example.solidconnection.e2e.DynamicFixture.createDummyGpa; -import static com.example.solidconnection.e2e.DynamicFixture.createDummyLanguageTest; -import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; - -@DisplayName("지원 상태 조회 테스트") -class VerifyStatusQueryTest extends UniversityDataSetUpEndToEndTest { - - @Autowired - private SiteUserRepository siteUserRepository; - - @Autowired - private TokenService tokenService; - - @Autowired - private ApplicationRepository applicationRepository; - - private String accessToken; - private SiteUser siteUser; - - @BeforeEach - public void setUpUserAndToken() { - // setUp - 회원 정보 저장 - String email = "email@email.com"; - siteUser = siteUserRepository.save(createSiteUserByEmail(email)); - - // setUp - 엑세스 토큰 생성과 리프레시 토큰 생성 및 저장 - accessToken = tokenService.generateToken(email, TokenType.ACCESS); - String refreshToken = tokenService.generateToken(email, TokenType.REFRESH); - tokenService.saveToken(refreshToken, TokenType.REFRESH); - } - - @Test - void 아무것도_제출하지_않은_상태를_반환한다() { - // request - 요청 - VerifyStatusResponse response = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .when().get("/application/status") - .then().log().all() - .statusCode(200) - .extract().as(VerifyStatusResponse.class); - - assertAll( - () -> assertThat(response.status()).isEqualTo("NOT_SUBMITTED"), - () -> assertThat(response.updateCount()).isZero() - ); - } - - @Test - void 성적만_제출한_상태를_반환한다() { - // setUp - 성적만 제출한 상태 - Application application = new Application(siteUser, createDummyGpa(), createDummyLanguageTest(),term); - applicationRepository.save(application); - - // request - 요청 - VerifyStatusResponse response = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .when().get("/application/status") - .then().log().all() - .statusCode(200) - .extract().as(VerifyStatusResponse.class); - - assertAll( - () -> assertThat(response.status()).isEqualTo("SCORE_SUBMITTED"), - () -> assertThat(response.updateCount()).isZero() - ); - } - - @Test - void 성적과_대학을_모두_제출하고_승인을_기대라는_상태를_반환한다() { - // setUp - 성적과 대학을 모두 제출한 상태 - Application application = new Application(siteUser, createDummyGpa(), createDummyLanguageTest(),term); - application.updateUniversityChoice(괌대학_B_지원_정보, 괌대학_A_지원_정보, 네바다주립대학_라스베이거스_지원_정보, "닉네임"); - applicationRepository.save(application); - - // request - 요청 - VerifyStatusResponse response = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .when().get("/application/status") - .then().log().all() - .statusCode(200) - .extract().as(VerifyStatusResponse.class); - - assertAll( - () -> assertThat(response.status()).isEqualTo("SUBMITTED_PENDING"), - () -> assertThat(response.updateCount()).isZero() - ); - } - - @Test - void 성적과_대학을_모두_제출했지만_승인이_반려된_상태를_반환한다() { - // setUp - 성적과 대학을 모두 제출했지만, 승인 거절 - Application application = new Application(siteUser, createDummyGpa(), createDummyLanguageTest(),term); - application.updateUniversityChoice(괌대학_B_지원_정보, 괌대학_A_지원_정보, 네바다주립대학_라스베이거스_지원_정보,"닉네임"); - application.setVerifyStatus(VerifyStatus.REJECTED); - applicationRepository.save(application); - - // request - 요청 - VerifyStatusResponse response = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .when().get("/application/status") - .then().log().all() - .statusCode(200) - .extract().as(VerifyStatusResponse.class); - - assertAll( - () -> assertThat(response.status()).isEqualTo("SUBMITTED_REJECTED"), - () -> assertThat(response.updateCount()).isZero() - ); - } - - @Test - void 성적과_대학을_모두_제출했으며_승인이_된_상태를_반환한다() { - // setUp - 성적과 대학을 모두 제출했으며, 승인이 된 상태 - Application application = new Application(siteUser, createDummyGpa(), createDummyLanguageTest(),term); - application.updateUniversityChoice(괌대학_B_지원_정보, 괌대학_A_지원_정보, 네바다주립대학_라스베이거스_지원_정보, "닉네임"); - application.setVerifyStatus(VerifyStatus.APPROVED); - applicationRepository.save(application); - - // request - 요청 - VerifyStatusResponse response = RestAssured.given().log().all() - .header("Authorization", "Bearer " + accessToken) - .when().get("/application/status") - .then().log().all() - .statusCode(200) - .extract().as(VerifyStatusResponse.class); - - assertAll( - () -> assertThat(response.status()).isEqualTo("SUBMITTED_APPROVED"), - () -> assertThat(response.updateCount()).isZero() - ); - } -} diff --git a/src/test/java/com/example/solidconnection/unit/repository/GpaScoreRepositoryTest.java b/src/test/java/com/example/solidconnection/unit/repository/GpaScoreRepositoryTest.java new file mode 100644 index 000000000..e3fa680c2 --- /dev/null +++ b/src/test/java/com/example/solidconnection/unit/repository/GpaScoreRepositoryTest.java @@ -0,0 +1,93 @@ +package com.example.solidconnection.unit.repository; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@ActiveProfiles("test") +@DisplayName("학점 레포지토리 테스트") +@Transactional +public class GpaScoreRepositoryTest { + @Autowired + private SiteUserRepository siteUserRepository; + @Autowired + private GpaScoreRepository gpaScoreRepository; + + private SiteUser siteUser; + + @BeforeEach + public void setUp() { + siteUser = createSiteUser(); + siteUserRepository.save(siteUser); + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE + ); + } + + @Test + public void 사용자의_학점을_조회한다_기존이력_없을_때() { + Optional gpaScoreBySiteUser = gpaScoreRepository.findGpaScoreBySiteUser(siteUser); + assertThat(gpaScoreBySiteUser).isEqualTo(Optional.empty()); + } + + @Test + public void 사용자의_학점을_조회한다_기존이력_있을_때() { + GpaScore gpaScore = new GpaScore( + new Gpa(4.5, 4.5, "http://example.com/gpa-report.pdf"), + siteUser, + LocalDate.of(2024, 10, 10) + ); + gpaScore.setSiteUser(siteUser); + gpaScoreRepository.save(gpaScore); + + Optional gpaScoreBySiteUser = gpaScoreRepository.findGpaScoreBySiteUser(siteUser); + assertThat(gpaScoreBySiteUser).isEqualTo(Optional.of(gpaScore)); + } + + @Test + public void 아이디와_사용자정보로_사용자의_학점을_조회한다_기존이력_없을_때() { + Optional gpaScoreBySiteUser = gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, 1L); + assertThat(gpaScoreBySiteUser).isEqualTo(Optional.empty()); + } + + @Test + public void 아이디와_사용자정보로_사용자의_학점을_조회한다_기존이력_있을_때() { + GpaScore gpaScore = new GpaScore( + new Gpa(4.5, 4.5, "http://example.com/gpa-report.pdf"), + siteUser, + LocalDate.of(2024, 10, 10) + ); + gpaScore.setSiteUser(siteUser); + gpaScoreRepository.save(gpaScore); + + Optional gpaScoreBySiteUser = gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScore.getId()); + assertThat(gpaScoreBySiteUser).isEqualTo(Optional.of(gpaScore)); + } +} diff --git a/src/test/java/com/example/solidconnection/unit/repository/LanguageTestScoreRepositoryTest.java b/src/test/java/com/example/solidconnection/unit/repository/LanguageTestScoreRepositoryTest.java new file mode 100644 index 000000000..7369f20fa --- /dev/null +++ b/src/test/java/com/example/solidconnection/unit/repository/LanguageTestScoreRepositoryTest.java @@ -0,0 +1,98 @@ +package com.example.solidconnection.unit.repository; + +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +@DataJpaTest +@ActiveProfiles("test") +@DisplayName("어학성적 레포지토리 테스트") +@Transactional +public class LanguageTestScoreRepositoryTest { + @Autowired + private SiteUserRepository siteUserRepository; + @Autowired + private LanguageTestScoreRepository languageTestScoreRepository; + + private SiteUser siteUser; + + @BeforeEach + public void setUp() { + siteUser = createSiteUser(); + siteUserRepository.save(siteUser); + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE + ); + } + + @Test + public void 사용자의_어학성적을_조회한다_기존이력_없을_때() { + Optional languageTestScore = languageTestScoreRepository + .findLanguageTestScoreBySiteUserAndLanguageTest_LanguageTestType(siteUser, LanguageTestType.TOEIC); + assertThat(languageTestScore).isEqualTo(Optional.empty()); + } + + @Test + public void 사용자의_어학성적을_조회한다_기존이력_있을_때() { + LanguageTestScore languageTestScore = new LanguageTestScore( + new LanguageTest(LanguageTestType.TOEIC, "990", "http://example.com/gpa-report.pdf"), + LocalDate.of(2024, 10, 10), + siteUser + ); + languageTestScore.setSiteUser(siteUser); + languageTestScoreRepository.save(languageTestScore); + + Optional languageTestScore1 = languageTestScoreRepository + .findLanguageTestScoreBySiteUserAndLanguageTest_LanguageTestType(siteUser, LanguageTestType.TOEIC); + assertThat(languageTestScore1).isEqualTo(Optional.of(languageTestScore)); + } + + @Test + public void 아이디와_사용자정보로_사용자의_어학성적을_조회한다_기존이력_없을_때() { + Optional languageTestScore = languageTestScoreRepository + .findLanguageTestScoreBySiteUserAndId(siteUser, 1L); + assertThat(languageTestScore).isEqualTo(Optional.empty()); + } + + @Test + public void 아이디와_사용자정보로_사용자의_어학성적을_조회한다_기존이력_있을_때() { + LanguageTestScore languageTestScore = new LanguageTestScore( + new LanguageTest(LanguageTestType.TOEIC, "990", "http://example.com/gpa-report.pdf"), + LocalDate.of(2024, 10, 10), + siteUser + ); + languageTestScore.setSiteUser(siteUser); + languageTestScoreRepository.save(languageTestScore); + + Optional languageTestScore1 = languageTestScoreRepository + .findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScore.getId()); + assertThat(languageTestScore1).isEqualTo(Optional.of(languageTestScore)); + } +} diff --git a/src/test/java/com/example/solidconnection/unit/service/ApplicationServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/ApplicationServiceTest.java index 7688478fc..dd87a383f 100644 --- a/src/test/java/com/example/solidconnection/unit/service/ApplicationServiceTest.java +++ b/src/test/java/com/example/solidconnection/unit/service/ApplicationServiceTest.java @@ -3,11 +3,16 @@ import com.example.solidconnection.application.domain.Application; import com.example.solidconnection.application.domain.Gpa; import com.example.solidconnection.application.domain.LanguageTest; -import com.example.solidconnection.application.dto.ScoreRequest; +import com.example.solidconnection.application.dto.ApplyRequest; import com.example.solidconnection.application.dto.UniversityChoiceRequest; import com.example.solidconnection.application.repository.ApplicationRepository; import com.example.solidconnection.application.service.ApplicationSubmissionService; import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; import com.example.solidconnection.type.*; @@ -16,19 +21,15 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.test.util.ReflectionTestUtils; +import org.springframework.beans.factory.annotation.Value; +import java.time.LocalDate; import java.util.Optional; -import static com.example.solidconnection.custom.exception.ErrorCode.CANT_APPLY_FOR_SAME_UNIVERSITY; -import static com.example.solidconnection.custom.exception.ErrorCode.SCORE_SHOULD_SUBMITTED_FIRST; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.*; @@ -41,27 +42,28 @@ public class ApplicationServiceTest { @Mock ApplicationRepository applicationRepository; @Mock + UniversityInfoForApplyRepository universityInfoForApplyRepository; + @Mock SiteUserRepository siteUserRepository; @Mock - UniversityInfoForApplyRepository universityInfoForApplyRepository; + GpaScoreRepository gpaScoreRepository; + @Mock + LanguageTestScoreRepository languageTestScoreRepository; + @Value("${university.term}") + private String term; private SiteUser siteUser; - private Application application; - private Application applicationBeforeTerm; - - private String term = "2024-1"; - private String beforeTerm = "1999-1"; + private GpaScore gpaScore; + private LanguageTestScore languageTestScore; + private final long gpaScoreId = 1L; + private final long languageTestScoreId = 1L; + private final long firstChoiceUniversityId = 1L; + private final long secondChoiceUniversityId = 2L; + private final long thirdChoiceUniversityId = 3L; @BeforeEach void setUp() { - ReflectionTestUtils.setField(applicationSubmissionService, "term", term); // 테스트시 @value값 주입위함 - siteUser = createSiteUser(); - application = createApplication(term); - applicationBeforeTerm = createApplication(beforeTerm); - } - - private SiteUser createSiteUser() { - return new SiteUser( + siteUser = new SiteUser( "test@example.com", "nickname", "profileImageUrl", @@ -70,154 +72,188 @@ private SiteUser createSiteUser() { Role.MENTEE, Gender.MALE ); - } - - private Application createApplication(String term) { - return new Application( + gpaScore = new GpaScore( + new Gpa(4.3, 4.5, "gpaScoreUrl"), siteUser, - new Gpa(4.0, 4.5, "url"), - new LanguageTest(LanguageTestType.TOEIC, "900", "url"), - term + LocalDate.of(2024, 10, 30) + ); + languageTestScore = new LanguageTestScore( + new LanguageTest(LanguageTestType.TOEIC, "990", "languageTestScoreUrl"), + LocalDate.of(2024, 10, 30), + siteUser ); } @Test - void 성적을_제출한다_금학기_제출이력_없음() { + void 지원한다_기존_이력_없음() { // Given - ScoreRequest scoreRequest = new ScoreRequest( - LanguageTestType.TOEIC, "990", "url", 4.5, 4.5, "url" + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) ); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + gpaScore.setVerifyStatus(VerifyStatus.APPROVED); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)).thenReturn(Optional.of(gpaScore)); + languageTestScore.setVerifyStatus(VerifyStatus.APPROVED); + when(languageTestScoreRepository.findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId)).thenReturn(Optional.of(languageTestScore)); when(applicationRepository.findBySiteUserAndTerm(siteUser, term)).thenReturn(Optional.empty()); // When - applicationSubmissionService.submitScore(siteUser.getEmail(), scoreRequest); + boolean result = applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); // Then + assertThat(result).isEqualTo(true); verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); - verify(applicationRepository, times(1)).findBySiteUserAndTerm(siteUser, term); + verify(gpaScoreRepository, times(1)).findGpaScoreBySiteUserAndId(siteUser, gpaScoreId); + verify(languageTestScoreRepository, times(1)).findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId); verify(applicationRepository, times(1)).save(any(Application.class)); } @Test - void 성적을_제출한다_금학기_제출이력_있음() { + void 지원한다_기존_이력_있음() { // Given - ScoreRequest scoreRequest = new ScoreRequest( - LanguageTestType.TOEIC, "990", "url", 4.5, 4.5, "url" + Application beforeApplication = new Application( + siteUser, + new Gpa(4.5, 4.5, "beforeGpaScoreUrl"), + new LanguageTest(LanguageTestType.TOEIC, "900", "beforeLanguageTestUrl"), + term + ); + beforeApplication.setVerifyStatus(VerifyStatus.APPROVED); + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) ); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); - when(applicationRepository.findBySiteUserAndTerm(siteUser, term)).thenReturn(Optional.of(application)); + gpaScore.setVerifyStatus(VerifyStatus.APPROVED); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, 1L)).thenReturn(Optional.of(gpaScore)); + languageTestScore.setVerifyStatus(VerifyStatus.APPROVED); + when(languageTestScoreRepository.findLanguageTestScoreBySiteUserAndId(siteUser, 1L)).thenReturn(Optional.of(languageTestScore)); + when(applicationRepository.findBySiteUserAndTerm(siteUser, term)).thenReturn(Optional.of(beforeApplication)); // When - applicationSubmissionService.submitScore(siteUser.getEmail(), scoreRequest); + boolean result = applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); // Then - assertEquals(application.getGpa().getGpa(), scoreRequest.gpa()); - assertEquals(application.getLanguageTest().getLanguageTestScore(), scoreRequest.languageTestScore()); + assertThat(result).isEqualTo(true); verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + verify(gpaScoreRepository, times(1)).findGpaScoreBySiteUserAndId(siteUser, gpaScoreId); + verify(languageTestScoreRepository, times(1)).findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId); verify(applicationRepository, times(1)).findBySiteUserAndTerm(siteUser, term); - verify(applicationRepository, times(0)).save(any(Application.class)); + verify(universityInfoForApplyRepository, times(1)).getUniversityInfoForApplyByIdAndTerm(firstChoiceUniversityId, term); + verify(universityInfoForApplyRepository, times(1)).getUniversityInfoForApplyByIdAndTerm(secondChoiceUniversityId, term); + verify(universityInfoForApplyRepository, times(1)).getUniversityInfoForApplyByIdAndTerm(thirdChoiceUniversityId, term); + verify(applicationRepository, times(1)).save(any(Application.class)); } - /** - * 지망대학 제출 - */ @Test - void 지망대학_제출할_때_성적_제출이력이_없다면_예외_응답을_반환한다() { + void 지원할_때_존재하지_않는_학점이라면_예외_응답을_반환한다() { // given - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( - 1L, 2L, 3L + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) ); - when(applicationRepository.findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail())) - .thenReturn(Optional.empty()); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)).thenReturn(Optional.empty()); // when, then CustomException exception = assertThrows(CustomException.class, () -> { - applicationSubmissionService.submitUniversityChoice(siteUser.getEmail(), universityChoiceRequest); + applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); }); assertThat(exception.getMessage()) - .isEqualTo(SCORE_SHOULD_SUBMITTED_FIRST.getMessage()); + .isEqualTo(ErrorCode.INVALID_GPA_SCORE.getMessage()); assertThat(exception.getCode()) - .isEqualTo(SCORE_SHOULD_SUBMITTED_FIRST.getCode()); + .isEqualTo(ErrorCode.INVALID_GPA_SCORE.getCode()); } @Test - void 지망대학_제출한다_이전학기_성적_제출이력_있음() { - // Given - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( - 1L, 2L, 3L + void 지원할_때_승인되지_않은_학점이라면_예외_응답을_반환한다() { + // given + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) ); - when(applicationRepository.findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail())) - .thenReturn(Optional.of(applicationBeforeTerm)); when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + gpaScore.setVerifyStatus(VerifyStatus.REJECTED); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)).thenReturn(Optional.of(gpaScore)); - // When - applicationSubmissionService.submitUniversityChoice(siteUser.getEmail(), universityChoiceRequest); - - // Then - verify(applicationRepository, times(1)).findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail()); - verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); - verify(applicationRepository, times(1)).save(any(Application.class)); + // when, then + CustomException exception = assertThrows(CustomException.class, () -> { + applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); + }); + assertThat(exception.getMessage()) + .isEqualTo(ErrorCode.INVALID_GPA_SCORE_STATUS.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(ErrorCode.INVALID_GPA_SCORE_STATUS.getCode()); } @Test - void 지망대학_제출한다_금학기_성적_제출이력_있음() { - // Given - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest( - 1L, 2L, 3L + void 지원할_때_존재하지_않는_어학성적이라면_예외_응답을_반환한다() { + // given + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) ); - when(applicationRepository.findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail())) - .thenReturn(Optional.of(application)); - - // When - applicationSubmissionService.submitUniversityChoice(siteUser.getEmail(), universityChoiceRequest); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + gpaScore.setVerifyStatus(VerifyStatus.APPROVED); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)).thenReturn(Optional.of(gpaScore)); + when(languageTestScoreRepository.findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId)).thenReturn(Optional.empty()); - // Then - verify(applicationRepository, times(1)).findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail()); - verify(siteUserRepository, times(0)).getByEmail(siteUser.getEmail()); - verify(applicationRepository, times(0)).save(any(Application.class)); + // when, then + CustomException exception = assertThrows(CustomException.class, () -> { + applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); + }); + assertThat(exception.getMessage()) + .isEqualTo(ErrorCode.INVALID_LANGUAGE_TEST_SCORE.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(ErrorCode.INVALID_LANGUAGE_TEST_SCORE.getCode()); } - @ParameterizedTest - @CsvSource({ - "1, 2, 3", - "1, , 3", - "1, 2, ", - "1, , " - }) - void 지망대학_제출할_때_2지망과_3지망은_NULL_허용한다(Long firstChoice, Long secondChoice, Long thirdChoice) { - // Given - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest(firstChoice, secondChoice, thirdChoice); - when(applicationRepository.findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail())) - .thenReturn(Optional.of(application)); - - // When - applicationSubmissionService.submitUniversityChoice(siteUser.getEmail(), universityChoiceRequest); + @Test + void 지원할_때_승인되지_않은_어학성적이라면_예외_응답을_반환한다() { + // given + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, secondChoiceUniversityId, thirdChoiceUniversityId) + ); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + gpaScore.setVerifyStatus(VerifyStatus.APPROVED); + when(gpaScoreRepository.findGpaScoreBySiteUserAndId(siteUser, gpaScoreId)).thenReturn(Optional.of(gpaScore)); + languageTestScore.setVerifyStatus(VerifyStatus.REJECTED); + when(languageTestScoreRepository.findLanguageTestScoreBySiteUserAndId(siteUser, languageTestScoreId)).thenReturn(Optional.of(languageTestScore)); - // Then - verify(applicationRepository, times(1)).findTop1BySiteUser_EmailOrderByTermDesc(siteUser.getEmail()); - verify(siteUserRepository, times(0)).getByEmail(siteUser.getEmail()); - verify(applicationRepository, times(0)).save(any(Application.class)); + // when, then + CustomException exception = assertThrows(CustomException.class, () -> { + applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); + }); + assertThat(exception.getMessage()) + .isEqualTo(ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS.getMessage()); + assertThat(exception.getCode()) + .isEqualTo(ErrorCode.INVALID_LANGUAGE_TEST_SCORE_STATUS.getCode()); } - @ParameterizedTest - @CsvSource({ - "1, 1, 1", - "1, 2, 1", - "1, 1, 2", - "1, , 1", - "1, 1, " - }) - void 지망대학_제출할_때_선택지가_중복된다면_예외_응답을_반환한다(Long firstChoice, Long secondChoice, Long thirdChoice) { + @Test + void 지원할_때_학교_선택이_중복되면_예외_응답을_반환한다() { // given - UniversityChoiceRequest universityChoiceRequest = new UniversityChoiceRequest(firstChoice, secondChoice, thirdChoice); + ApplyRequest applyRequest = new ApplyRequest( + gpaScoreId, + languageTestScoreId, + new UniversityChoiceRequest(firstChoiceUniversityId, firstChoiceUniversityId, firstChoiceUniversityId) + ); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); // when, then CustomException exception = assertThrows(CustomException.class, () -> { - applicationSubmissionService.submitUniversityChoice(siteUser.getEmail(), universityChoiceRequest); + applicationSubmissionService.apply(siteUser.getEmail(), applyRequest); }); assertThat(exception.getMessage()) - .isEqualTo(CANT_APPLY_FOR_SAME_UNIVERSITY.getMessage()); + .isEqualTo(ErrorCode.CANT_APPLY_FOR_SAME_UNIVERSITY.getMessage()); assertThat(exception.getCode()) - .isEqualTo(CANT_APPLY_FOR_SAME_UNIVERSITY.getCode()); + .isEqualTo(ErrorCode.CANT_APPLY_FOR_SAME_UNIVERSITY.getCode()); } } diff --git a/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java index 917d073f5..57c5916a9 100644 --- a/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java +++ b/src/test/java/com/example/solidconnection/unit/service/PostServiceTest.java @@ -7,7 +7,7 @@ import com.example.solidconnection.comment.service.CommentService; import com.example.solidconnection.custom.exception.CustomException; import com.example.solidconnection.custom.exception.ErrorCode; -import com.example.solidconnection.dto.PostFindPostImageResponse; +import com.example.solidconnection.post.dto.PostFindPostImageResponse; import com.example.solidconnection.entity.PostImage; import com.example.solidconnection.post.domain.PostLike; import com.example.solidconnection.post.repository.PostLikeRepository; diff --git a/src/test/java/com/example/solidconnection/unit/service/ScoreServiceTest.java b/src/test/java/com/example/solidconnection/unit/service/ScoreServiceTest.java new file mode 100644 index 000000000..39deadb54 --- /dev/null +++ b/src/test/java/com/example/solidconnection/unit/service/ScoreServiceTest.java @@ -0,0 +1,201 @@ +package com.example.solidconnection.unit.service; + +import com.example.solidconnection.application.domain.Gpa; +import com.example.solidconnection.application.domain.LanguageTest; +import com.example.solidconnection.score.domain.GpaScore; +import com.example.solidconnection.score.domain.LanguageTestScore; +import com.example.solidconnection.score.dto.*; +import com.example.solidconnection.score.repository.GpaScoreRepository; +import com.example.solidconnection.score.repository.LanguageTestScoreRepository; +import com.example.solidconnection.score.service.ScoreService; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.LanguageTestType; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDate; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +@DisplayName("점수 서비스 테스트") +public class ScoreServiceTest { + @InjectMocks + ScoreService scoreService; + @Mock + GpaScoreRepository gpaScoreRepository; + @Mock + LanguageTestScoreRepository languageTestScoreRepository; + @Mock + SiteUserRepository siteUserRepository; + + private SiteUser siteUser; + private GpaScore beforeGpaScore; + private GpaScore beforeGpaScore2; + private LanguageTestScore beforeLanguageTestScore; + private LanguageTestScore beforeLanguageTestScore2; + + @BeforeEach + void setUp() { + siteUser = createSiteUser(); + beforeGpaScore = createBeforeGpaScore(siteUser, 4.5); + beforeGpaScore2 = createBeforeGpaScore(siteUser, 4.3); + beforeLanguageTestScore = createBeforeLanguageTestScore(siteUser); + beforeLanguageTestScore2 = createBeforeLanguageTestScore2(siteUser); + } + + private SiteUser createSiteUser() { + return new SiteUser( + "test@example.com", + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE + ); + } + + private GpaScore createBeforeGpaScore(SiteUser siteUser, Double gpa) { + return new GpaScore( + new Gpa(gpa, 4.5, "http://example.com/gpa-report.pdf"), + siteUser, + LocalDate.of(2024, 10, 20) + ); + } + + private LanguageTestScore createBeforeLanguageTestScore(SiteUser siteUser) { + return new LanguageTestScore( + new LanguageTest(LanguageTestType.TOEIC, "900", "http://example.com/gpa-report.pdf"), + LocalDate.of(2024, 10, 30), + siteUser + ); + } + + private LanguageTestScore createBeforeLanguageTestScore2(SiteUser siteUser) { + return new LanguageTestScore( + new LanguageTest(LanguageTestType.TOEFL_IBT, "100", "http://example.com/gpa-report.pdf"), + LocalDate.of(2024, 10, 30), + siteUser + ); + } + + @Test + void 학점을_등록한다_기존이력이_없을_때() { + // Given + GpaScoreRequest gpaScoreRequest = new GpaScoreRequest( + 4.5, 4.5, LocalDate.of(2024, 10, 20), "http://example.com/gpa-report.pdf" + ); + GpaScore newGpaScore = new GpaScore(gpaScoreRequest.toGpa(), siteUser, gpaScoreRequest.issueDate()); + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(gpaScoreRepository.save(newGpaScore)).thenReturn(newGpaScore); + + // 새로운 gpa 저장하게된다. + scoreService.submitGpaScore(siteUser.getEmail(), gpaScoreRequest); + + // Then + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + verify(gpaScoreRepository, times(1)).save(any(GpaScore.class)); + } + + @Test + void 어학성적을_등록한다_기존이력이_없을_때() { + // Given + LanguageTestScoreRequest languageTestScoreRequest = new LanguageTestScoreRequest( + LanguageTestType.TOEIC, "900", + LocalDate.of(2024, 10, 30), "http://example.com/gpa-report.pdf" + ); + LanguageTest languageTest = languageTestScoreRequest.toLanguageTest(); + LanguageTestScore languageTestScore = new LanguageTestScore(languageTest, LocalDate.of(2024, 10, 30), siteUser); + + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + when(languageTestScoreRepository.save(any(LanguageTestScore.class))).thenReturn(languageTestScore); + + //when + scoreService.submitLanguageTestScore(siteUser.getEmail(), languageTestScoreRequest); + + // Then + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + verify(languageTestScoreRepository, times(1)).save(any(LanguageTestScore.class)); + } + + @Test + void 학점이력을_조회한다_제출이력이_있을_때() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + beforeGpaScore.setSiteUser(siteUser); + beforeGpaScore2.setSiteUser(siteUser); + + // when + GpaScoreStatusResponse gpaScoreStatusResponse = scoreService.getGpaScoreStatus(siteUser.getEmail()); + + // Then + List expectedStatusList = List.of( + GpaScoreStatus.from(beforeGpaScore), + GpaScoreStatus.from(beforeGpaScore2) + ); + assertThat(gpaScoreStatusResponse.gpaScoreStatusList()) + .hasSize(2) + .containsExactlyElementsOf(expectedStatusList); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + } + + @Test + void 학점이력을_조회한다_제출이력이_없을_때() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + + // when + GpaScoreStatusResponse gpaScoreStatus = scoreService.getGpaScoreStatus(siteUser.getEmail()); + + // Then + assertThat(gpaScoreStatus.gpaScoreStatusList()).isEmpty(); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + } + + + @Test + void 어학이력을_조회한다_제출이력이_있을_때() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + beforeLanguageTestScore.setSiteUser(siteUser); + beforeLanguageTestScore2.setSiteUser(siteUser); + + // when + LanguageTestScoreStatusResponse languageTestScoreStatus = scoreService.getLanguageTestScoreStatus(siteUser.getEmail()); + + // Then + List expectedStatusList = List.of( + LanguageTestScoreStatus.from(beforeLanguageTestScore), + LanguageTestScoreStatus.from(beforeLanguageTestScore2) + ); + assertThat(languageTestScoreStatus.languageTestScoreStatusList()) + .hasSize(2) + .containsExactlyElementsOf(expectedStatusList); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + } + + @Test + void 어학이력을_조회한다_제출이력이_없을_때() { + // Given + when(siteUserRepository.getByEmail(siteUser.getEmail())).thenReturn(siteUser); + + // when + LanguageTestScoreStatusResponse languageTestScoreStatus = scoreService.getLanguageTestScoreStatus(siteUser.getEmail()); + + // Then + assertThat(languageTestScoreStatus.languageTestScoreStatusList()).isEmpty(); + verify(siteUserRepository, times(1)).getByEmail(siteUser.getEmail()); + } +}