diff --git a/.github/workflows/cd-dev.yml b/.github/workflows/cd-dev.yml index 308c2112..44d549ad 100644 --- a/.github/workflows/cd-dev.yml +++ b/.github/workflows/cd-dev.yml @@ -26,9 +26,10 @@ jobs: - name: Create application.yml run: | pwd - touch src/main/resources/application-dev.yml - echo "${{ secrets.APPLICATION_DEV }}" >> src/main/resources/application-dev.yml - cat src/main/resources/application-dev.yml + cd ./operation-api/src/main/resources + touch ./application-dev.yml + echo "${{ secrets.APPLICATION_DEV }}" >> ./application-dev.yml + cat ./application-dev.yml - name: Build with Gradle run: ./gradlew build diff --git a/.github/workflows/cd-prod.yml b/.github/workflows/cd-prod.yml index fcf8f2db..19038e1c 100644 --- a/.github/workflows/cd-prod.yml +++ b/.github/workflows/cd-prod.yml @@ -26,9 +26,10 @@ jobs: - name: Create application.yml run: | pwd - touch src/main/resources/application-prod.yml - echo "${{ secrets.APPLICATION_PROD }}" >> src/main/resources/application-prod.yml - cat src/main/resources/application-prod.yml + cd ./operation-api/src/main/resources + touch ./application-prod.yml + echo "${{ secrets.APPLICATION_PROD }}" >> ./application-prod.yml + cat ./application-prod.yml - name: Build with Gradle run: ./gradlew build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3120e8b6..32b14339 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,9 +26,10 @@ jobs: - name: Create application.yml run: | pwd - touch src/main/resources/application-dev.yml - echo "${{ secrets.APPLICATION_DEV }}" >> src/main/resources/application-dev.yml - cat src/main/resources/application-dev.yml + cd ./operation-api/src/main/resources + touch ./application-dev.yml + echo "${{ secrets.APPLICATION_DEV }}" >> ./application-dev.yml + cat ./application-dev.yml - name: Build with Gradle run: ./gradlew build diff --git a/.gitignore b/.gitignore index 601e1684..5df0f4ca 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ out/ .vscode/ ### config yml ### -application-**.yml \ No newline at end of file +application-**.yml + +*/src/main/generated \ No newline at end of file diff --git a/README.md b/README.md index ebe176e8..f5199570 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,59 @@ -# sopt-operation-backend -**메이커스 운영팀 서버** : 출석 관리 어드민 서비스, 회원 출석 체크 서비스, 알림 전송 서비스 +# SOPT 메이커스 운영 프로덕트 서버 +> SOPT 활동 기수 회원과 임원진의 편리한 운영을 위한 서비스를 만들어요. + +### 웹 어드민 (임원진 대상) +- 세미나, 행사 등 **세션 생성** +- 활동 기수 회원의 **출석 내역 관리** +- 푸시 알림을 전송할 **공지 및 소식 작성, 푸시알림 전송** + +### 출석 앱 (활동 기수 회원 대상) +- 참여한 세션 **출석 체크**, **자신의 출석 내역 조회** + +
+ +## Server Acrchitecture + +image + +
+ +## Used Stacks +image
-## 🛠 Used Stacks +## 프로젝트 폴더 구조 +### 멀티모듈 구조 +``` +📁 operation-api # Controller, Service +📁 operation-auth # Authentication 관련 기능 +📁 operation-common # 공통 기능 +📁 operation-domain # Entity +📁 operation-external # 외부 API 기능(SOPT 메이커스 내 플레이그라운드, 알림TF) +``` -- Java 17 -- Gradle -- Spring Boot 2.7.4 -- Spring Data JPA -- PostgreSQL +### 모듈 내 구조 +``` +📁 src +|_ 📁 main +|_ |_ 📁 app # 앱 기능 +|_ |_ 📁 common # 공통 기능 +|_ |_ 📁 web # 웹 기능 +```
-## 👥 팀원 +## Member | [이용택](https://github.com/dragontaek-lee)| [김소현](https://github.com/thguss)| |:-----:|:------:| | | | -|- 프로젝트 초기 세팅
- HTTPS 설정
- 회원 출석 체크 서비스|- 프로젝트 초기 세팅
- CICD 환경 구축
- 출석 관리 어드민 서비스| +|- 프로젝트 초기 세팅
- HTTPS 설정
- (App)회원 출석 체크 기능
- 알림TF|- 프로젝트 초기 세팅
- CICD 환경 구축
- (Web)세션 출석 관리 기능
- 알림 관리 기능|
-## 📏 Process +## Process 1. 개발 전에 `github issue`를 생성해주세요! 1. 템플릿에 맞게 내용을 작성한다 2. Assignees, Label을 단다 @@ -38,12 +68,10 @@ 7. `approve`가 완료되었다면 `merge`를 진행해주세!요 > `코드 외적인 부분`(환경변수, db 필드 및 테이블 수정, 인프라 세팅 등) 수정사항이 있다면 팀원에게 먼저 물어보고 진행하거나, 그러지 못하였더라면 빠르게 전달해주세요!(카톡, 슬랙, 디코 등) -> -
-## 🌴 Commit Convention +## Commit Convention | 태그 이름 | 설명 | | --- | --- | | [CHORE] | 코드 수정, 내부 파일 수정 | @@ -61,7 +89,7 @@
-## ✨ Branch Strategy +## Branch Strategy - `main`, `develop`, `feature` 브랜치가 있습니다! - **main**은 production용 브랜치입니다 - 실서비스용 ec2(**makers.operation.prod)**로 배포되도록 파이프라인이 구축되어 있습니다 @@ -72,61 +100,3 @@ - 각자 이슈에 대한 작업물의 브랜치입니다 - develop에 PR을 거쳐 merge 해주세요 -
- -## 🗂 프로젝트 폴더 구조 - -``` -📁 src -|_ 📁 main -|_ |_ 📁 common -|_ |_ 📁 config -|_ |_ 📁 controller -|_ |_ 📁 dto -|_ |_ 📁 entity -|_ |_ 📁 exception -|_ |_ 📁 repository -|_ |_ 📁 security -|_ |_ 📁 service -|_ |_ 📁 util - -``` - -
- -## 🌴 Dependencies Module -build.gradle -``` -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" - implementation "com.querydsl:querydsl-apt:${queryDslVersion}" - - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.h2database:h2' - runtimeOnly 'org.postgresql:postgresql' - annotationProcessor 'org.projectlombok:lombok' - - // jwt - implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' - - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - // swagger - implementation 'io.springfox:springfox-boot-starter:3.0.0' - implementation 'io.springfox:springfox-swagger-ui:3.0.0' -} - -``` - -
- - -## 🏗 Architecture -![image](https://user-images.githubusercontent.com/55437339/236621230-8d2dd581-c68d-44e9-bc0d-ea35dee08ebe.png) - diff --git a/build.gradle b/build.gradle index 74b34945..a8f27535 100644 --- a/build.gradle +++ b/build.gradle @@ -1,74 +1,55 @@ buildscript { - ext { - queryDslVersion = "5.0.0" + repositories { + mavenCentral() } } plugins { id 'java' - id 'org.springframework.boot' version '2.7.4' + id 'org.springframework.boot' version '3.0.0' id 'io.spring.dependency-management' version '1.1.0' - id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' } -group = 'org.sopt.makers' -version = '0.0.1-SNAPSHOT' -sourceCompatibility = '17' +allprojects { + apply plugin: 'java' + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' -configurations { - compileOnly { - extendsFrom annotationProcessor - } -} - -repositories { - mavenCentral() -} + group = 'org.sopt.makers.operation' + version = '0.0.1-SNAPSHOT' + sourceCompatibility = '17' -dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-security' - implementation "com.querydsl:querydsl-jpa:${queryDslVersion}" - implementation "com.querydsl:querydsl-apt:${queryDslVersion}" + repositories { + mavenCentral() + } - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.h2database:h2' - runtimeOnly 'org.postgresql:postgresql' - annotationProcessor 'org.projectlombok:lombok' + configurations { + compileOnly { + extendsFrom annotationProcessor + } + } - // jwt - implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' - runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + dependencies { + // lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + // test + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + } - // swagger - implementation 'io.springfox:springfox-boot-starter:3.0.0' - implementation 'io.springfox:springfox-swagger-ui:3.0.0' + tasks.named('test') { + useJUnitPlatform() + } } -tasks.named('test') { - useJUnitPlatform() +jar { + enabled = true } -def querydslDir = "$buildDir/generated/querydsl" - -querydsl { - jpa = true - querydslSourcesDir = querydslDir -} -sourceSets { - main.java.srcDir querydslDir -} -compileQuerydsl{ - options.annotationProcessorPath = configurations.querydsl -} -configurations { - compileOnly { - extendsFrom annotationProcessor - } - querydsl.extendsFrom compileClasspath -} +bootJar { + enabled = false +} \ No newline at end of file diff --git a/operation-api/build.gradle b/operation-api/build.gradle new file mode 100644 index 00000000..2d20d26b --- /dev/null +++ b/operation-api/build.gradle @@ -0,0 +1,24 @@ +jar { + enabled = false +} + +bootJar { + enabled = true +} + +dependencies { + // module + implementation project(path: ':operation-auth') + implementation project(path: ':operation-common') + implementation project(path: ':operation-domain') + implementation project(path: ':operation-external') + + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-validation' + + // swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + +} \ No newline at end of file diff --git a/operation-api/src/main/java/org/sopt/makers/operation/OperationApplication.java b/operation-api/src/main/java/org/sopt/makers/operation/OperationApplication.java new file mode 100644 index 00000000..ee82620f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/OperationApplication.java @@ -0,0 +1,14 @@ +package org.sopt.makers.operation; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication( + scanBasePackageClasses = {AuthRoot.class, CommonRoot.class, DomainRoot.class, ExternalRoot.class} +) +public class OperationApplication { + + public static void main(String[] args) { + SpringApplication.run(OperationApplication.class, args); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApi.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApi.java new file mode 100644 index 00000000..cf6c86cb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApi.java @@ -0,0 +1,41 @@ +package org.sopt.makers.operation.app.attendance.api; + +import java.security.Principal; + +import org.sopt.makers.operation.app.attendance.dto.request.LectureAttendRequest; +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.ResponseEntity; + +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Operation; +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.tags.Tag; +import lombok.NonNull; + +@Tag(name = "앱 출석 관련 API") +public interface AppAttendanceApi { + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 출석 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> attend( + @RequestBody LectureAttendRequest request, + @Parameter(hidden = true) @NonNull Principal principal); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApiController.java new file mode 100644 index 00000000..0415f03b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/api/AppAttendanceApiController.java @@ -0,0 +1,36 @@ +package org.sopt.makers.operation.app.attendance.api; + +import static org.sopt.makers.operation.code.success.app.AttendanceSuccessCode.*; + +import java.security.Principal; + +import org.sopt.makers.operation.app.attendance.dto.request.LectureAttendRequest; +import org.sopt.makers.operation.app.attendance.service.AppAttendanceService; +import org.sopt.makers.operation.common.util.CommonUtils; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.ResponseEntity; +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 lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/app/attendances") +public class AppAttendanceApiController implements AppAttendanceApi { + + private final AppAttendanceService attendanceService; + private final CommonUtils utils; + + @Override + @PostMapping("/attend") + public ResponseEntity> attend(@RequestBody LectureAttendRequest request, Principal principal) { + val memberId = utils.getMemberId(principal); + val response = attendanceService.attend(memberId, request); + return ApiResponseUtil.success(SUCCESS_ATTEND, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/request/LectureAttendRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/request/LectureAttendRequest.java new file mode 100644 index 00000000..794dd562 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/request/LectureAttendRequest.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.app.attendance.dto.request; + +import lombok.NonNull; + +public record LectureAttendRequest( + long subLectureId, + @NonNull String code +) { +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/response/LectureAttendResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/response/LectureAttendResponse.java new file mode 100644 index 00000000..442b05b3 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/dto/response/LectureAttendResponse.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.app.attendance.dto.response; + +import static lombok.AccessLevel.*; + +import lombok.Builder; + +import org.sopt.makers.operation.attendance.domain.SubAttendance; + +@Builder(access = PRIVATE) +public record LectureAttendResponse( + long subLectureId +) { + public static LectureAttendResponse of(SubAttendance subAttendance) { + return LectureAttendResponse.builder() + .subLectureId(subAttendance.getSubLecture().getId()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceService.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceService.java new file mode 100644 index 00000000..3d0c5668 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceService.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.app.attendance.service; + +import org.sopt.makers.operation.app.attendance.dto.request.LectureAttendRequest; +import org.sopt.makers.operation.app.attendance.dto.response.LectureAttendResponse; + +public interface AppAttendanceService { + LectureAttendResponse attend(long memberId, LectureAttendRequest request); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceServiceImpl.java new file mode 100644 index 00000000..af4f4b59 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/attendance/service/AppAttendanceServiceImpl.java @@ -0,0 +1,115 @@ +package org.sopt.makers.operation.app.attendance.service; + +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; +import static org.sopt.makers.operation.code.failure.member.memberFailureCode.*; +import static org.sopt.makers.operation.code.failure.subAttendance.subAttendanceFailureCode.*; +import static org.sopt.makers.operation.code.failure.lecture.LectureFailureCode.*; +import static org.sopt.makers.operation.code.failure.subLecture.subLectureFailureCode.*; + +import org.sopt.makers.operation.app.attendance.dto.request.LectureAttendRequest; +import org.sopt.makers.operation.app.attendance.dto.response.LectureAttendResponse; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.attendance.repository.attendance.AttendanceRepository; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.SubLectureException; +import org.sopt.makers.operation.exception.LectureException; +import org.sopt.makers.operation.exception.MemberException; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.SubLecture; +import org.sopt.makers.operation.lecture.repository.subLecture.SubLectureRepository; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AppAttendanceServiceImpl implements AppAttendanceService { + + private final AttendanceRepository attendanceRepository; + private final MemberRepository memberRepository; + private final SubLectureRepository subLectureRepository; + private final ValueConfig valueConfig; + + @Override + @Transactional + public LectureAttendResponse attend(long playgroundId, LectureAttendRequest request) { + val subAttendance = getSubAttendance(request.subLectureId(), request.code(), playgroundId); + subAttendance.updateStatus(ATTENDANCE); + return LectureAttendResponse.of(subAttendance); + } + + private SubAttendance getSubAttendance(long subLectureId, String code, long playgroundId) { + val subLecture = getSubLecture(subLectureId, code); + val attendance = getAttendance(subLecture, playgroundId); + return getSubAttendance(attendance, subLecture.getRound()); + } + + private SubLecture getSubLecture(long subLectureId, String code) { + val subLecture = findSubLecture(subLectureId); + checkSubLectureValidity(subLecture); + checkMatchedCode(subLecture, code); + return subLecture; + } + + private SubLecture findSubLecture(long subLectureId) { + return subLectureRepository + .findById(subLectureId) + .orElseThrow(() -> new SubLectureException(INVALID_ATTENDANCE)); + } + + private void checkSubLectureValidity(SubLecture subLecture) { + checkSubLectureStarted(subLecture); + checkSubLectureEnded(subLecture); + } + + private void checkSubLectureStarted(SubLecture subLecture) { + if (subLecture.isNotStarted()) { + throw new LectureException(NOT_STARTED_NTH_ATTENDANCE); + } + } + + private void checkSubLectureEnded(SubLecture subLecture) { + val attendanceMinute = valueConfig.getATTENDANCE_MINUTE(); + if (subLecture.isEnded(attendanceMinute)) { + throw new LectureException(ENDED_ATTENDANCE, subLecture.getRound()); + } + } + + private void checkMatchedCode(SubLecture subLecture, String code) { + if (!subLecture.isMatchCode(code)) { + throw new SubLectureException(INVALID_CODE); + } + } + + private Attendance getAttendance(SubLecture subLecture, long playgroundId) { + val lecture = subLecture.getLecture(); + val generation = valueConfig.getGENERATION(); + val member = findMember(playgroundId, generation); + return findAttendance(lecture, member); + } + + private Member findMember(long playgroundId, int generation) { + return memberRepository + .getMemberByPlaygroundIdAndGeneration(playgroundId, generation) + .orElseThrow(() -> new MemberException(INVALID_MEMBER)); + } + + private Attendance findAttendance(Lecture lecture, Member member) { + return attendanceRepository.findByLectureAndMember(lecture, member) + .orElseThrow(() -> new LectureException(INVALID_ATTENDANCE)); + } + + private SubAttendance getSubAttendance(Attendance attendance, int round) { + return attendance.getSubAttendances().stream() + .filter(subAttendance -> subAttendance.isMatchRound(round)) + .findFirst() + .orElseThrow(() -> new SubLectureException(INVALID_SUB_ATTENDANCE)); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApi.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApi.java new file mode 100644 index 00000000..095c0a6f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApi.java @@ -0,0 +1,59 @@ +package org.sopt.makers.operation.app.lecture.api; + +import java.security.Principal; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.NonNull; + +@Tag(name = "앱 세션 관련 API") +public interface AppLectureApi { + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 진행 중인 세션 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "진행 중인 세션 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getTodayLecture( + @Parameter(hidden = true) @NonNull Principal principal); + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 출석 차수 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 차수 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getRound(@PathVariable long lectureId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApiController.java new file mode 100644 index 00000000..4db6f636 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/api/AppLectureApiController.java @@ -0,0 +1,38 @@ +package org.sopt.makers.operation.app.lecture.api; + +import static org.sopt.makers.operation.code.success.app.LectureSuccessCode.*; + +import java.security.Principal; + +import org.sopt.makers.operation.app.lecture.service.AppLectureService; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/app/lectures") +public class AppLectureApiController implements AppLectureApi { + + private final AppLectureService lectureService; + + @Override + @GetMapping + public ResponseEntity> getTodayLecture(@NonNull Principal principal) { + val memberId = Long.parseLong(principal.getName()); + val response = lectureService.getTodayLecture(memberId); + return ApiResponseUtil.success(SUCCESS_SINGLE_GET_LECTURE, response); + } + + @Override + @GetMapping("/round/{lectureId}") + public ResponseEntity> getRound(@PathVariable long lectureId) { + val response = lectureService.getCurrentLectureRound(lectureId); + return ApiResponseUtil.success(SUCCESS_GET_LECTURE_ROUND, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureCurrentRoundResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureCurrentRoundResponse.java new file mode 100644 index 00000000..231ea7c9 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureCurrentRoundResponse.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.app.lecture.dto.response; + +import lombok.Builder; +import org.sopt.makers.operation.lecture.domain.SubLecture; + +import static lombok.AccessLevel.PRIVATE; + +@Builder(access = PRIVATE) +public record LectureCurrentRoundResponse( + long id, + int round +) { + public static LectureCurrentRoundResponse of(SubLecture subLecture){ + return LectureCurrentRoundResponse.builder() + .id(subLecture.getId()) + .round(subLecture.getRound()) + .build(); + } +} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseType.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureResponseType.java similarity index 58% rename from src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseType.java rename to operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureResponseType.java index 9c5e7c33..ac9e5c3b 100644 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseType.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/LectureResponseType.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.dto.lecture; +package org.sopt.makers.operation.app.lecture.dto.response; public enum LectureResponseType { NO_SESSION, HAS_ATTENDANCE, NO_ATTENDANCE diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/TodayLectureResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/TodayLectureResponse.java new file mode 100644 index 00000000..c3d2a370 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/dto/response/TodayLectureResponse.java @@ -0,0 +1,100 @@ +package org.sopt.makers.operation.app.lecture.dto.response; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.lecture.domain.Lecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record TodayLectureResponse( + LectureResponseType type, + long id, + String location, + String name, + String startDate, + String endDate, + String message, + List attendances +) { + public static TodayLectureResponse of(LectureResponseType type, Lecture lecture, String message, List attendances) { + return TodayLectureResponse.builder() + .type(type) + .id(lecture.getId()) + .location(lecture.getPlace()) + .name(lecture.getName()) + .startDate(lecture.getStartDate().format(convertFormat())) + .endDate(lecture.getEndDate().format(convertFormat())) + .message(message) + .attendances(attendances.stream() + .map(subAttendance -> LectureGetResponseVO.of(subAttendance.getStatus(), subAttendance.getLastModifiedDate())) + .collect(Collectors.toList())) + .build(); + } + + public static TodayLectureResponse getEmptyResponse() { + return TodayLectureResponse.builder() + .type(LectureResponseType.NO_SESSION) + .id(0L) + .location("") + .name("") + .startDate("") + .endDate("") + .message("") + .attendances(Collections.emptyList()) + .build(); + } + + private static DateTimeFormatter convertFormat() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + } + + public static TodayLectureResponse getOnAttendanceLectureResponse( + SubAttendance subAttendance, + Lecture lecture, + LectureResponseType responseType, + String message + ) { + return lecture.isFirst() + ? TodayLectureResponse.of(responseType, lecture, message, Collections.emptyList()) + : TodayLectureResponse.of(responseType, lecture, message, Collections.singletonList(subAttendance)); + } + + public static TodayLectureResponse getAttendanceLectureResponse( + List subAttendances, + SubAttendance subAttendance, + Lecture lecture, + LectureResponseType responseType, + String message + ) { + return lecture.isFirst() + ? TodayLectureResponse.of(responseType, lecture, message, Collections.singletonList(subAttendance)) + : TodayLectureResponse.of(responseType, lecture, message, subAttendances); + } + + @Builder + record LectureGetResponseVO( + AttendanceStatus status, + String attendedAt + + ) { + public static LectureGetResponseVO of(AttendanceStatus status, LocalDateTime attendedAt) { + return LectureGetResponseVO.builder() + .status(status) + .attendedAt(attendedAt.format((convertFormat()))) + .build(); + } + + private static DateTimeFormatter convertFormat() { + return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureService.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureService.java new file mode 100644 index 00000000..4a7f113e --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureService.java @@ -0,0 +1,10 @@ +package org.sopt.makers.operation.app.lecture.service; + +import org.sopt.makers.operation.app.lecture.dto.response.LectureCurrentRoundResponse; +import org.sopt.makers.operation.app.lecture.dto.response.TodayLectureResponse; + +public interface AppLectureService { + + TodayLectureResponse getTodayLecture(long memberPlaygroundId); + LectureCurrentRoundResponse getCurrentLectureRound(long lectureId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureServiceImpl.java new file mode 100644 index 00000000..d7e8551d --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/lecture/service/AppLectureServiceImpl.java @@ -0,0 +1,185 @@ +package org.sopt.makers.operation.app.lecture.service; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Collections; +import java.util.List; + +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; +import static org.sopt.makers.operation.code.failure.lecture.LectureFailureCode.*; +import static org.sopt.makers.operation.code.failure.subLecture.subLectureFailureCode.*; +import static org.sopt.makers.operation.lecture.domain.Attribute.*; +import static org.sopt.makers.operation.lecture.domain.LectureStatus.*; + +import org.sopt.makers.operation.app.lecture.dto.response.LectureCurrentRoundResponse; +import org.sopt.makers.operation.app.lecture.dto.response.LectureResponseType; +import org.sopt.makers.operation.app.lecture.dto.response.TodayLectureResponse; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.attendance.repository.attendance.AttendanceRepository; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.LectureException; +import org.sopt.makers.operation.exception.SubLectureException; +import org.sopt.makers.operation.lecture.domain.Attribute; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.SubLecture; +import org.sopt.makers.operation.lecture.repository.lecture.LectureRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AppLectureServiceImpl implements AppLectureService { + + private final LectureRepository lectureRepository; + private final AttendanceRepository attendanceRepository; + private final ValueConfig valueConfig; + + @Override + public TodayLectureResponse getTodayLecture(long memberPlaygroundId) { + val attendances = attendanceRepository.findToday(memberPlaygroundId); + checkAttendancesSize(attendances); + + if (attendances.isEmpty()) { + return TodayLectureResponse.getEmptyResponse(); + } + + val attendance = getNowAttendance(attendances); + val lecture = attendance.getLecture(); + val responseType = getResponseType(lecture); + val message = getMessage(lecture.getAttribute()); + + if (responseType.equals(LectureResponseType.NO_ATTENDANCE) || lecture.isBefore()) { + return TodayLectureResponse.of(responseType, lecture, message, Collections.emptyList()); + } + + val subAttendances = attendance.getSubAttendances(); + + return getTodayLectureResponse(subAttendances, responseType, lecture); + } + + private void checkAttendancesSize(List attendances) { + if (attendances.size() > valueConfig.getSUB_LECTURE_MAX_ROUND()) { + throw new LectureException(INVALID_COUNT_SESSION); + } + } + + private boolean checkOnAttendanceAbsence(SubLecture subLecture, SubAttendance subAttendance) { + val isOnAttendanceCheck = subLecture.isEnded(valueConfig.getATTENDANCE_MINUTE()); + return !isOnAttendanceCheck && subAttendance.getStatus().equals(ABSENT); + } + + private Attendance getNowAttendance(List attendances) { + val index = getAttendanceIndex(attendances); + return attendances.get(index); + } + + private int getAttendanceIndex(List attendances) { + val isMultipleAttendance = getIsMultipleAttendance(attendances.size()); + return isMultipleAttendance ? 1 : 0; + } + private boolean getIsMultipleAttendance(int lectureCount) { + return LocalDateTime.now().getHour() >= valueConfig.getHACKATHON_LECTURE_START_HOUR() + && lectureCount == valueConfig.getMAX_LECTURE_COUNT(); + } + + private SubAttendance getNowSubAttendance(List subAttendances, Lecture lecture) { + val index = getSubAttendanceIndex(lecture); + return subAttendances.get(index); + } + + private int getSubAttendanceIndex(Lecture lecture) { + return lecture.isFirst() ? 0 : 1; + } + + private LectureResponseType getResponseType(Lecture lecture) { + val attribute = lecture.getAttribute(); + return attribute.equals(ETC) ? LectureResponseType.NO_ATTENDANCE : LectureResponseType.HAS_ATTENDANCE; + } + + private String getMessage(Attribute attribute) { + return switch (attribute) { + case SEMINAR -> valueConfig.getSEMINAR_MESSAGE(); + case EVENT -> valueConfig.getEVENT_MESSAGE(); + case ETC -> valueConfig.getETC_MESSAGE(); + }; + } + + private TodayLectureResponse getTodayLectureResponse( + List subAttendances, + LectureResponseType responseType, + Lecture lecture + ) { + val subAttendance = getNowSubAttendance(subAttendances, lecture); + val subLecture = subAttendance.getSubLecture(); + val message = getMessage(lecture.getAttribute()); + + if (checkOnAttendanceAbsence(subLecture, subAttendance)) { + return TodayLectureResponse.getOnAttendanceLectureResponse(subAttendance, lecture, responseType, message); + } + + return TodayLectureResponse.getAttendanceLectureResponse(subAttendances, subAttendance, lecture, responseType, message); + } + + @Override + public LectureCurrentRoundResponse getCurrentLectureRound(long lectureId) { + val lecture = findLecture(lectureId); + val subLecture = getSubLecture(lecture); + checkLectureExist(lecture); + checkLectureBefore(lecture); + checkEndAttendance(subLecture); + checkLectureEnd(lecture); + return LectureCurrentRoundResponse.of(subLecture); + } + + private Lecture findLecture(Long id) { + return lectureRepository.findById(id) + .orElseThrow(() -> new LectureException(INVALID_LECTURE)); + } + + private SubLecture getSubLecture(Lecture lecture) { + val status = lecture.getLectureStatus(); + val round = status.equals(FIRST) ? 1 : 2; + return getSubLecture(lecture, round); + } + + private SubLecture getSubLecture(Lecture lecture, int round) { + return lecture.getSubLectures().stream() + .filter(l -> l.getRound() == round) + .findFirst() + .orElseThrow(() -> new SubLectureException(NO_SUB_LECTURE_EQUAL_ROUND)); + } + + private void checkLectureExist(Lecture lecture) { + val today = LocalDate.now(); + val startOfDay = today.atStartOfDay(); + val endOfDay = LocalDateTime.of(today, LocalTime.MAX); + val startAt = lecture.getStartDate(); + if (startAt.isBefore(startOfDay) || startAt.isAfter(endOfDay)) { + throw new LectureException(NO_SESSION); + } + } + + private void checkLectureBefore(Lecture lecture) { + if (lecture.isBefore()) { + throw new LectureException(NOT_STARTED_ATTENDANCE); + } + } + + private void checkEndAttendance(SubLecture subLecture) { + if (subLecture.isEnded(valueConfig.getATTENDANCE_MINUTE())) { + throw new LectureException(ENDED_ATTENDANCE, subLecture.getRound()); + } + } + + private void checkLectureEnd(Lecture lecture) { + if (lecture.isEnd()) { + throw new LectureException(END_LECTURE); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApi.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApi.java new file mode 100644 index 00000000..88771cb8 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApi.java @@ -0,0 +1,57 @@ +package org.sopt.makers.operation.app.member.api; + +import java.security.Principal; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.ResponseEntity; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import lombok.NonNull; + +public interface AppMemberApi { + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 전체 출석 정보 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "전체 출석 정보 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getMemberTotalAttendance( + @Parameter(hidden = true) @NonNull Principal principal); + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 출석 점수 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 점수 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getScore( + @Parameter(hidden = true) @NonNull Principal principal); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApiController.java new file mode 100644 index 00000000..caad302b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/api/AppMemberApiController.java @@ -0,0 +1,43 @@ +package org.sopt.makers.operation.app.member.api; + +import static org.sopt.makers.operation.code.success.app.MemberSuccessCode.*; + +import java.security.Principal; + +import org.sopt.makers.operation.app.member.service.AppMemberService; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.common.util.CommonUtils; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/app/members") +public class AppMemberApiController implements AppMemberApi { + + private final AppMemberService memberService; + private final CommonUtils utils; + + @Override + @GetMapping("/attendances") + public ResponseEntity> getMemberTotalAttendance(@NonNull Principal principal) { + val memberId = utils.getMemberId(principal); + val response = memberService.getMemberTotalAttendance(memberId); + return ApiResponseUtil.success(SUCCESS_GET_TOTAL_ATTENDANCE, response); + } + + @Override + @GetMapping("/score") + public ResponseEntity> getScore(@NonNull Principal principal) { + val memberId = utils.getMemberId(principal); + val response = memberService.getMemberScore(memberId); + return ApiResponseUtil.success(SUCCESS_GET_ATTENDANCE_SCORE, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceStatusListVO.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceStatusListVO.java new file mode 100644 index 00000000..fcde3507 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceStatusListVO.java @@ -0,0 +1,32 @@ +package org.sopt.makers.operation.app.member.dto.response; + +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; + +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.member.domain.Member; + +import lombok.Builder; + +@Builder +public record AttendanceStatusListVO( + int attendance, + int absent, + int tardy, + int participate +) { + + public static AttendanceStatusListVO of(Member member) { + return AttendanceStatusListVO.builder() + .attendance(getCount(member, ATTENDANCE)) + .absent(getCount(member, ABSENT)) + .tardy(getCount(member, TARDY)) + .participate(getCount(member, PARTICIPATE)) + .build(); + } + + private static int getCount(Member member, AttendanceStatus status) { + return (int)member.getAttendances().stream() + .filter(attendance -> attendance.getStatus().equals(status)) + .count(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalResponseDTO.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalResponseDTO.java new file mode 100644 index 00000000..1bf8f22a --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalResponseDTO.java @@ -0,0 +1,34 @@ +package org.sopt.makers.operation.app.member.dto.response; + +import java.util.List; + +import lombok.Builder; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.member.domain.Member; + +import static lombok.AccessLevel.PRIVATE; + +@Builder(access = PRIVATE) +public record AttendanceTotalResponseDTO( + Part part, + int generation, + String name, + float score, + AttendanceStatusListVO total, + List attendances +) { + public static AttendanceTotalResponseDTO of(Member member, List attendances){ + return AttendanceTotalResponseDTO.builder() + .part(member.getPart()) + .generation(member.getGeneration()) + .name(member.getName()) + .score(member.getScore()) + .total(getTotal(member)) + .attendances(attendances) + .build(); + } + + private static AttendanceStatusListVO getTotal(Member member) { + return AttendanceStatusListVO.of(member); + } +} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalVO.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalVO.java similarity index 77% rename from src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalVO.java rename to operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalVO.java index 0d576ca2..e9754509 100644 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalVO.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/AttendanceTotalVO.java @@ -1,11 +1,11 @@ -package org.sopt.makers.operation.dto.attendance; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.lecture.Attribute; +package org.sopt.makers.operation.app.member.dto.response; import java.time.format.DateTimeFormatter; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.lecture.domain.Attribute; + public record AttendanceTotalVO( Attribute attribute, String name, diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/MemberScoreGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/MemberScoreGetResponse.java new file mode 100644 index 00000000..6dfc6601 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/dto/response/MemberScoreGetResponse.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.app.member.dto.response; + +import static lombok.AccessLevel.PRIVATE; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record MemberScoreGetResponse( + float score +) { + public static MemberScoreGetResponse of(float score){ + return MemberScoreGetResponse.builder() + .score(score) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberService.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberService.java new file mode 100644 index 00000000..db0afb33 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberService.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.app.member.service; + +import org.sopt.makers.operation.app.member.dto.response.AttendanceTotalResponseDTO; +import org.sopt.makers.operation.app.member.dto.response.MemberScoreGetResponse; + +public interface AppMemberService { + AttendanceTotalResponseDTO getMemberTotalAttendance(Long playGroundId); + MemberScoreGetResponse getMemberScore(Long playGroundId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberServiceImpl.java new file mode 100644 index 00000000..f56a849a --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/member/service/AppMemberServiceImpl.java @@ -0,0 +1,65 @@ +package org.sopt.makers.operation.app.member.service; + +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; +import static org.sopt.makers.operation.code.failure.member.memberFailureCode.*; +import static org.sopt.makers.operation.lecture.domain.Attribute.*; + +import java.util.List; + +import org.sopt.makers.operation.app.member.dto.response.AttendanceTotalResponseDTO; +import org.sopt.makers.operation.app.member.dto.response.AttendanceTotalVO; +import org.sopt.makers.operation.app.member.dto.response.MemberScoreGetResponse; +import org.sopt.makers.operation.attendance.repository.attendance.AttendanceRepository; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.exception.MemberException; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AppMemberServiceImpl implements AppMemberService { + private final MemberRepository memberRepository; + private final AttendanceRepository attendanceRepository; + private final ValueConfig valueConfig; + + @Override + public AttendanceTotalResponseDTO getMemberTotalAttendance(Long playGroundId) { + val member = memberRepository.getMemberByPlaygroundIdAndGeneration(playGroundId, valueConfig.getGENERATION()) + .orElseThrow(() -> new MemberException(INVALID_MEMBER)); + + val attendances = findAttendances(member); + + val filteredAttendances = filterEtcNoAppearance(attendances); + + return AttendanceTotalResponseDTO.of(member, filteredAttendances); + } + + @Override + public MemberScoreGetResponse getMemberScore(Long playGroundId) { + val member = memberRepository.getMemberByPlaygroundIdAndGeneration(playGroundId, valueConfig.getGENERATION()) + .orElseThrow(() -> new MemberException(INVALID_MEMBER)); + + return MemberScoreGetResponse.of(member.getScore()); + } + + private List filterEtcNoAppearance(List attendances) { + return attendances.stream() + .filter(attendanceTotalVO -> + !(attendanceTotalVO.attribute().equals(ETC) + && attendanceTotalVO.status().equals(NOT_PARTICIPATE)) + ) + .toList(); + } + + private List findAttendances(Member member) { + return attendanceRepository.findAttendanceByMemberId(member.getId()) + .stream().map(AttendanceTotalVO::getTotalAttendanceVO) + .toList(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApi.java b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApi.java new file mode 100644 index 00000000..9fcc6627 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApi.java @@ -0,0 +1,37 @@ +package org.sopt.makers.operation.app.schedule.api; + +import java.time.LocalDateTime; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +public interface ScheduleApi { + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 일정 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "일정 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getSchedules( + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start, + @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApiController.java new file mode 100644 index 00000000..542ce85c --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/api/ScheduleApiController.java @@ -0,0 +1,37 @@ +package org.sopt.makers.operation.app.schedule.api; + +import static org.sopt.makers.operation.code.success.app.ScheduleSuccessCode.*; +import static org.springframework.format.annotation.DateTimeFormat.ISO.*; + +import java.time.LocalDateTime; + +import org.sopt.makers.operation.app.schedule.service.ScheduleService; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.springframework.format.annotation.DateTimeFormat; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/app/schedules") +public class ScheduleApiController implements ScheduleApi { + + private final ScheduleService scheduleService; + + @Override + @GetMapping + public ResponseEntity> getSchedules( + @RequestParam @DateTimeFormat(iso = DATE_TIME) LocalDateTime start, + @RequestParam @DateTimeFormat(iso = DATE_TIME) LocalDateTime end + ) { + val response = scheduleService.getSchedules(start, end); + return ApiResponseUtil.success(SUCCESS_GET_SCHEDULES, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/dto/response/ScheduleListGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/dto/response/ScheduleListGetResponse.java new file mode 100644 index 00000000..c2f38087 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/dto/response/ScheduleListGetResponse.java @@ -0,0 +1,68 @@ +package org.sopt.makers.operation.app.schedule.dto.response; + +import static java.time.format.TextStyle.*; +import static java.util.Locale.*; +import static lombok.AccessLevel.*; + +import java.time.LocalDate; +import java.util.List; +import java.util.Map; + +import org.sopt.makers.operation.lecture.domain.Attribute; +import org.sopt.makers.operation.schedule.domain.Schedule; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record ScheduleListGetResponse( + List dates +) { + + public static ScheduleListGetResponse of(Map> scheduleMap) { + return ScheduleListGetResponse.builder() + .dates(getDates(scheduleMap)) + .build(); + } + + private static List getDates(Map> scheduleMap) { + return scheduleMap.keySet().stream().sorted() + .map(key -> DateResponse.of(key, scheduleMap.get(key))) + .toList(); + } + + @Builder(access = PRIVATE) + record DateResponse( + String date, + String dayOfWeek, + List schedules + ) { + + private static DateResponse of(LocalDate date, List schedules) { + return DateResponse.builder() + .date(date.toString()) + .dayOfWeek(date.getDayOfWeek().getDisplayName(SHORT, KOREAN)) + .schedules(schedules.stream().map(ScheduleResponse::of).toList()) + .build(); + } + } + + @Builder(access = PRIVATE) + record ScheduleResponse( + long scheduleId, + String startDate, + String endDate, + Attribute attribute, + String title + ) { + + private static ScheduleResponse of(Schedule schedule) { + return ScheduleResponse.builder() + .scheduleId(schedule.getId()) + .startDate(schedule.getStartDate().toString()) + .endDate(schedule.getEndDate().toString()) + .attribute(schedule.getAttribute()) + .title(schedule.getTitle()) + .build(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleService.java b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleService.java new file mode 100644 index 00000000..3f84203a --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleService.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.app.schedule.service; + +import java.time.LocalDateTime; + +import org.sopt.makers.operation.app.schedule.dto.response.ScheduleListGetResponse; + +public interface ScheduleService { + ScheduleListGetResponse getSchedules(LocalDateTime startAt, LocalDateTime endAt); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleServiceImpl.java new file mode 100644 index 00000000..74cb962b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/app/schedule/service/ScheduleServiceImpl.java @@ -0,0 +1,95 @@ +package org.sopt.makers.operation.app.schedule.service; + +import static java.time.temporal.ChronoUnit.*; +import static org.sopt.makers.operation.code.failure.ScheduleFailureCode.*; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.schedule.domain.Schedule; +import org.sopt.makers.operation.schedule.repository.ScheduleRepository; +import org.sopt.makers.operation.exception.ScheduleException; +import org.sopt.makers.operation.app.schedule.dto.response.ScheduleListGetResponse; +import org.springframework.stereotype.Service; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +public class ScheduleServiceImpl implements ScheduleService { + + private final ScheduleRepository scheduleRepository; + + private final ValueConfig valueConfig; + + @Override + public ScheduleListGetResponse getSchedules(LocalDateTime startAt, LocalDateTime endAt) { + val schedules = scheduleRepository.findBetween(startAt, endAt); + val scheduleMap = getClassifiedMap(schedules, startAt, endAt); + return ScheduleListGetResponse.of(scheduleMap); + } + + private Map> getClassifiedMap( + List schedules, + LocalDateTime startAt, + LocalDateTime endAt + ) { + val scheduleMap = getInitializedMap(startAt, endAt); + schedules.forEach(schedule -> putScheduleToMap(scheduleMap, schedule)); + return scheduleMap; + } + + private Map> getInitializedMap(LocalDateTime startAt, LocalDateTime endAt) { + //TODO: 클라이언트 개발 시간 리소스 절약을 위해 해당 메소드 활용, 가능한 일정이 존재하는 날짜만 key로 가지는 HashMap로 변경 요망 + val duration = getDuration(startAt, endAt); + return IntStream.range(0, duration) + .mapToObj(startAt::plusDays) + .collect(Collectors.toMap(LocalDateTime::toLocalDate, date -> new ArrayList<>())); + } + + private int getDuration(LocalDateTime startAt, LocalDateTime endAt) { + val duration = Duration.between(startAt, endAt).toDays() + 1; + val minDuration = valueConfig.getMIN_SCHEDULE_DURATION(); + val maxDuration = valueConfig.getMAX_SCHEDULE_DURATION(); + + if (duration < minDuration || duration > maxDuration) { + throw new ScheduleException(INVALID_DATE_PERM); + } + + return (int)duration; + } + + private void putScheduleToMap(Map> scheduleMap, Schedule schedule) { + val duration = DAYS.between(schedule.getStartDate(), schedule.getEndDate()); + val dayDuration = valueConfig.getDAY_DURATION(); + val twoDaysDuration = valueConfig.getTWO_DAYS_DURATION(); + + scheduleMap.computeIfAbsent(schedule.getStartDate().toLocalDate(), k -> new ArrayList<>()).add(schedule); + + if (duration >= dayDuration) { + scheduleMap.computeIfAbsent(schedule.getEndDate().toLocalDate(), k -> new ArrayList<>()).add(schedule); + if (duration >= twoDaysDuration) { + putScheduleMapBetween(scheduleMap, schedule, (int)duration); + } + } + } + + private void putScheduleMapBetween(Map> scheduleMap, Schedule schedule, int duration) { + Stream.iterate(1, i -> i + 1).limit(duration - 1) + .forEach(i -> putScheduleAtDayCount(scheduleMap, schedule, i)); + } + + private void putScheduleAtDayCount(Map> scheduleMap, Schedule schedule, int dayCount) { + val date = schedule.getStartDate().plusDays(dayCount).toLocalDate(); + scheduleMap.computeIfAbsent(date, k -> new ArrayList<>()).add(schedule); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/common/config/SwaggerConfig.java b/operation-api/src/main/java/org/sopt/makers/operation/common/config/SwaggerConfig.java new file mode 100644 index 00000000..8d8c0134 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/config/SwaggerConfig.java @@ -0,0 +1,29 @@ +package org.sopt.makers.operation.common.config; + +import io.swagger.v3.oas.annotations.enums.SecuritySchemeIn; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.annotations.security.SecurityScheme; +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; + +@SecurityScheme( + name = "Authorization", + type = SecuritySchemeType.HTTP, + in = SecuritySchemeIn.HEADER, + bearerFormat = "JWT", + scheme = "Bearer" +) +@Configuration +public class SwaggerConfig { + @Bean + public OpenAPI api() { + Info info = new Info() + .title("Makers Operation API Docs") + .version("v2.0") + .description("운영 프로덕트 API 명세서 입니다."); + return new OpenAPI() + .info(info); + } +} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/config/TimezoneConfig.java b/operation-api/src/main/java/org/sopt/makers/operation/common/config/TimezoneConfig.java similarity index 53% rename from src/main/java/org/sopt/makers/operation/config/TimezoneConfig.java rename to operation-api/src/main/java/org/sopt/makers/operation/common/config/TimezoneConfig.java index f14faffe..bbf0503d 100644 --- a/src/main/java/org/sopt/makers/operation/config/TimezoneConfig.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/config/TimezoneConfig.java @@ -1,13 +1,13 @@ -package org.sopt.makers.operation.config; +package org.sopt.makers.operation.common.config; import java.util.TimeZone; -import javax.annotation.PostConstruct; - import org.springframework.context.annotation.Configuration; +import jakarta.annotation.PostConstruct; + @Configuration -public class TimezoneConfig { +public class TimezoneConfig { //TODO: 타임 체크 로컬에서 UTF로 바꿔서 체크 @PostConstruct public void init() { diff --git a/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java b/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java new file mode 100644 index 00000000..5a4093f2 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/handler/ErrorHandler.java @@ -0,0 +1,77 @@ +package org.sopt.makers.operation.common.handler; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.exception.AdminFailureException; +import org.sopt.makers.operation.exception.AlarmException; +import org.sopt.makers.operation.exception.AttendanceException; +import org.sopt.makers.operation.exception.DateTimeParseCustomException; +import org.sopt.makers.operation.exception.LectureException; +import org.sopt.makers.operation.exception.MemberException; +import org.sopt.makers.operation.exception.ScheduleException; +import org.sopt.makers.operation.exception.SubLectureException; +import org.sopt.makers.operation.exception.TokenException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestControllerAdvice +public class ErrorHandler { + + @ExceptionHandler(AdminFailureException.class) + public ResponseEntity> authFailureException(AdminFailureException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(TokenException.class) + public ResponseEntity> tokenException(TokenException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(ScheduleException.class) + public ResponseEntity> scheduleException(ScheduleException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(MemberException.class) + public ResponseEntity> memberException(MemberException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(LectureException.class) + public ResponseEntity> lectureException(LectureException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(SubLectureException.class) + public ResponseEntity> subLectureException(SubLectureException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(DateTimeParseCustomException.class) + public ResponseEntity> dateTimeParseException(DateTimeParseCustomException ex) { + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(AlarmException.class) + public ResponseEntity> alarmException(AlarmException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + + @ExceptionHandler(AttendanceException.class) + public ResponseEntity> attendanceException(AttendanceException ex) { + log.error(ex.getMessage()); + return ApiResponseUtil.failure(ex.getFailureCode()); + } + +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/common/util/CommonUtils.java b/operation-api/src/main/java/org/sopt/makers/operation/common/util/CommonUtils.java new file mode 100644 index 00000000..40b7f0e7 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/util/CommonUtils.java @@ -0,0 +1,14 @@ +package org.sopt.makers.operation.common.util; + +import java.security.Principal; + +import org.springframework.context.annotation.Configuration; + +import lombok.NonNull; + +@Configuration +public class CommonUtils { + public long getMemberId(@NonNull Principal principal) { + return Long.parseLong(principal.getName()); + } +} diff --git a/src/main/java/org/sopt/makers/operation/util/Cookie.java b/operation-api/src/main/java/org/sopt/makers/operation/common/util/Cookie.java similarity index 94% rename from src/main/java/org/sopt/makers/operation/util/Cookie.java rename to operation-api/src/main/java/org/sopt/makers/operation/common/util/Cookie.java index 14b0f300..849bed17 100644 --- a/src/main/java/org/sopt/makers/operation/util/Cookie.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/common/util/Cookie.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.util; +package org.sopt.makers.operation.common.util; import lombok.val; import org.springframework.context.annotation.Configuration; diff --git a/operation-api/src/main/java/org/sopt/makers/operation/scheduler/LectureScheduler.java b/operation-api/src/main/java/org/sopt/makers/operation/scheduler/LectureScheduler.java new file mode 100644 index 00000000..16a914eb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/scheduler/LectureScheduler.java @@ -0,0 +1,22 @@ +package org.sopt.makers.operation.scheduler; + +import org.sopt.makers.operation.web.lecture.service.WebLectureService; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import lombok.RequiredArgsConstructor; + +@Component +@EnableScheduling +@RequiredArgsConstructor +public class LectureScheduler { + + private final WebLectureService lectureService; + + @Scheduled(cron = "0 0 0 ? * SUN") + public void endLecture() { + lectureService.endLectures(); + } + +} diff --git a/src/main/java/org/sopt/makers/operation/controller/HealthCheckController.java b/operation-api/src/main/java/org/sopt/makers/operation/test/HealthCheckController.java similarity index 62% rename from src/main/java/org/sopt/makers/operation/controller/HealthCheckController.java rename to operation-api/src/main/java/org/sopt/makers/operation/test/HealthCheckController.java index 12bfd6af..efebcb82 100644 --- a/src/main/java/org/sopt/makers/operation/controller/HealthCheckController.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/test/HealthCheckController.java @@ -1,15 +1,14 @@ -package org.sopt.makers.operation.controller; +package org.sopt.makers.operation.test; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import springfox.documentation.annotations.ApiIgnore; - @RestController -@ApiIgnore +@RequestMapping("/api/v1/test") public class HealthCheckController { - @GetMapping("/") + @GetMapping public String healthCheck() { return "Hello Operation!"; } diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApi.java new file mode 100644 index 00000000..4b222fdc --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApi.java @@ -0,0 +1,75 @@ +package org.sopt.makers.operation.web.admin.api; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.web.admin.dto.request.LoginRequest; +import org.sopt.makers.operation.web.admin.dto.request.SignUpRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.RequestBody; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +public interface AdminApi { + + @Operation( + summary = "회원가입 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "회원가입 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> signup(@RequestBody SignUpRequest signUpRequestDTO); + + + @Operation( + summary = "로그인 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "로그인 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> login(@RequestBody LoginRequest userLoginRequestDTO); + + + @Operation( + security = @SecurityRequirement(name = "Authorization"), + summary = "앱 내 일정 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "일정 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> refresh(@CookieValue String refreshToken); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApiController.java new file mode 100644 index 00000000..d173ffc3 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/api/AdminApiController.java @@ -0,0 +1,51 @@ +package org.sopt.makers.operation.web.admin.api; + +import static org.sopt.makers.operation.code.success.web.AdminSuccessCode.*; + +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.common.util.Cookie; +import org.sopt.makers.operation.web.admin.dto.request.LoginRequest; +import org.sopt.makers.operation.web.admin.dto.request.SignUpRequest; +import org.sopt.makers.operation.web.admin.service.AdminService; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/auth") +@ComponentScan("operation-business") +public class AdminApiController implements AdminApi { + + private final AdminService authService; + private final Cookie cookie; + + @Override + @PostMapping("/signup") + public ResponseEntity> signup(SignUpRequest signUpRequestDTO) { + val response = authService.signUp(signUpRequestDTO); + return ApiResponseUtil.success(SUCCESS_SIGN_UP, response); + } + + @Override + @PostMapping("/login") + public ResponseEntity> login(LoginRequest userLoginRequestDTO) { + val response = authService.login(userLoginRequestDTO); + val headers = cookie.setRefreshToken(response.refreshToken()); + return ApiResponseUtil.success(SUCCESS_LOGIN_UP, headers, response.loginResponseVO()); + } + + @Override + @PatchMapping("/refresh") + public ResponseEntity> refresh(String refreshToken) { + val response = authService.refresh(refreshToken); + return ApiResponseUtil.success(SUCCESS_GET_REFRESH_TOKEN, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/LoginRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/LoginRequest.java new file mode 100644 index 00000000..d612c34f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/LoginRequest.java @@ -0,0 +1,7 @@ +package org.sopt.makers.operation.web.admin.dto.request; + +public record LoginRequest( + String email, + String password +) { +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/SignUpRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/SignUpRequest.java new file mode 100644 index 00000000..8fed9817 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/request/SignUpRequest.java @@ -0,0 +1,21 @@ +package org.sopt.makers.operation.web.admin.dto.request; + +import org.sopt.makers.operation.admin.domain.Admin; +import org.sopt.makers.operation.admin.domain.Role; + +public record SignUpRequest( + String email, + String password, + String name, + Role role +) { + + public Admin toEntity(String encodedPassword) { + return Admin.builder() + .email(this.email) + .password(encodedPassword) + .name(this.name) + .role(this.role) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/LoginResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/LoginResponse.java new file mode 100644 index 00000000..9f72926b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/LoginResponse.java @@ -0,0 +1,39 @@ +package org.sopt.makers.operation.web.admin.dto.response; + +import org.sopt.makers.operation.admin.domain.Admin; +import org.sopt.makers.operation.admin.domain.AdminStatus; +import lombok.Builder; + +import static lombok.AccessLevel.PRIVATE; + +@Builder(access = PRIVATE) +public record LoginResponse( + LoginResponseVO loginResponseVO, + String refreshToken +) { + + public static LoginResponse of(Admin admin, String accessToken) { + return builder() + .loginResponseVO(LoginResponseVO.of(admin, accessToken)) + .refreshToken(admin.getRefreshToken()) + .build(); + } + + @Builder + record LoginResponseVO( + Long id, + String name, + AdminStatus adminStatus, + String accessToken + ) { + + public static LoginResponseVO of(Admin admin, String accessToken) { + return builder() + .id(admin.getId()) + .name(admin.getName()) + .adminStatus(admin.getStatus()) + .accessToken(accessToken) + .build(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/SignUpResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/SignUpResponse.java new file mode 100644 index 00000000..5eb18f59 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/SignUpResponse.java @@ -0,0 +1,25 @@ +package org.sopt.makers.operation.web.admin.dto.response; + +import org.sopt.makers.operation.admin.domain.Admin; +import org.sopt.makers.operation.admin.domain.Role; +import lombok.Builder; + +import static lombok.AccessLevel.PRIVATE; + +@Builder(access = PRIVATE) +public record SignUpResponse( + long id, + String email, + String name, + Role role +) { + + public static SignUpResponse of(Admin admin) { + return SignUpResponse.builder() + .id(admin.getId()) + .email(admin.getEmail()) + .name(admin.getName()) + .role(admin.getRole()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/TokenRefreshGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/TokenRefreshGetResponse.java new file mode 100644 index 00000000..68cdf41b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/dto/response/TokenRefreshGetResponse.java @@ -0,0 +1,17 @@ +package org.sopt.makers.operation.web.admin.dto.response; + +import lombok.Builder; + +import static lombok.AccessLevel.PRIVATE; + +@Builder(access = PRIVATE) +public record TokenRefreshGetResponse( + String accessToken +) { + + public static TokenRefreshGetResponse of(String accessToken) { + return TokenRefreshGetResponse.builder() + .accessToken(accessToken) + .build(); + } +} \ No newline at end of file diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminService.java new file mode 100644 index 00000000..030eecb5 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminService.java @@ -0,0 +1,13 @@ +package org.sopt.makers.operation.web.admin.service; + +import org.sopt.makers.operation.web.admin.dto.request.LoginRequest; +import org.sopt.makers.operation.web.admin.dto.request.SignUpRequest; +import org.sopt.makers.operation.web.admin.dto.response.LoginResponse; +import org.sopt.makers.operation.web.admin.dto.response.TokenRefreshGetResponse; +import org.sopt.makers.operation.web.admin.dto.response.SignUpResponse; + +public interface AdminService { + SignUpResponse signUp(SignUpRequest request); + LoginResponse login(LoginRequest request); + TokenRefreshGetResponse refresh(String refreshToken); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminServiceImpl.java new file mode 100644 index 00000000..e775cfd9 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/admin/service/AdminServiceImpl.java @@ -0,0 +1,109 @@ +package org.sopt.makers.operation.web.admin.service; + +import static org.sopt.makers.operation.code.failure.member.memberFailureCode.*; +import static org.sopt.makers.operation.code.failure.admin.AdminFailureCode.*; + +import org.sopt.makers.operation.admin.domain.Admin; +import org.sopt.makers.operation.authentication.AdminAuthentication; +import org.sopt.makers.operation.exception.AdminFailureException; +import org.sopt.makers.operation.jwt.JwtTokenProvider; +import org.sopt.makers.operation.jwt.JwtTokenType; +import org.sopt.makers.operation.web.admin.dto.request.SignUpRequest; +import org.sopt.makers.operation.web.admin.dto.response.SignUpResponse; +import org.sopt.makers.operation.web.admin.dto.request.LoginRequest; +import org.sopt.makers.operation.web.admin.dto.response.LoginResponse; +import org.sopt.makers.operation.web.admin.dto.response.TokenRefreshGetResponse; +import org.sopt.makers.operation.admin.repository.AdminRepository; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class AdminServiceImpl implements AdminService { + + private final JwtTokenProvider jwtTokenProvider; + private final PasswordEncoder passwordEncoder; + private final AdminRepository adminRepository; + + @Override + @Transactional + public SignUpResponse signUp(SignUpRequest request){ + checkEmailDuplicated(request.email()); + val adminEntity = request.toEntity(passwordEncoder.encode(request.password())); + val admin = adminRepository.save(adminEntity); + return SignUpResponse.of(admin); + } + + private void checkEmailDuplicated(String email) { + val isExist = adminRepository.existsByEmail(email); + if (isExist) { + throw new AdminFailureException(DUPLICATED_EMAIL); + } + } + + @Override + @Transactional + public LoginResponse login(LoginRequest request) { + val admin = findByEmail(request.email()); + checkPasswordMatched(request.password(), admin); + checkAdminAllowed(admin); + val refreshToken = generateRefreshToken(admin); + admin.updateRefreshToken(refreshToken); + val accessToken = generateAccessToken(admin); + return LoginResponse.of(admin, accessToken); + } + + private String generateAccessToken(Admin admin) { + val adminAuthentication = new AdminAuthentication(admin.getId(), null, null); + return jwtTokenProvider.generateAccessToken(adminAuthentication); + } + + private String generateRefreshToken(Admin admin) { + val authentication = new AdminAuthentication(admin.getId(), null, null); + return jwtTokenProvider.generateRefreshToken(authentication); + } + + private Admin findByEmail(String email) { + return adminRepository.findByEmail(email) + .orElseThrow(() -> new AdminFailureException(INVALID_EMAIL)); + } + + private void checkPasswordMatched(String password, Admin admin) { + if (!admin.checkPasswordMatched(passwordEncoder, password)) { + throw new AdminFailureException(INVALID_PASSWORD); + } + } + + private void checkAdminAllowed(Admin admin) { + if (admin.isNotAllowed()) { + throw new AdminFailureException(NOT_APPROVED_ACCOUNT); + } + } + + @Override + @Transactional + public TokenRefreshGetResponse refresh(String refreshToken) { + val adminId = jwtTokenProvider.getId(refreshToken, JwtTokenType.REFRESH_TOKEN); + val admin = findById(adminId); + validateRefreshToken(admin, refreshToken); + val newAccessToken = generateAccessToken(admin); + + return TokenRefreshGetResponse.of(newAccessToken); + } + + public void validateRefreshToken(Admin admin, String refreshToken) { + if (!admin.isMatchRefreshToken(refreshToken)) { + throw new AdminFailureException(INVALID_REFRESH_TOKEN); + } + } + + private Admin findById(Long adminId) { + return adminRepository.findById(adminId) + .orElseThrow(() -> new AdminFailureException(INVALID_MEMBER)); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApi.java new file mode 100644 index 00000000..b0052754 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApi.java @@ -0,0 +1,122 @@ +package org.sopt.makers.operation.web.alarm.api; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.alarm.domain.Status; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmCreateRequest; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmSendRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +public interface AlarmApi { + + @Operation( + summary = "알림 전송 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "알림 전송 성공" + ), + @ApiResponse( + responseCode = "400", + description = "알림 전송에 실패하였습니다." + ), + @ApiResponse( + responseCode = "400", + description = "전송된 알림입니다." + ), + @ApiResponse( + responseCode = "400", + description = "비활동 유저 불러오기에 실패하였습니다." + ), + @ApiResponse( + responseCode = "404", + description = "알림이 존재하지 않습니다." + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> sendAlarm(@RequestBody AlarmSendRequest request); + + @Operation( + summary = "알림 생성 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "알림 생성 성공" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> createAlarm(@RequestBody AlarmCreateRequest request); + + @Operation( + summary = "알림 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "알림 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getAlarms( + @RequestParam(required = false) Integer generation, + @RequestParam(required = false) Part part, + @RequestParam(required = false) Status status, + Pageable pageable + ); + + @Operation( + summary = "알림 상세 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "알림 상세 조회 성공" + ), + @ApiResponse( + responseCode = "404", + description = "알림이 존재하지 않습니다." + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getAlarm(@PathVariable long alarmId); + + @Operation( + summary = "알림 삭제 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "알림 삭제 성공" + ), + @ApiResponse( + responseCode = "404", + description = "알림이 존재하지 않습니다." + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> deleteAlarm(@PathVariable long alarmId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApiController.java new file mode 100644 index 00000000..88c1523c --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/api/AlarmApiController.java @@ -0,0 +1,71 @@ +package org.sopt.makers.operation.web.alarm.api; + +import static org.sopt.makers.operation.code.success.web.AlarmSuccessCode.*; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.alarm.domain.Status; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmCreateRequest; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmSendRequest; +import org.sopt.makers.operation.web.alarm.service.AlarmService; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/alarms") +public class AlarmApiController implements AlarmApi { + + private final AlarmService alarmService; + + @Override + @PostMapping("/send") + public ResponseEntity> sendAlarm(AlarmSendRequest request) { + alarmService.sendAlarm(request); + return ApiResponseUtil.success(SUCCESS_SEND_ALARM); + } + + @Override + @PostMapping + public ResponseEntity> createAlarm(AlarmCreateRequest request) { + val response = alarmService.saveAlarm(request); + return ApiResponseUtil.success(SUCCESS_CREATE_ALARM, response.alarmId()); + } + + @Override + @GetMapping + public ResponseEntity> getAlarms( + @RequestParam(required = false) Integer generation, + @RequestParam(required = false) Part part, + @RequestParam(required = false) Status status, + Pageable pageable + ) { + val response = alarmService.getAlarms(generation, part, status, pageable); + return ApiResponseUtil.success(SUCCESS_GET_ALARMS, response); + } + + @Override + @GetMapping("/{alarmId}") + public ResponseEntity> getAlarm(@PathVariable long alarmId) { + val response = alarmService.getAlarm(alarmId); + return ApiResponseUtil.success(SUCCESS_GET_ALARM, response); + } + + @Override + @DeleteMapping("/{alarmId}") + public ResponseEntity> deleteAlarm(@PathVariable long alarmId) { + alarmService.deleteAlarm(alarmId); + return ApiResponseUtil.success(SUCCESS_DELETE_ALARM); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmCreateRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmCreateRequest.java new file mode 100644 index 00000000..d2ad0347 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmCreateRequest.java @@ -0,0 +1,34 @@ +package org.sopt.makers.operation.web.alarm.dto.request; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Alarm; +import org.sopt.makers.operation.alarm.domain.Attribute; + +public record AlarmCreateRequest( + int generation, + int generationAt, + Attribute attribute, + String title, + String content, + String link, + Boolean isActive, + Part part, + List targetList +) { + + public Alarm toEntity() { + return Alarm.builder() + .generation(this.generation) + .generationAt(this.generationAt) + .attribute(this.attribute) + .title(this.title) + .content(this.content) + .link(this.link) + .isActive(this.isActive) + .part(this.part) + .targetList(this.targetList) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmSendRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmSendRequest.java new file mode 100644 index 00000000..a3a30aea --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/request/AlarmSendRequest.java @@ -0,0 +1,6 @@ +package org.sopt.makers.operation.web.alarm.dto.request; + +public record AlarmSendRequest( + long alarmId +) { +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmCreateResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmCreateResponse.java new file mode 100644 index 00000000..d6e237c2 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmCreateResponse.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.web.alarm.dto.response; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.alarm.domain.Alarm; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AlarmCreateResponse( + long alarmId +) { + + public static AlarmCreateResponse of(Alarm alarm) { + return AlarmCreateResponse.builder() + .alarmId(alarm.getId()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmGetResponse.java new file mode 100644 index 00000000..56fe9ec2 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmGetResponse.java @@ -0,0 +1,50 @@ +package org.sopt.makers.operation.web.alarm.dto.response; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; +import static java.util.Objects.*; +import static lombok.AccessLevel.*; + +import java.time.LocalDateTime; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Alarm; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AlarmGetResponse( + String attribute, + @JsonInclude(value = NON_NULL) + String part, + Boolean isActive, + String title, + String content, + String link, + String createdAt, + @JsonInclude(value = NON_NULL) + String sendAt +) { + + public static AlarmGetResponse of(Alarm alarm) { + return AlarmGetResponse.builder() + .attribute(alarm.getAttribute().getName()) + .part(getPartName(alarm.getPart())) + .isActive(alarm.getIsActive()) + .title(alarm.getTitle()) + .content(alarm.getContent()) + .link(alarm.getLink()) + .createdAt(alarm.getCreatedDate().toString()) + .sendAt(getSendAt(alarm.getSendAt())) + .build(); + } + + private static String getPartName(Part part) { + return nonNull(part) ? part.getName() : null; + } + + private static String getSendAt(LocalDateTime sendAt) { + return nonNull(sendAt) ? sendAt.toString() : null; + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmListGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmListGetResponse.java new file mode 100644 index 00000000..c37b31e8 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/dto/response/AlarmListGetResponse.java @@ -0,0 +1,63 @@ +package org.sopt.makers.operation.web.alarm.dto.response; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; +import static java.util.Objects.*; +import static lombok.AccessLevel.*; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Alarm; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AlarmListGetResponse( + List alarms, + int totalCount +) { + + public static AlarmListGetResponse of(List alarmList, int totalCount) { + return AlarmListGetResponse.builder() + .alarms(alarmList.stream().map(AlarmResponse::of).toList()) + .totalCount(totalCount) + .build(); + } + + @Builder(access = PRIVATE) + private record AlarmResponse( + long alarmId, + @JsonInclude(value = NON_NULL) + String part, + String attribute, + String title, + String content, + @JsonInclude(value = NON_NULL) + String sendAt, + String status + ) { + + private static AlarmResponse of(Alarm alarm) { + return AlarmResponse.builder() + .alarmId(alarm.getId()) + .part(getPartName(alarm.getPart())) + .attribute(alarm.getAttribute().getName()) + .title(alarm.getTitle()) + .content(alarm.getContent()) + .sendAt(getSendAt(alarm.getSendAt())) + .status(alarm.getStatus().getName()) + .build(); + } + + private static String getPartName(Part part) { + return nonNull(part) ? part.getName() : null; + } + + private static String getSendAt(LocalDateTime sendAt) { + return nonNull(sendAt) ? sendAt.toString() : null; + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmService.java new file mode 100644 index 00000000..94a551a6 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmService.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.web.alarm.service; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Status; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmCreateRequest; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmSendRequest; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmCreateResponse; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmGetResponse; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmListGetResponse; +import org.springframework.data.domain.Pageable; + +public interface AlarmService { + void sendAlarm(AlarmSendRequest request); + AlarmCreateResponse saveAlarm(AlarmCreateRequest requestDTO); + AlarmListGetResponse getAlarms(Integer generation, Part part, Status status, Pageable pageable); + AlarmGetResponse getAlarm(long alarmId); + void deleteAlarm(long alarmId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmServiceImpl.java new file mode 100644 index 00000000..a6229e65 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/alarm/service/AlarmServiceImpl.java @@ -0,0 +1,125 @@ +package org.sopt.makers.operation.web.alarm.service; + +import static org.sopt.makers.operation.code.failure.AlarmFailureCode.*; + +import java.util.List; +import java.util.Objects; + +import org.sopt.makers.operation.client.alarm.AlarmSender; +import org.sopt.makers.operation.client.alarm.dto.AlarmSenderRequest; +import org.sopt.makers.operation.client.playground.PlayGroundServer; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Alarm; +import org.sopt.makers.operation.alarm.domain.Status; +import org.sopt.makers.operation.alarm.repository.AlarmRepository; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmCreateRequest; +import org.sopt.makers.operation.exception.AlarmException; +import org.sopt.makers.operation.web.alarm.dto.request.AlarmSendRequest; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmCreateResponse; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmGetResponse; +import org.sopt.makers.operation.web.alarm.dto.response.AlarmListGetResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +public class AlarmServiceImpl implements AlarmService { + + private final AlarmRepository alarmRepository; + private final MemberRepository memberRepository; + + private final AlarmSender alarmSender; + private final PlayGroundServer playGroundServer; + private final ValueConfig valueConfig; + + @Override + @Transactional + public void sendAlarm(AlarmSendRequest request) { + val alarm = getAlarmReadyToSend(request.alarmId()); + val targets = getTargets(alarm); + alarmSender.send(AlarmSenderRequest.of(alarm, targets)); + alarm.updateToSent(); + } + + @Override + @Transactional + public AlarmCreateResponse saveAlarm(AlarmCreateRequest request) { + val savedAlarm = alarmRepository.save(request.toEntity()); + return AlarmCreateResponse.of(savedAlarm); + } + + @Override + public AlarmListGetResponse getAlarms(Integer generation, Part part, Status status, Pageable pageable) { + val alarms = alarmRepository.findOrderByCreatedDate(generation, part, status, pageable); + val totalCount = alarmRepository.count(generation, part, status); + return AlarmListGetResponse.of(alarms, totalCount); + } + + @Override + public AlarmGetResponse getAlarm(long alarmId) { + val alarm = findAlarm(alarmId); + return AlarmGetResponse.of(alarm); + } + + @Override + @Transactional + public void deleteAlarm(long alarmId) { + val alarm = findAlarm(alarmId); + alarmRepository.delete(alarm); + } + + private Alarm getAlarmReadyToSend(long alarmId) { + val alarm = findAlarm(alarmId); + if (alarm.isSent()) { + throw new AlarmException(SENT_ALARM); + } + return alarm; + } + + private Alarm findAlarm(long id) { + return alarmRepository.findById(id) + .orElseThrow(() -> new AlarmException(INVALID_ALARM)); + } + + private List getTargets(Alarm alarm) { + return alarm.hasTargets() + ? alarm.getTargetList() + : getTargetsByActivityAndPart(alarm.getIsActive(), alarm.getPart()); + } + + private List getTargetsByActivityAndPart(boolean isActive, Part part) { + return isActive ? getActiveTargets(part) : getInactiveTargets(part); + } + + private List getActiveTargets(Part part) { + val generation = valueConfig.getGENERATION(); + val members = memberRepository.find(generation, part); + return members.stream() + .map(Member::getPlaygroundId) + .filter(Objects::nonNull) + .map(String::valueOf) + .toList(); + } + + private List getInactiveTargets(Part part) { + val generation = valueConfig.getGENERATION(); + val activePlaygroundIds = getActiveTargets(part); + return getPlaygroundIds(generation, part).stream() + .filter(id -> !activePlaygroundIds.contains(id)) + .toList(); + } + + private List getPlaygroundIds(int generation, Part part) { + val members = playGroundServer.getMembers(generation, part); + return members.memberIds().stream() + .map(String::valueOf) + .toList(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApi.java new file mode 100644 index 00000000..1efb6170 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApi.java @@ -0,0 +1,95 @@ +package org.sopt.makers.operation.web.attendnace.api; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.web.attendnace.dto.request.SubAttendanceUpdateRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +public interface WebAttendanceApi { + + @Operation( + summary = "출석 상태 변경 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 상태 변경 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> updateSubAttendance(@RequestBody SubAttendanceUpdateRequest request); + + @Operation( + summary = "회원별 출석 정보 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "회원별 출석 정보 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> findAttendancesByMember(@PathVariable long memberId); + + @Operation( + summary = "출석 점수 갱신 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 점수 갱신 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> updateMemberScore(@PathVariable long memberId); + + @Operation( + summary = "세션별 출석 정보 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션별 출석 정보 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> findAttendancesByLecture( + @PathVariable long lectureId, + @RequestParam(required = false) Part part, + Pageable pageable); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApiController.java new file mode 100644 index 00000000..062b682d --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/api/WebAttendanceApiController.java @@ -0,0 +1,61 @@ +package org.sopt.makers.operation.web.attendnace.api; + +import static org.sopt.makers.operation.code.success.web.AttendanceSuccessCode.*; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.web.attendnace.dto.request.SubAttendanceUpdateRequest; +import org.sopt.makers.operation.web.attendnace.service.WebAttendanceService; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/attendances") +public class WebAttendanceApiController implements WebAttendanceApi { + + private final WebAttendanceService attendanceService; + + @Override + @PatchMapping + public ResponseEntity> updateSubAttendance(@RequestBody SubAttendanceUpdateRequest request) { + val response = attendanceService.updateSubAttendance(request); + return ApiResponseUtil.success(SUCCESS_UPDATE_ATTENDANCE_STATUS, response); + } + + @Override + @GetMapping("/{memberId}") + public ResponseEntity> findAttendancesByMember(@PathVariable long memberId) { + val response = attendanceService.getAttendancesByMember(memberId); + return ApiResponseUtil.success(SUCCESS_GET_MEMBER_ATTENDANCE, response); + } + + @Override + @PatchMapping("/member/{memberId}") + public ResponseEntity> updateMemberScore(@PathVariable long memberId) { + val response = attendanceService.updateMemberAllScore(memberId); + return ApiResponseUtil.success(SUCCESS_UPDATE_MEMBER_SCORE, response.score()); + } + + @Override + @GetMapping("/lecture/{lectureId}") + public ResponseEntity> findAttendancesByLecture( + @PathVariable long lectureId, + @RequestParam(required = false) Part part, + Pageable pageable + ) { + val response = attendanceService.getAttendancesByLecture(lectureId, part, pageable); + return ApiResponseUtil.success(SUCCESS_GET_ATTENDANCES, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/request/SubAttendanceUpdateRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/request/SubAttendanceUpdateRequest.java new file mode 100644 index 00000000..91b22a61 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/request/SubAttendanceUpdateRequest.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.web.attendnace.dto.request; + +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; + +public record SubAttendanceUpdateRequest( + long subAttendanceId, + AttendanceStatus status +) { +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByLectureGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByLectureGetResponse.java new file mode 100644 index 00000000..9e048d9f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByLectureGetResponse.java @@ -0,0 +1,77 @@ +package org.sopt.makers.operation.web.attendnace.dto.response; + +import static lombok.AccessLevel.*; + +import java.util.List; + +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.member.domain.Member; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AttendanceListByLectureGetResponse( + List attendances, + int totalCount +) { + + public static AttendanceListByLectureGetResponse of(List attendanceList, int totalCount) { + return AttendanceListByLectureGetResponse.builder() + .attendances(attendanceList.stream().map(AttendanceResponse::of).toList()) + .totalCount(totalCount) + .build(); + } + + @Builder(access = PRIVATE) + private record AttendanceResponse( + long attendanceId, + MemberResponse member, + List attendances, + float updatedScore + ) { + private static AttendanceResponse of(Attendance attendance) { + return AttendanceResponse.builder() + .attendanceId(attendance.getId()) + .member(MemberResponse.of(attendance.getMember())) + .attendances(attendance.getSubAttendances().stream().map(SubAttendanceVO::of).toList()) + .updatedScore(attendance.getScore()) + .build(); + } + } + + @Builder(access = PRIVATE) + private record MemberResponse( + long memberId, + String name, + String university, + String part + ) { + private static MemberResponse of(Member member) { + return MemberResponse.builder() + .memberId(member.getId()) + .name(member.getName()) + .university(member.getUniversity()) + .part(member.getPart().getName()) + .build(); + } + } + + @Builder(access = PRIVATE) + private record SubAttendanceVO( + long subAttendanceId, + int round, + AttendanceStatus status, + String updateAt + ) { + private static SubAttendanceVO of(SubAttendance subAttendance) { + return SubAttendanceVO.builder() + .subAttendanceId(subAttendance.getId()) + .round(subAttendance.getSubLecture().getRound()) + .status(subAttendance.getStatus()) + .updateAt(subAttendance.getLastModifiedDate().toString()) + .build(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByMemberGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByMemberGetResponse.java new file mode 100644 index 00000000..7a6a13fc --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/AttendanceListByMemberGetResponse.java @@ -0,0 +1,67 @@ +package org.sopt.makers.operation.web.attendnace.dto.response; + +import static lombok.AccessLevel.*; + +import java.util.List; + +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.member.domain.Member; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AttendanceListByMemberGetResponse( + String name, + float score, + String part, + String university, + String phone, + List lectures +) { + + public static AttendanceListByMemberGetResponse of(Member member, List attendances) { + return AttendanceListByMemberGetResponse.builder() + .name(member.getName()) + .score(member.getScore()) + .part(member.getPart().getName()) + .university(member.getUniversity()) + .phone(member.getPhone()) + .lectures(attendances.stream().map(LectureResponse::of).toList()) + .build(); + } + + @Builder(access = PRIVATE) + record LectureResponse( + String lecture, + float additiveScore, + String status, + List attendances + ) { + + public static LectureResponse of(Attendance attendance) { + return LectureResponse.builder() + .lecture(attendance.getLecture().getName()) + .additiveScore(attendance.getScore()) + .status(attendance.getStatus().getName()) + .attendances(attendance.getSubAttendances().stream().map(AttendanceResponse::of).toList()) + .build(); + } + } + + @Builder(access = PRIVATE) + record AttendanceResponse( + int round, + String status, + String date + ) { + + public static AttendanceResponse of(SubAttendance subAttendance) { + return AttendanceResponse.builder() + .round(subAttendance.getSubLecture().getRound()) + .status(subAttendance.getStatus().getName()) + .date(subAttendance.getLastModifiedDate().toString()) + .build(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/MemberScoreUpdateResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/MemberScoreUpdateResponse.java new file mode 100644 index 00000000..9069918a --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/MemberScoreUpdateResponse.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.web.attendnace.dto.response; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.member.domain.Member; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record MemberScoreUpdateResponse( + float score +) { + + public static MemberScoreUpdateResponse of(Member member) { + return MemberScoreUpdateResponse.builder() + .score(member.getScore()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/SubAttendanceUpdateResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/SubAttendanceUpdateResponse.java new file mode 100644 index 00000000..15f6faba --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/dto/response/SubAttendanceUpdateResponse.java @@ -0,0 +1,22 @@ +package org.sopt.makers.operation.web.attendnace.dto.response; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.attendance.domain.SubAttendance; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record SubAttendanceUpdateResponse( + long subAttendanceId, + AttendanceStatus status +) { + + public static SubAttendanceUpdateResponse of(SubAttendance subAttendance) { + return SubAttendanceUpdateResponse.builder() + .subAttendanceId(subAttendance.getId()) + .status(subAttendance.getStatus()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceService.java new file mode 100644 index 00000000..affade26 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceService.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.web.attendnace.service; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.web.attendnace.dto.request.SubAttendanceUpdateRequest; +import org.sopt.makers.operation.web.attendnace.dto.response.AttendanceListByLectureGetResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.AttendanceListByMemberGetResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.MemberScoreUpdateResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.SubAttendanceUpdateResponse; +import org.springframework.data.domain.Pageable; + +public interface WebAttendanceService { + SubAttendanceUpdateResponse updateSubAttendance(SubAttendanceUpdateRequest request); + AttendanceListByMemberGetResponse getAttendancesByMember(long memberId); + MemberScoreUpdateResponse updateMemberAllScore(long memberId); + AttendanceListByLectureGetResponse getAttendancesByLecture(long lectureId, Part part, Pageable pageable); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceServiceImpl.java new file mode 100644 index 00000000..b64ebca2 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/attendnace/service/WebAttendanceServiceImpl.java @@ -0,0 +1,86 @@ +package org.sopt.makers.operation.web.attendnace.service; + +import static org.sopt.makers.operation.code.failure.AttendanceFailureCode.*; +import static org.sopt.makers.operation.code.failure.LectureFailureCode.*; +import static org.sopt.makers.operation.code.failure.MemberFailureCode.*; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.attendance.repository.attendance.AttendanceRepository; +import org.sopt.makers.operation.attendance.repository.subAttendance.SubAttendanceRepository; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.repository.lecture.LectureRepository; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.sopt.makers.operation.exception.LectureException; +import org.sopt.makers.operation.exception.MemberException; +import org.sopt.makers.operation.web.attendnace.dto.request.SubAttendanceUpdateRequest; +import org.sopt.makers.operation.web.attendnace.dto.response.AttendanceListByMemberGetResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.AttendanceListByLectureGetResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.MemberScoreUpdateResponse; +import org.sopt.makers.operation.web.attendnace.dto.response.SubAttendanceUpdateResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import lombok.val; + +@Slf4j +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class WebAttendanceServiceImpl implements WebAttendanceService { + + private final AttendanceRepository attendanceRepository; + private final SubAttendanceRepository subAttendanceRepository; + private final MemberRepository memberRepository; + private final LectureRepository lectureRepository; + + @Override + @Transactional + public SubAttendanceUpdateResponse updateSubAttendance(SubAttendanceUpdateRequest request) { + val subAttendance = findSubAttendance(request.subAttendanceId()); + subAttendance.updateStatus(request.status()); + return SubAttendanceUpdateResponse.of(subAttendance); + } + + @Override + public AttendanceListByMemberGetResponse getAttendancesByMember(long memberId) { + val member = findMember(memberId); + val attendances = attendanceRepository.findFetchJoin(member); + return AttendanceListByMemberGetResponse.of(member, attendances); + } + + @Override + @Transactional + public MemberScoreUpdateResponse updateMemberAllScore(long memberId) { + val member = findMember(memberId); + member.updateTotalScore(); + return MemberScoreUpdateResponse.of(member); + } + + @Override + public AttendanceListByLectureGetResponse getAttendancesByLecture(long lectureId, Part part, Pageable pageable) { + val lecture = findLecture(lectureId); + val attendances = attendanceRepository.findFetchJoin(lecture, part, pageable); + val totalCount = attendanceRepository.count(lecture, part); + return AttendanceListByLectureGetResponse.of(attendances, totalCount); + } + + private SubAttendance findSubAttendance(long id) { + return subAttendanceRepository.findById(id) + .orElseThrow(() -> new LectureException(INVALID_ATTENDANCE)); + } + + private Member findMember(long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new MemberException(INVALID_MEMBER)); + } + + private Lecture findLecture(long id) { + return lectureRepository.findById(id) + .orElseThrow(() -> new LectureException(INVALID_LECTURE)); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApi.java new file mode 100644 index 00000000..802ff563 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApi.java @@ -0,0 +1,151 @@ +package org.sopt.makers.operation.web.lecture.api; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.web.lecture.dto.request.SubLectureStartRequest; +import org.sopt.makers.operation.web.lecture.dto.request.LectureCreateRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +public interface WebLectureApi { + + @Operation( + summary = "세션 생성 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 생성 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> createLecture(@RequestBody LectureCreateRequest request); + + @Operation( + summary = "세션 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getLectures( + @RequestParam int generation, + @RequestParam(required = false) Part part); + + @Operation( + summary = "세션 단일 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 단일 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getLecture(@PathVariable long lectureId); + + @Operation( + summary = "출석 시작 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "출석 시작 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> startSubLecture(@RequestBody SubLectureStartRequest request); + + @Operation( + summary = "세션 종료 후 출석 점수 갱신 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 종료 후 출석 점수 갱신 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> endLecture(@PathVariable long lectureId); + + @Operation( + summary = "세션 삭제 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 삭제 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> deleteLecture(@PathVariable long lectureId); + + @Operation( + summary = "세션 팝업용 상세 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "세션 팝업용 상세 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getLectureDetail(@PathVariable long lectureId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApiController.java new file mode 100644 index 00000000..33913f1e --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/api/WebLectureApiController.java @@ -0,0 +1,83 @@ +package org.sopt.makers.operation.web.lecture.api; + +import static org.sopt.makers.operation.code.success.web.LectureSuccessCode.*; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.web.lecture.dto.request.SubLectureStartRequest; +import org.sopt.makers.operation.web.lecture.dto.request.LectureCreateRequest; +import org.sopt.makers.operation.web.lecture.service.WebLectureService; +import org.springframework.http.ResponseEntity; +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.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/lectures") +public class WebLectureApiController implements WebLectureApi { + + private final WebLectureService lectureService; + + @Override + @PostMapping + public ResponseEntity> createLecture(@RequestBody LectureCreateRequest request) { + val response = lectureService.createLecture(request); + return ApiResponseUtil.success(SUCCESS_CREATE_LECTURE, response.lectureId()); + } + + @Override + @GetMapping + public ResponseEntity> getLectures( + @RequestParam int generation, + @RequestParam(required = false) Part part + ) { + val response = lectureService.getLectures(generation, part); + return ApiResponseUtil.success(SUCCESS_GET_LECTURES, response); + } + + @Override + @GetMapping("/{lectureId}") + public ResponseEntity> getLecture(@PathVariable long lectureId) { + val response = lectureService.getLecture(lectureId); + return ApiResponseUtil.success(SUCCESS_GET_LECTURE, response); + } + + @Override + @PatchMapping("/attendance") + public ResponseEntity> startSubLecture(SubLectureStartRequest request) { + val response = lectureService.startSubLecture(request); + return ApiResponseUtil.success(SUCCESS_START_ATTENDANCE, response); + } + + @Override + @PatchMapping("/{lectureId}") + public ResponseEntity> endLecture(@PathVariable long lectureId) { + lectureService.endLecture(lectureId); + return ApiResponseUtil.success(SUCCESS_END_LECTURE); + } + + @Override + @DeleteMapping("/{lectureId}") + public ResponseEntity> deleteLecture(@PathVariable long lectureId) { + lectureService.deleteLecture(lectureId); + return ApiResponseUtil.success(SUCCESS_DELETE_LECTURE); + } + + @Override + @GetMapping("/detail/{lectureId}") + public ResponseEntity> getLectureDetail(@PathVariable long lectureId) { + val response = lectureService.getLectureDetail(lectureId); + return ApiResponseUtil.success(SUCCESS_GET_LECTURE, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/LectureCreateRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/LectureCreateRequest.java new file mode 100644 index 00000000..17c6f32c --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/LectureCreateRequest.java @@ -0,0 +1,47 @@ +package org.sopt.makers.operation.web.lecture.dto.request; + +import static lombok.AccessLevel.*; +import static org.sopt.makers.operation.code.failure.LectureFailureCode.*; + +import java.time.LocalDateTime; +import java.time.format.DateTimeParseException; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.lecture.domain.Attribute; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.exception.DateTimeParseCustomException; + +import lombok.Builder; +import lombok.NonNull; + +@Builder(access = PRIVATE) +public record LectureCreateRequest( + @NonNull Part part, + @NonNull String name, + int generation, + String place, + String startDate, + String endDate, + @NonNull Attribute attribute +) { + + public Lecture toEntity() { + return Lecture.builder() + .name(this.name) + .part(this.part) + .generation(this.generation) + .place(this.place) + .startDate(convertLocalDateTime(this.startDate)) + .endDate(convertLocalDateTime(this.endDate)) + .attribute(this.attribute) + .build(); + } + + private LocalDateTime convertLocalDateTime(String date) { + try { + return LocalDateTime.parse(date); + } catch (DateTimeParseException exception) { + throw new DateTimeParseCustomException(INVALID_DATE_PATTERN, date, exception.getErrorIndex()); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/SubLectureStartRequest.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/SubLectureStartRequest.java new file mode 100644 index 00000000..aedeec2c --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/request/SubLectureStartRequest.java @@ -0,0 +1,10 @@ +package org.sopt.makers.operation.web.lecture.dto.request; + +import lombok.NonNull; + +public record SubLectureStartRequest( + long lectureId, + int round, + @NonNull String code +) { +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/AttendanceStatusListResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/AttendanceStatusListResponse.java new file mode 100644 index 00000000..bddfb4bb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/AttendanceStatusListResponse.java @@ -0,0 +1,41 @@ +package org.sopt.makers.operation.web.lecture.dto.response; + +import static lombok.AccessLevel.*; +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; + +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.lecture.domain.Lecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record AttendanceStatusListResponse( + int attendance, + int absent, + int tardy, + int unknown +) { + + public static AttendanceStatusListResponse of(Lecture lecture) { + return AttendanceStatusListResponse.builder() + .attendance(getCount(lecture, ATTENDANCE)) + .absent(getAbsentCount(lecture)) + .tardy(getCount(lecture, TARDY)) + .unknown(getUnknownCount(lecture)) + .build(); + } + + private static int getCount(Lecture lecture, AttendanceStatus status) { + return (int)lecture.getAttendances().stream() + .filter(attendance -> attendance.getStatus().equals(status)) + .count(); + } + + private static int getAbsentCount(Lecture lecture) { + return lecture.isEnd() ? getCount(lecture, ABSENT) : 0; + } + + private static int getUnknownCount(Lecture lecture) { + return lecture.isEnd() ? 0 : getCount(lecture, ABSENT); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureCreateResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureCreateResponse.java new file mode 100644 index 00000000..c47b36f0 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureCreateResponse.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.web.lecture.dto.response; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.lecture.domain.Lecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record LectureCreateResponse( + long lectureId +) { + + public static LectureCreateResponse of(Lecture lecture) { + return LectureCreateResponse.builder() + .lectureId(lecture.getId()) + .build(); + } +} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureDetailResponseDTO.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureDetailGetResponse.java similarity index 57% rename from src/main/java/org/sopt/makers/operation/dto/lecture/LectureDetailResponseDTO.java rename to operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureDetailGetResponse.java index 95042350..e09424c0 100644 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureDetailResponseDTO.java +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureDetailGetResponse.java @@ -1,12 +1,14 @@ -package org.sopt.makers.operation.dto.lecture; +package org.sopt.makers.operation.web.lecture.dto.response; -import org.sopt.makers.operation.entity.lecture.Lecture; +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.lecture.domain.Lecture; import lombok.Builder; -@Builder -public record LectureDetailResponseDTO( - Long lectureId, +@Builder(access = PRIVATE) +public record LectureDetailGetResponse( + long lectureId, String part, String name, String place, @@ -15,8 +17,9 @@ public record LectureDetailResponseDTO( String endDate, int generation ) { - public static LectureDetailResponseDTO of(Lecture lecture) { - return LectureDetailResponseDTO.builder() + + public static LectureDetailGetResponse of(Lecture lecture) { + return LectureDetailGetResponse.builder() .lectureId(lecture.getId()) .part(lecture.getPart().getName()) .name(lecture.getName()) diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureGetResponse.java new file mode 100644 index 00000000..c478c31a --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureGetResponse.java @@ -0,0 +1,64 @@ +package org.sopt.makers.operation.web.lecture.dto.response; + +import static java.util.Objects.*; +import static lombok.AccessLevel.*; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.lecture.domain.Attribute; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.LectureStatus; +import org.sopt.makers.operation.lecture.domain.SubLecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record LectureGetResponse( + long lectureId, + String name, + int generation, + Part part, + Attribute attribute, + List subLectures, + AttendanceStatusListResponse attendances, + LectureStatus status + +) { + + public static LectureGetResponse of(Lecture lecture) { + return LectureGetResponse.builder() + .lectureId(lecture.getId()) + .name(lecture.getName()) + .generation(lecture.getGeneration()) + .part(lecture.getPart()) + .attribute(lecture.getAttribute()) + .subLectures(lecture.getSubLectures().stream().map(SubLectureResponse::of).toList()) + .attendances(AttendanceStatusListResponse.of(lecture)) + .status(lecture.getLectureStatus()) + .build(); + } + + @Builder(access = PRIVATE) + public record SubLectureResponse( + long subLectureId, + int round, + String startAt, + String code + ) { + + private static SubLectureResponse of(SubLecture subLecture) { + return SubLectureResponse.builder() + .subLectureId(subLecture.getId()) + .round(subLecture.getRound()) + .startAt(getStartAt(subLecture.getStartAt())) + .code(subLecture.getCode()) + .build(); + } + + private static String getStartAt(LocalDateTime startAt) { + return nonNull(startAt) ? startAt.toString() : null; + } + } +} \ No newline at end of file diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureListGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureListGetResponse.java new file mode 100644 index 00000000..f1ef0c0f --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/LectureListGetResponse.java @@ -0,0 +1,55 @@ +package org.sopt.makers.operation.web.lecture.dto.response; + +import static lombok.AccessLevel.*; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.lecture.domain.Attribute; +import org.sopt.makers.operation.lecture.domain.Lecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record LectureListGetResponse( + int generation, + List lectures +) { + + public static LectureListGetResponse of(int generation, List lectureList) { + return LectureListGetResponse.builder() + .generation(generation) + .lectures(lectureList.stream().map(LectureResponse::of).toList()) + .build(); + } + + @Builder(access = PRIVATE) + public record LectureResponse( + long lectureId, + String name, + Part partValue, + String partName, + String startDate, + String endDate, + Attribute attributeValue, + String attributeName, + String place, + AttendanceStatusListResponse attendances + ) { + + private static LectureResponse of(Lecture lecture) { + return LectureResponse.builder() + .lectureId(lecture.getId()) + .name(lecture.getName()) + .partValue(lecture.getPart()) + .partName(lecture.getPart().getName()) + .startDate(lecture.getStartDate().toString()) + .endDate(lecture.getEndDate().toString()) + .attributeValue(lecture.getAttribute()) + .attributeName(lecture.getAttribute().getName()) + .place(lecture.getPlace()) + .attendances(AttendanceStatusListResponse.of(lecture)) + .build(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/SubLectureStartResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/SubLectureStartResponse.java new file mode 100644 index 00000000..1e6941b8 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/dto/response/SubLectureStartResponse.java @@ -0,0 +1,22 @@ +package org.sopt.makers.operation.web.lecture.dto.response; + +import static lombok.AccessLevel.*; + +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.SubLecture; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record SubLectureStartResponse( + long lectureId, + long subLectureId +) { + + public static SubLectureStartResponse of(Lecture lecture, SubLecture subLecture) { + return SubLectureStartResponse.builder() + .lectureId(lecture.getId()) + .subLectureId(subLecture.getId()) + .build(); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureService.java new file mode 100644 index 00000000..8fa12dcb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureService.java @@ -0,0 +1,21 @@ +package org.sopt.makers.operation.web.lecture.service; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.web.lecture.dto.request.SubLectureStartRequest; +import org.sopt.makers.operation.web.lecture.dto.request.LectureCreateRequest; +import org.sopt.makers.operation.web.lecture.dto.response.SubLectureStartResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureCreateResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureDetailGetResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureGetResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureListGetResponse; + +public interface WebLectureService { + LectureCreateResponse createLecture(LectureCreateRequest request); + LectureListGetResponse getLectures(int generation, Part part); + LectureGetResponse getLecture(long lectureId); + SubLectureStartResponse startSubLecture(SubLectureStartRequest request); + void endLecture(long lectureId); + void endLectures(); + void deleteLecture(long lectureId); + LectureDetailGetResponse getLectureDetail(long lectureId); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureServiceImpl.java new file mode 100644 index 00000000..c11adc30 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/lecture/service/WebLectureServiceImpl.java @@ -0,0 +1,199 @@ +package org.sopt.makers.operation.web.lecture.service; + +import static org.sopt.makers.operation.code.failure.LectureFailureCode.*; + +import java.util.List; +import java.util.stream.Stream; + +import org.sopt.makers.operation.client.alarm.AlarmSender; +import org.sopt.makers.operation.client.alarm.dto.AlarmSenderRequest; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.attendance.repository.attendance.AttendanceRepository; +import org.sopt.makers.operation.attendance.repository.subAttendance.SubAttendanceRepository; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.SubLecture; +import org.sopt.makers.operation.lecture.repository.lecture.LectureRepository; +import org.sopt.makers.operation.lecture.repository.subLecture.SubLectureRepository; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.sopt.makers.operation.exception.LectureException; +import org.sopt.makers.operation.exception.SubLectureException; +import org.sopt.makers.operation.web.lecture.dto.request.SubLectureStartRequest; +import org.sopt.makers.operation.web.lecture.dto.request.LectureCreateRequest; +import org.sopt.makers.operation.web.lecture.dto.response.SubLectureStartResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureCreateResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureDetailGetResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureGetResponse; +import org.sopt.makers.operation.web.lecture.dto.response.LectureListGetResponse; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class WebLectureServiceImpl implements WebLectureService { + + private final LectureRepository lectureRepository; + private final SubLectureRepository subLectureRepository; + private final AttendanceRepository attendanceRepository; + private final SubAttendanceRepository subAttendanceRepository; + private final MemberRepository memberRepository; + + private final AlarmSender alarmSender; + private final ValueConfig valueConfig; + + @Override + @Transactional + public LectureCreateResponse createLecture(LectureCreateRequest request) { + val savedLecture = saveLecture(request); + createAttendances(request.generation(), request.part(), savedLecture); + return LectureCreateResponse.of(savedLecture); + } + + @Override + public LectureListGetResponse getLectures(int generation, Part part) { + val lectures = lectureRepository.find(generation, part); + return LectureListGetResponse.of(generation, lectures); + } + + @Override + public LectureGetResponse getLecture(long lectureId) { + val lecture = findLecture(lectureId); + return LectureGetResponse.of(lecture); + } + + @Override + @Transactional + public SubLectureStartResponse startSubLecture(SubLectureStartRequest request) { + val lecture = getLectureToStartAttendance(request.lectureId(), request.round()); + val subLecture = getSubLectureToStartAttendance(lecture, request.round()); + subLecture.updateCode(request.code()); + return SubLectureStartResponse.of(lecture, subLecture); + } + + @Override + @Transactional + public void endLecture(long lectureId) { + val lecture = getLectureReadyToEnd(lectureId); + lecture.updateToEnd(); + sendAlarm(lecture); + } + + @Override + @Transactional + public void endLectures() { + val lectures = lectureRepository.findLecturesReadyToEnd(); + lectures.forEach(lecture -> endLecture(lecture.getId())); + } + + @Override + @Transactional + public void deleteLecture(long lectureId) { + val lecture = getLectureToDelete(lectureId); + deleteRelationship(lecture); + lectureRepository.deleteById(lectureId); + } + + @Override + public LectureDetailGetResponse getLectureDetail(long lectureId) { + val lecture = findLecture(lectureId); + return LectureDetailGetResponse.of(lecture); + } + + private Lecture saveLecture(LectureCreateRequest request) { + val savedLecture = lectureRepository.save(request.toEntity()); + createSubLectures(savedLecture); + return savedLecture; + } + + private void createSubLectures(Lecture lecture) { + val maxRound = valueConfig.getSUB_LECTURE_MAX_ROUND(); + Stream.iterate(1, i -> i + 1).limit(maxRound) + .forEach(round -> saveSubLecture(lecture, round)); + } + + private void saveSubLecture(Lecture lecture, int round) { + subLectureRepository.save(new SubLecture(lecture, round)); + } + + private void createAttendances(int generation, Part part, Lecture lecture) { + val members = memberRepository.find(generation, part); + members.forEach(member -> saveAttendance(member, lecture)); + } + + private void saveAttendance(Member member, Lecture lecture) { + val savedAttendance = attendanceRepository.save(new Attendance(member, lecture)); + createSubAttendances(savedAttendance); + } + + private void createSubAttendances(Attendance attendance) { + val subLectures = attendance.getLecture().getSubLectures(); + subLectures.forEach(subLecture -> saveSubAttendance(attendance, subLecture)); + } + + private void saveSubAttendance(Attendance attendance, SubLecture subLecture) { + subAttendanceRepository.save(new SubAttendance(attendance, subLecture)); + } + + private Lecture findLecture(long id) { + return lectureRepository.findById(id) + .orElseThrow(() -> new LectureException(INVALID_LECTURE)); + } + + private Lecture getLectureToStartAttendance(long lectureId, int round) { + val lecture = findLecture(lectureId); + if (lecture.isEnd()) { + throw new LectureException(END_LECTURE); + } else if (round == 2 && lecture.isBefore()) { + throw new LectureException(NOT_STARTED_PRE_ATTENDANCE); + } + return lecture; + } + + private SubLecture getSubLectureToStartAttendance(Lecture lecture, int round) { + return lecture.getSubLectures().stream() + .filter(l -> l.getRound() == round) + .findFirst() + .orElseThrow(() -> new SubLectureException(NO_SUB_LECTURE_EQUAL_ROUND)); + } + + private Lecture getLectureReadyToEnd(long lectureId) { + val lecture = findLecture(lectureId); + if (lecture.isNotYetToEnd()) { + throw new LectureException(NOT_END_TIME_YET); + } + if (lecture.isEnd()) { + throw new LectureException(END_LECTURE); + } + return lecture; + } + + private void sendAlarm(Lecture lecture) { + alarmSender.send(AlarmSenderRequest.of(lecture, valueConfig)); + } + + private Lecture getLectureToDelete(long lectureId) { + val lecture = findLecture(lectureId); + if (lecture.isEnd()) { + restoreAttendances(lecture.getAttendances()); + } + return lecture; + } + + private void restoreAttendances(List attendances) { + attendances.forEach(Attendance::restoreMemberScore); + } + + private void deleteRelationship(Lecture lecture) { + subAttendanceRepository.deleteAllBySubLectureIn(lecture.getSubLectures()); + subLectureRepository.deleteAllByLecture(lecture); + attendanceRepository.deleteByLecture(lecture); + } + +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApi.java b/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApi.java new file mode 100644 index 00000000..9801990d --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApi.java @@ -0,0 +1,35 @@ +package org.sopt.makers.operation.web.member.api; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestParam; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; + +public interface WebMemberApi { + + @Operation( + summary = "멤버 리스트 조회 API", + responses = { + @ApiResponse( + responseCode = "200", + description = "멤버 리스트 조회 성공" + ), + @ApiResponse( + responseCode = "400", + description = "잘못된 요청" + ), + @ApiResponse( + responseCode = "500", + description = "서버 내부 오류" + ) + } + ) + ResponseEntity> getMembers( + @RequestParam(required = false) Part part, + @RequestParam int generation, + Pageable pageable); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApiController.java b/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApiController.java new file mode 100644 index 00000000..0f39709b --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/member/api/WebMemberApiController.java @@ -0,0 +1,36 @@ +package org.sopt.makers.operation.web.member.api; + +import static org.sopt.makers.operation.code.success.web.MemberSuccessCode.*; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.sopt.makers.operation.web.member.service.WebMemberService; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/members") +public class WebMemberApiController implements WebMemberApi { + + private final WebMemberService memberService; + + @Override + @GetMapping("/list") + public ResponseEntity> getMembers( + @RequestParam(required = false) Part part, + @RequestParam int generation, + Pageable pageable + ) { + val response = memberService.getMembers(part, generation, pageable); + return ApiResponseUtil.success(SUCCESS_GET_MEMBERS, response); + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/member/dto/response/MemberListGetResponse.java b/operation-api/src/main/java/org/sopt/makers/operation/web/member/dto/response/MemberListGetResponse.java new file mode 100644 index 00000000..477b28d9 --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/member/dto/response/MemberListGetResponse.java @@ -0,0 +1,71 @@ +package org.sopt.makers.operation.web.member.dto.response; + +import static lombok.AccessLevel.*; +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.AttendanceStatus; +import org.sopt.makers.operation.member.domain.Member; + +import lombok.Builder; + +@Builder(access = PRIVATE) +public record MemberListGetResponse( + List members, + int totalCount +) { + public static MemberListGetResponse of(List memberList, int totalCount) { + return MemberListGetResponse.builder() + .members(memberList.stream().map(MemberResponse::of).toList()) + .totalCount(totalCount) + .build(); + } + + @Builder(access = PRIVATE) + private record MemberResponse( + long id, + String name, + String university, + Part part, + float score, + AttendanceStatusResponse total + ) { + + private static MemberResponse of(Member member) { + return MemberResponse.builder() + .id(member.getId()) + .name(member.getName()) + .university(member.getUniversity()) + .part(member.getPart()) + .score(member.getScore()) + .total(AttendanceStatusResponse.of(member)) + .build(); + } + } + + @Builder(access = PRIVATE) + private record AttendanceStatusResponse( + int attendance, + int absent, + int tardy, + int participate + ) { + + private static AttendanceStatusResponse of(Member member) { + return AttendanceStatusResponse.builder() + .attendance(getCount(member, ATTENDANCE)) + .absent(getCount(member, ABSENT)) + .tardy(getCount(member, TARDY)) + .participate(getCount(member, PARTICIPATE)) + .build(); + } + + private static int getCount(Member member, AttendanceStatus status) { + return (int)member.getAttendances().stream() + .filter(attendance -> attendance.getStatus().equals(status)) + .count(); + } + } +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberService.java b/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberService.java new file mode 100644 index 00000000..2c8edacb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberService.java @@ -0,0 +1,9 @@ +package org.sopt.makers.operation.web.member.service; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.web.member.dto.response.MemberListGetResponse; +import org.springframework.data.domain.Pageable; + +public interface WebMemberService { + MemberListGetResponse getMembers(Part part, int generation, Pageable pageable); +} diff --git a/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberServiceImpl.java b/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberServiceImpl.java new file mode 100644 index 00000000..c02a78eb --- /dev/null +++ b/operation-api/src/main/java/org/sopt/makers/operation/web/member/service/WebMemberServiceImpl.java @@ -0,0 +1,26 @@ +package org.sopt.makers.operation.web.member.service; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.member.repository.MemberRepository; +import org.sopt.makers.operation.web.member.dto.response.MemberListGetResponse; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import lombok.RequiredArgsConstructor; +import lombok.val; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class WebMemberServiceImpl implements WebMemberService { + + private final MemberRepository memberRepository; + + @Override + public MemberListGetResponse getMembers(Part part, int generation, Pageable pageable) { + val members = memberRepository.find(generation, part, pageable); + val totalCount = memberRepository.count(generation, part); + return MemberListGetResponse.of(members, totalCount); + } +} diff --git a/src/main/resources/application.yml b/operation-api/src/main/resources/application.yml similarity index 100% rename from src/main/resources/application.yml rename to operation-api/src/main/resources/application.yml diff --git a/src/main/resources/console-appender.xml b/operation-api/src/main/resources/console-appender.xml similarity index 100% rename from src/main/resources/console-appender.xml rename to operation-api/src/main/resources/console-appender.xml diff --git a/src/main/resources/file-error-appender.xml b/operation-api/src/main/resources/file-error-appender.xml similarity index 90% rename from src/main/resources/file-error-appender.xml rename to operation-api/src/main/resources/file-error-appender.xml index 2746bed3..f5698d26 100644 --- a/src/main/resources/file-error-appender.xml +++ b/operation-api/src/main/resources/file-error-appender.xml @@ -1,6 +1,6 @@ - /home/ubuntu/operation/log/error/error-${BY_DATE}.log + ./log/error/error-${BY_DATE}.log ERROR ACCEPT diff --git a/src/main/resources/file-info-appender.xml b/operation-api/src/main/resources/file-info-appender.xml similarity index 90% rename from src/main/resources/file-info-appender.xml rename to operation-api/src/main/resources/file-info-appender.xml index 600fa4bb..c6422ede 100644 --- a/src/main/resources/file-info-appender.xml +++ b/operation-api/src/main/resources/file-info-appender.xml @@ -1,6 +1,6 @@ - /home/ubuntu/operation/log/info/info-${BY_DATE}.log + ./log/info/info-${BY_DATE}.log INFO ACCEPT diff --git a/src/main/resources/logback-spring.xml b/operation-api/src/main/resources/logback-spring.xml similarity index 100% rename from src/main/resources/logback-spring.xml rename to operation-api/src/main/resources/logback-spring.xml diff --git a/operation-auth/build.gradle b/operation-auth/build.gradle new file mode 100644 index 00000000..8509938c --- /dev/null +++ b/operation-auth/build.gradle @@ -0,0 +1,21 @@ +jar { + enabled = true +} + +bootJar { + enabled = false +} + +dependencies { + implementation project(':operation-common') + + implementation 'org.springframework.boot:spring-boot-starter-security' + + // jwt + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' + implementation group: 'javax.xml.bind', name: 'jaxb-api', version: '2.1' + + implementation 'org.springframework.boot:spring-boot-starter-web' +} \ No newline at end of file diff --git a/operation-auth/src/main/java/org/sopt/makers/operation/AuthRoot.java b/operation-auth/src/main/java/org/sopt/makers/operation/AuthRoot.java new file mode 100644 index 00000000..5046a789 --- /dev/null +++ b/operation-auth/src/main/java/org/sopt/makers/operation/AuthRoot.java @@ -0,0 +1,4 @@ +package org.sopt.makers.operation; + +public interface AuthRoot { +} diff --git a/src/main/java/org/sopt/makers/operation/security/jwt/AdminAuthentication.java b/operation-auth/src/main/java/org/sopt/makers/operation/authentication/AdminAuthentication.java similarity index 89% rename from src/main/java/org/sopt/makers/operation/security/jwt/AdminAuthentication.java rename to operation-auth/src/main/java/org/sopt/makers/operation/authentication/AdminAuthentication.java index f80aa5d7..bfbca60a 100644 --- a/src/main/java/org/sopt/makers/operation/security/jwt/AdminAuthentication.java +++ b/operation-auth/src/main/java/org/sopt/makers/operation/authentication/AdminAuthentication.java @@ -1,11 +1,11 @@ -package org.sopt.makers.operation.security.jwt; +package org.sopt.makers.operation.authentication; + import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import java.util.Collection; public class AdminAuthentication extends UsernamePasswordAuthenticationToken { - public AdminAuthentication(Object principal, Object credentials, Collection authorities) { super(principal, credentials, authorities); } diff --git a/src/main/java/org/sopt/makers/operation/security/config/SecurityConfig.java b/operation-auth/src/main/java/org/sopt/makers/operation/config/SecurityConfig.java similarity index 50% rename from src/main/java/org/sopt/makers/operation/security/config/SecurityConfig.java rename to operation-auth/src/main/java/org/sopt/makers/operation/config/SecurityConfig.java index 1b2625b5..cd21f2fb 100644 --- a/src/main/java/org/sopt/makers/operation/security/config/SecurityConfig.java +++ b/operation-auth/src/main/java/org/sopt/makers/operation/config/SecurityConfig.java @@ -1,12 +1,12 @@ -package org.sopt.makers.operation.security.config; +package org.sopt.makers.operation.config; import lombok.RequiredArgsConstructor; import lombok.val; -import org.sopt.makers.operation.security.jwt.JwtAuthenticationFilter; -import org.sopt.makers.operation.security.jwt.JwtExceptionFilter; -import org.springframework.beans.factory.annotation.Value; +import org.sopt.makers.operation.filter.JwtAuthenticationFilter; +import org.sopt.makers.operation.filter.JwtExceptionFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; @@ -14,6 +14,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -24,56 +25,58 @@ public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; private final JwtExceptionFilter jwtExceptionFilter; - - @Value("${admin.url.prod}") - private String ADMIN_PROD_URL; - - @Value("${admin.url.dev}") - private String ADMIN_DEV_URL; - - @Value("${admin.url.prod_legacy}") - private String ADMIN_PROD_URL_LEGACY; - - @Value("${admin.url.dev_legacy}") - private String ADMIN_DEV_URL_LEGACY; - - @Value("${admin.url.local}") - private String ADMIN_LOCAL_URL; + private final ValueConfig valueConfig; @Bean public static PasswordEncoder passwordEncoder() { return PasswordEncoderFactories.createDelegatingPasswordEncoder(); } + @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - return http.antMatcher("/**") - .httpBasic().disable() + @Profile("dev") + public SecurityFilterChain filterChainDev(HttpSecurity http) throws Exception { + http.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests + .requestMatchers(new AntPathRequestMatcher("/swagger-ui/**")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/v3/**")).permitAll() + ); + setHttp(http); + return http.build(); + } + + @Bean + @Profile("prod") + public SecurityFilterChain filterChainProd(HttpSecurity http) throws Exception { + setHttp(http); + return http.build(); + } + + private void setHttp(HttpSecurity http) throws Exception { + http.httpBasic().disable() .csrf().disable() .formLogin().disable() .cors().configurationSource(corsConfigurationSource()) .and() - .authorizeRequests() - .antMatchers("/api/v1/auth/**","/exception/**").permitAll() - .and() - .authorizeRequests() - .antMatchers("/api/v1/**", "/swagger-ui/**").authenticated() - .and() - .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) + .authorizeHttpRequests(authorizeHttpRequests -> + authorizeHttpRequests + .requestMatchers(new AntPathRequestMatcher("/api/v1/auth/*")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/api/v1/test/**")).permitAll() + .requestMatchers(new AntPathRequestMatcher("/error")).permitAll() + .anyRequest().authenticated()) + .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) - .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class) - .build(); + .addFilterBefore(jwtExceptionFilter, JwtAuthenticationFilter.class); } @Bean public CorsConfigurationSource corsConfigurationSource() { val configuration = new CorsConfiguration(); - configuration.addAllowedOrigin(ADMIN_PROD_URL); - configuration.addAllowedOrigin(ADMIN_DEV_URL); - configuration.addAllowedOrigin(ADMIN_LOCAL_URL); - configuration.addAllowedOrigin(ADMIN_PROD_URL_LEGACY); - configuration.addAllowedOrigin(ADMIN_DEV_URL_LEGACY); + configuration.addAllowedOrigin(valueConfig.getADMIN_PROD_URL()); + configuration.addAllowedOrigin(valueConfig.getADMIN_DEV_URL()); + configuration.addAllowedOrigin(valueConfig.getADMIN_PROD_URL_LEGACY()); + configuration.addAllowedOrigin(valueConfig.getADMIN_DEV_URL_LEGACY()); + configuration.addAllowedOrigin(valueConfig.getADMIN_LOCAL_URL()); configuration.addAllowedHeader("*"); configuration.addAllowedMethod("*"); configuration.setAllowCredentials(true); diff --git a/src/main/java/org/sopt/makers/operation/security/jwt/JwtAuthenticationFilter.java b/operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtAuthenticationFilter.java similarity index 78% rename from src/main/java/org/sopt/makers/operation/security/jwt/JwtAuthenticationFilter.java rename to operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtAuthenticationFilter.java index 064af05a..ff8a552c 100644 --- a/src/main/java/org/sopt/makers/operation/security/jwt/JwtAuthenticationFilter.java +++ b/operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtAuthenticationFilter.java @@ -1,15 +1,19 @@ -package org.sopt.makers.operation.security.jwt; +package org.sopt.makers.operation.filter; +import static org.sopt.makers.operation.code.failure.TokenFailureCode.*; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.val; -import org.sopt.makers.operation.common.ExceptionMessage; import org.sopt.makers.operation.exception.TokenException; +import org.sopt.makers.operation.jwt.JwtTokenProvider; +import org.sopt.makers.operation.jwt.JwtTokenType; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; -import javax.servlet.*; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import java.io.IOException; import org.springframework.stereotype.Component; @@ -25,7 +29,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { val uri = request.getRequestURI(); - if ((uri.startsWith("/api/v1")) && !uri.contains("auth")) { + if ((uri.startsWith("/api/v1")) && !uri.contains("auth") && !uri.contains("test")) { val token = jwtTokenProvider.resolveToken(request); val jwtTokenType = validateTokenType(request); @@ -42,7 +46,7 @@ public void doFilterInternal(HttpServletRequest request, HttpServletResponse res private void checkJwtAvailable (String token, JwtTokenType jwtTokenType) { if (token == null || !jwtTokenProvider.validateTokenExpiration(token, jwtTokenType)) { - throw new TokenException(ExceptionMessage.INVALID_AUTH_REQUEST.getName()); + throw new TokenException(INVALID_TOKEN); } } diff --git a/operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtExceptionFilter.java b/operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtExceptionFilter.java new file mode 100644 index 00000000..a0517f11 --- /dev/null +++ b/operation-auth/src/main/java/org/sopt/makers/operation/filter/JwtExceptionFilter.java @@ -0,0 +1,46 @@ +package org.sopt.makers.operation.filter; + +import org.sopt.makers.operation.code.failure.FailureCode; +import org.sopt.makers.operation.dto.BaseResponse; +import org.sopt.makers.operation.exception.TokenException; +import org.sopt.makers.operation.util.ApiResponseUtil; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.val; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; + +@Component +public class JwtExceptionFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest httpServletRequest, + HttpServletResponse httpServletResponse, + FilterChain filterChain + ) throws ServletException, IOException { + try { + filterChain.doFilter(httpServletRequest, httpServletResponse); + } catch(TokenException e) { + val objectMapper = new ObjectMapper(); + val jsonResponse = objectMapper.writeValueAsString(getFailureResponse(e.getFailureCode())); + + httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); + httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); + httpServletResponse.setCharacterEncoding("UTF-8"); + httpServletResponse.getWriter().write(jsonResponse); + } + } + + private ResponseEntity> getFailureResponse(FailureCode failureCode) { + return ApiResponseUtil.failure(failureCode); + } +} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenProvider.java b/operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenProvider.java similarity index 80% rename from src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenProvider.java rename to operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenProvider.java index ae7d974a..ae249d01 100644 --- a/src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenProvider.java +++ b/operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenProvider.java @@ -1,23 +1,25 @@ -package org.sopt.makers.operation.security.jwt; +package org.sopt.makers.operation.jwt; + +import static org.sopt.makers.operation.code.failure.TokenFailureCode.*; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.SignatureException; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.val; -import org.sopt.makers.operation.common.ExceptionMessage; +import org.sopt.makers.operation.authentication.AdminAuthentication; import org.sopt.makers.operation.exception.TokenException; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import javax.crypto.spec.SecretKeySpec; -import javax.servlet.http.HttpServletRequest; import javax.xml.bind.DatatypeConverter; + import java.nio.charset.StandardCharsets; import java.time.LocalDateTime; import java.time.ZoneId; @@ -27,7 +29,6 @@ import java.util.Map; @RequiredArgsConstructor -@Transactional(readOnly = true) @Service public class JwtTokenProvider { @@ -40,7 +41,7 @@ public class JwtTokenProvider { @Value("${spring.jwt.secretKey.app}") private String appAccessSecretKey; - public String generateAccessToken(Authentication authentication) { + public String generateAccessToken(final Authentication authentication) { val encodedKey = encodeKey(accessSecretKey); val secretKeyBytes = DatatypeConverter.parseBase64Binary(encodedKey); val accessKey = new SecretKeySpec(secretKeyBytes, SignatureAlgorithm.HS256.getJcaName()); @@ -53,7 +54,7 @@ public String generateAccessToken(Authentication authentication) { .compact(); } - public String generateRefreshToken(Authentication authentication) { + public String generateRefreshToken(final Authentication authentication) { val encodedKey = encodeKey(refreshSecretKey); val secretKeyBytes = DatatypeConverter.parseBase64Binary(encodedKey); val refreshKey = new SecretKeySpec(secretKeyBytes, SignatureAlgorithm.HS256.getJcaName()); @@ -70,10 +71,8 @@ public boolean validateTokenExpiration(String token, JwtTokenType jwtTokenType) try { getClaimsFromToken(token, jwtTokenType); return true; - } catch (ExpiredJwtException e) { - throw new TokenException(ExceptionMessage.EXPIRED_TOKEN.getName()); - } catch (SignatureException e) { - throw new TokenException(ExceptionMessage.INVALID_SIGNATURE.getName()); + } catch (ExpiredJwtException | SignatureException e) { + return false; } } @@ -89,10 +88,8 @@ public Long getPlayGroundId(String token, JwtTokenType jwtTokenType) { val claims = getClaimsFromToken(token, jwtTokenType); return Long.parseLong(claims.get("playgroundId").toString()); - } catch (ExpiredJwtException e) { - throw new TokenException(ExceptionMessage.EXPIRED_TOKEN.getName()); - } catch (SignatureException e) { - throw new TokenException(ExceptionMessage.INVALID_SIGNATURE.getName()); + } catch (ExpiredJwtException | SignatureException e) { + throw new TokenException(INVALID_TOKEN); } } @@ -101,10 +98,8 @@ public Long getId(String token, JwtTokenType jwtTokenType) { val claims = getClaimsFromToken(token, jwtTokenType); return Long.parseLong(claims.getSubject()); - } catch (ExpiredJwtException e) { - throw new TokenException(ExceptionMessage.EXPIRED_TOKEN.getName()); - } catch (SecurityException e) { - throw new TokenException(ExceptionMessage.INVALID_SIGNATURE.getName()); + } catch (ExpiredJwtException | SignatureException e) { + throw new TokenException(INVALID_TOKEN); } } @@ -143,7 +138,7 @@ private LocalDateTime setExpireTime(LocalDateTime now, JwtTokenType jwtTokenType return switch (jwtTokenType) { case ACCESS_TOKEN -> now.plusHours(5); case REFRESH_TOKEN -> now.plusWeeks(2); - case APP_ACCESS_TOKEN -> throw new TokenException(ExceptionMessage.INVALID_TOKEN.getName()); + case APP_ACCESS_TOKEN -> throw new TokenException(INVALID_TOKEN); }; } diff --git a/src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenType.java b/operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenType.java similarity index 62% rename from src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenType.java rename to operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenType.java index 753724db..385ec839 100644 --- a/src/main/java/org/sopt/makers/operation/security/jwt/JwtTokenType.java +++ b/operation-auth/src/main/java/org/sopt/makers/operation/jwt/JwtTokenType.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.security.jwt; +package org.sopt.makers.operation.jwt; public enum JwtTokenType { ACCESS_TOKEN, REFRESH_TOKEN, APP_ACCESS_TOKEN diff --git a/operation-common/build.gradle b/operation-common/build.gradle new file mode 100644 index 00000000..4712a383 --- /dev/null +++ b/operation-common/build.gradle @@ -0,0 +1,11 @@ +jar { + enabled = true +} + +bootJar { + enabled = false +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' +} \ No newline at end of file diff --git a/operation-common/src/main/java/org/sopt/makers/operation/CommonRoot.java b/operation-common/src/main/java/org/sopt/makers/operation/CommonRoot.java new file mode 100644 index 00000000..c2e06e1f --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/CommonRoot.java @@ -0,0 +1,4 @@ +package org.sopt.makers.operation; + +public interface CommonRoot { +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AlarmFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AlarmFailureCode.java new file mode 100644 index 00000000..ce9ae1ac --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AlarmFailureCode.java @@ -0,0 +1,21 @@ +package org.sopt.makers.operation.code.failure; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum AlarmFailureCode implements FailureCode { + FAIL_SEND_ALARM(BAD_REQUEST, "알림 전송에 실패하였습니다."), + SENT_ALARM(BAD_REQUEST, "전송된 알림입니다."), + INVALID_ALARM(NOT_FOUND, "알림이 존재하지 않습니다."), + FAIL_INACTIVE_USERS(BAD_REQUEST, "비활동 유저 불러오기에 실패하였습니다."), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AttendanceFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AttendanceFailureCode.java new file mode 100644 index 00000000..d39fe3d1 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/AttendanceFailureCode.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.code.failure; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum AttendanceFailureCode implements FailureCode { + INVALID_ATTENDANCE(NOT_FOUND, "존재하지 않는 출석 세션입니다."), + INVALID_SUB_ATTENDANCE(NOT_FOUND, "존재하지 않는 N차 출석입니다."), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/FailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/FailureCode.java new file mode 100644 index 00000000..5363d453 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/FailureCode.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.code.failure; + +import org.springframework.http.HttpStatus; + +public interface FailureCode { + HttpStatus getStatus(); + String getMessage(); +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/LectureFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/LectureFailureCode.java new file mode 100644 index 00000000..c23d10a8 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/LectureFailureCode.java @@ -0,0 +1,30 @@ +package org.sopt.makers.operation.code.failure; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum LectureFailureCode implements FailureCode { + INVALID_DATE_PATTERN(BAD_REQUEST, "유효하지 않은 날짜 형식입니다."), + INVALID_SUB_LECTURE(NOT_FOUND, "존재하지 않는 세션입니다."), + INVALID_CODE(BAD_REQUEST, "코드가 일치하지 않아요!"), + NOT_STARTED_NTH_ATTENDANCE(BAD_REQUEST, "차 출석 시작 전입니다."), + ENDED_ATTENDANCE(BAD_REQUEST, "차 출석이 이미 종료되었습니다."), + INVALID_LECTURE(NOT_FOUND, "존재하지 않는 세션입니다."), + NOT_END_TIME_YET(BAD_REQUEST, "세션 종료 시간이 지나지 않았습니다."), + NO_SUB_LECTURE_EQUAL_ROUND(NOT_FOUND, "해당 라운드와 일치하는 출석 세션이 없습니다."), + END_LECTURE(BAD_REQUEST, "이미 종료된 세션입니다."), + NOT_STARTED_PRE_ATTENDANCE(BAD_REQUEST, "이전의 출석체크가 시작되지 않았습니다."), + INVALID_COUNT_SESSION(BAD_REQUEST, "세션의 개수가 올바르지 않습니다."), + NO_SESSION(NOT_FOUND, "오늘 세션이 없습니다."), + NOT_STARTED_ATTENDANCE(BAD_REQUEST, "출석 시작 전입니다."), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/MemberFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/MemberFailureCode.java new file mode 100644 index 00000000..fcb0ef99 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/MemberFailureCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.failure; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum MemberFailureCode implements FailureCode { + INVALID_MEMBER(NOT_FOUND, "존재하지 않는 회원입니다.") + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/ScheduleFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/ScheduleFailureCode.java new file mode 100644 index 00000000..7d4ffca5 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/ScheduleFailureCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.failure; + +import static org.springframework.http.HttpStatus.*; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum ScheduleFailureCode implements FailureCode { + INVALID_DATE_PERM(BAD_REQUEST, "조회할 날짜 기간은 50일을 넘길 수 없습니다."); + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/TokenFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/TokenFailureCode.java new file mode 100644 index 00000000..5713fec5 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/TokenFailureCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.failure; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@RequiredArgsConstructor +@Getter +public enum TokenFailureCode implements FailureCode { + EMPTY_TOKEN(BAD_REQUEST, "빈 토큰입니다."), + INVALID_TOKEN(BAD_REQUEST, "유효하지 않은 토큰입니다.") + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/admin/AdminFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/admin/AdminFailureCode.java new file mode 100644 index 00000000..eeadbd2d --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/admin/AdminFailureCode.java @@ -0,0 +1,22 @@ +package org.sopt.makers.operation.code.failure.admin; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum AdminFailureCode implements FailureCode { + DUPLICATED_EMAIL(BAD_REQUEST,"중복되는 이메일입니다."), + INVALID_EMAIL(BAD_REQUEST,"이메일이 존재하지 않습니다."), + INVALID_PASSWORD(BAD_REQUEST,"비밀번호가 일치하지 않습니다."), + NOT_APPROVED_ACCOUNT(BAD_REQUEST,"승인되지 않은 계정입니다."), + INVALID_REFRESH_TOKEN(BAD_REQUEST,"유효하지 않은 리프레시 토큰입니다."), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/attendance/AttendanceFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/attendance/AttendanceFailureCode.java new file mode 100644 index 00000000..1c35e1ed --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/attendance/AttendanceFailureCode.java @@ -0,0 +1,17 @@ +package org.sopt.makers.operation.code.failure.attendance; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum AttendanceFailureCode implements FailureCode { + INVALID_ATTENDANCE(BAD_REQUEST, "존재하지 않는 출석 세션입니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/lecture/LectureFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/lecture/LectureFailureCode.java new file mode 100644 index 00000000..ef9d4d23 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/lecture/LectureFailureCode.java @@ -0,0 +1,26 @@ +package org.sopt.makers.operation.code.failure.lecture; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum LectureFailureCode implements FailureCode { + NOT_STARTED_NTH_ATTENDANCE(BAD_REQUEST, "차 출석 시작 전입니다."), + INVALID_ATTENDANCE(BAD_REQUEST,"존재하지 않는 출석 세션입니다."), + ENDED_ATTENDANCE(BAD_REQUEST, "차 출석이 이미 종료되었습니다."), + ENDED_FIRST_ATTENDANCE(BAD_REQUEST, "1차 출석이 이미 종료되었습니다."), + ENDED_SECOND_ATTENDANCE(BAD_REQUEST, "차 출석이 이미 종료되었습니다."), + INVALID_COUNT_SESSION(BAD_REQUEST,"세션의 개수가 올바르지 않습니다."), + INVALID_LECTURE(BAD_REQUEST,"존재하지 않는 세션입니다."), + NO_SESSION(BAD_REQUEST,"오늘 세션이 없습니다."), + NOT_STARTED_ATTENDANCE(BAD_REQUEST,"출석 시작 전입니다."), + END_LECTURE(BAD_REQUEST,"이미 종료된 세션입니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/member/memberFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/member/memberFailureCode.java new file mode 100644 index 00000000..7e9fc0bc --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/member/memberFailureCode.java @@ -0,0 +1,17 @@ +package org.sopt.makers.operation.code.failure.member; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum memberFailureCode implements FailureCode { + INVALID_MEMBER(BAD_REQUEST, "존재하지 않는 회원입니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subAttendance/subAttendanceFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subAttendance/subAttendanceFailureCode.java new file mode 100644 index 00000000..b024a7e7 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subAttendance/subAttendanceFailureCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.failure.subAttendance; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum subAttendanceFailureCode implements FailureCode { + INVALID_SUB_LECTURE(BAD_REQUEST, "존재하지 않는 세션입니다."), + INVALID_CODE(BAD_REQUEST, "코드가 일치하지 않아요!"); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subLecture/subLectureFailureCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subLecture/subLectureFailureCode.java new file mode 100644 index 00000000..5da961ab --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/failure/subLecture/subLectureFailureCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.failure.subLecture; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.failure.FailureCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + +@Getter +@RequiredArgsConstructor +public enum subLectureFailureCode implements FailureCode { + INVALID_SUB_ATTENDANCE(BAD_REQUEST, "존재하지 않는 N차 출석입니다."), + NO_SUB_LECTURE_EQUAL_ROUND(BAD_REQUEST,"해당 라운드와 일치하는 출석 세션이 없습니다."); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/SuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/SuccessCode.java new file mode 100644 index 00000000..e4575f0e --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/SuccessCode.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.code.success; + +import org.springframework.http.HttpStatus; + +public interface SuccessCode { + HttpStatus getStatus(); + String getMessage(); +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/AttendanceSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/AttendanceSuccessCode.java new file mode 100644 index 00000000..67b66bd5 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/AttendanceSuccessCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.success.app; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum AttendanceSuccessCode implements SuccessCode { + SUCCESS_ATTEND(OK, "출석 성공"); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/LectureSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/LectureSuccessCode.java new file mode 100644 index 00000000..c5ac68a8 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/LectureSuccessCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.success.app; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.OK; + +@Getter +@RequiredArgsConstructor +public enum LectureSuccessCode implements SuccessCode { + SUCCESS_SINGLE_GET_LECTURE(OK,"세션 조회 성공"), + SUCCESS_GET_LECTURE_ROUND(OK,"출석 차수 조회 성공"); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/MemberSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/MemberSuccessCode.java new file mode 100644 index 00000000..19513fba --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/MemberSuccessCode.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.code.success.app; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.OK; + +@Getter +@RequiredArgsConstructor +public enum MemberSuccessCode implements SuccessCode { + SUCCESS_GET_TOTAL_ATTENDANCE(OK,"전체 출석정보 조회 성공"), + SUCCESS_GET_ATTENDANCE_SCORE(OK,"출석 점수 조회 성공"); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/ScheduleSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/ScheduleSuccessCode.java new file mode 100644 index 00000000..a6cf8515 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/app/ScheduleSuccessCode.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.code.success.app; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum ScheduleSuccessCode implements SuccessCode { + SUCCESS_GET_SCHEDULES(OK, "일정 리스트 조회 성공"), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AdminSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AdminSuccessCode.java new file mode 100644 index 00000000..cf6d82e3 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AdminSuccessCode.java @@ -0,0 +1,20 @@ +package org.sopt.makers.operation.code.success.web; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import static org.springframework.http.HttpStatus.OK; + +@Getter +@RequiredArgsConstructor +public enum AdminSuccessCode implements SuccessCode { + SUCCESS_ATTEND(OK, "출석 성공"), + SUCCESS_SIGN_UP(OK,"회원 가입 성공"), + SUCCESS_LOGIN_UP(OK,"로그인 성공"), + SUCCESS_GET_REFRESH_TOKEN(OK,"토큰 재발급 성공"); + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AlarmSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AlarmSuccessCode.java new file mode 100644 index 00000000..f1526ee2 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AlarmSuccessCode.java @@ -0,0 +1,23 @@ +package org.sopt.makers.operation.code.success.web; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum AlarmSuccessCode implements SuccessCode { + SUCCESS_SEND_ALARM(OK, "알림 전송 성공"), + SUCCESS_CREATE_ALARM(CREATED, "알림 생성 성공"), + SUCCESS_GET_ALARMS(OK, "알림 리스트 조회 성공"), + SUCCESS_GET_ALARM(OK, "알림 상세 조회 성공"), + SUCCESS_DELETE_ALARM(OK, "알림 삭제 성공"), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AttendanceSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AttendanceSuccessCode.java new file mode 100644 index 00000000..21c8e634 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/AttendanceSuccessCode.java @@ -0,0 +1,22 @@ +package org.sopt.makers.operation.code.success.web; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum AttendanceSuccessCode implements SuccessCode { + SUCCESS_UPDATE_ATTENDANCE_STATUS(OK, "출석 상태 변경 성공"), + SUCCESS_GET_MEMBER_ATTENDANCE(OK, "회원 출석 정보 조회 성공"), + SUCCESS_UPDATE_MEMBER_SCORE(OK, "회원 출석 점수 갱신 성공"), + SUCCESS_GET_ATTENDANCES(OK, "출석 리스트 조회 성공"), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/LectureSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/LectureSuccessCode.java new file mode 100644 index 00000000..beeee546 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/LectureSuccessCode.java @@ -0,0 +1,26 @@ +package org.sopt.makers.operation.code.success.web; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum LectureSuccessCode implements SuccessCode { + SUCCESS_CREATE_LECTURE(CREATED, "세션 생성 성공"), + SUCCESS_GET_LECTURES(OK, "세션 리스트 조회 성공"), + SUCCESS_GET_LECTURE(OK, "세션 상세 조회 성공"), + SUCCESS_START_ATTENDANCE(CREATED, "출석 시작 성공"), + SUCCESS_GET_MEMBERS(OK, "유저 리스트 조회 성공"), + SUCCESS_DELETE_LECTURE(OK, "세션 삭제 성공"), + SUCCESS_UPDATE_MEMBER_SCORE(OK, "회원 출석 점수 갱신 성공"), + SUCCESS_END_LECTURE(OK, "세션 종료 성공"), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/MemberSuccessCode.java b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/MemberSuccessCode.java new file mode 100644 index 00000000..1bb0ea65 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/code/success/web/MemberSuccessCode.java @@ -0,0 +1,19 @@ +package org.sopt.makers.operation.code.success.web; + +import static org.springframework.http.HttpStatus.*; + +import org.sopt.makers.operation.code.success.SuccessCode; +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum MemberSuccessCode implements SuccessCode { + SUCCESS_GET_MEMBERS(OK, "유저 리스트 조회 성공"), + ; + + private final HttpStatus status; + private final String message; +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java new file mode 100644 index 00000000..cdeaa8a7 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/config/ValueConfig.java @@ -0,0 +1,67 @@ +package org.sopt.makers.operation.config; + +import java.util.Arrays; +import java.util.List; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +import lombok.Getter; + +@Configuration +@Getter +public class ValueConfig { + + @Value("${sopt.alarm.message.title_end}") + private String ALARM_MESSAGE_TITLE; + @Value("${sopt.alarm.message.content_end}") + private String ALARM_MESSAGE_CONTENT; + @Value("${sopt.current.generation}") + private int GENERATION; + @Value("${admin.url.prod}") + private String ADMIN_PROD_URL; + @Value("${admin.url.dev}") + private String ADMIN_DEV_URL; + @Value("${admin.url.prod_legacy}") + private String ADMIN_PROD_URL_LEGACY; + @Value("${admin.url.dev_legacy}") + private String ADMIN_DEV_URL_LEGACY; + @Value("${admin.url.local}") + private String ADMIN_LOCAL_URL; + @Value("${notification.key}") + private String NOTIFICATION_KEY; + @Value("${notification.url}") + private String NOTIFICATION_URL; + @Value("${sopt.makers.playground.server}") + private String playGroundURI; + @Value("${sopt.makers.playground.token}") + private String playGroundToken; + + private final int SUB_LECTURE_MAX_ROUND = 2; + private final int MAX_LECTURE_COUNT = 2; + private final String ETC_MESSAGE = "출석 점수가 반영되지 않아요."; + private final String SEMINAR_MESSAGE = ""; + private final String EVENT_MESSAGE = "행사도 참여하고, 출석점수도 받고, 일석이조!"; + private final String SWAGGER_URI = "/swagger-ui/**"; + private final int ATTENDANCE_MINUTE = 10; + private final int MIN_SCHEDULE_DURATION = 1; + private final int MAX_SCHEDULE_DURATION = 50; + private final int DAY_DURATION = 1; + private final int TWO_DAYS_DURATION = 2; + private final int HACKATHON_LECTURE_START_HOUR = 16; + + private final List APP_LINK_LIST = Arrays.asList( + "home", + "home/notification", + "home/mypage", + "home/attendance", + "home/attendance/attendance-modal", + "home/soptamp", + "home/soptamp/entire-ranking", + "home/soptamp/current-generation-ranking" + ); + private final List WEB_LINK_LIST = Arrays.asList( + "https://playground.sopt.org/members", + "https://playground.sopt.org/group" + ); +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/dto/BaseResponse.java b/operation-common/src/main/java/org/sopt/makers/operation/dto/BaseResponse.java new file mode 100644 index 00000000..4fb2c888 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/dto/BaseResponse.java @@ -0,0 +1,32 @@ +package org.sopt.makers.operation.dto; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.*; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import lombok.AccessLevel; +import lombok.Builder; + +@Builder(access = AccessLevel.PRIVATE) +public record BaseResponse ( + boolean success, + String message, + @JsonInclude(value = NON_NULL) + T data +) { + + public static BaseResponse of(String message, T data) { + return BaseResponse.builder() + .success(true) + .message(message) + .data(data) + .build(); + } + + public static BaseResponse of(boolean isSuccess, String message) { + return BaseResponse.builder() + .success(isSuccess) + .message(message) + .build(); + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java new file mode 100644 index 00000000..d19abe71 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class AdminFailureException extends RuntimeException { + + private final FailureCode failureCode; + + public AdminFailureException(FailureCode failureCode) { + super("[AuthFailureException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/AlarmException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/AlarmException.java new file mode 100644 index 00000000..c31cf79b --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/AlarmException.java @@ -0,0 +1,17 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.AlarmFailureCode; +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class AlarmException extends RuntimeException { + + private final FailureCode failureCode; + + public AlarmException(AlarmFailureCode failureCode) { + super("[AlarmException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/AttendanceException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/AttendanceException.java new file mode 100644 index 00000000..6b374182 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/AttendanceException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class AttendanceException extends RuntimeException { + + private final FailureCode failureCode; + + public AttendanceException(FailureCode failureCode) { + super("[AttendanceException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/DateTimeParseCustomException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/DateTimeParseCustomException.java new file mode 100644 index 00000000..3a129a4e --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/DateTimeParseCustomException.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.exception; + +import java.time.format.DateTimeParseException; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class DateTimeParseCustomException extends DateTimeParseException { + + private final FailureCode failureCode; + + public DateTimeParseCustomException(FailureCode failureCode, String input, int index) { + super("[DateTimeParseException] : " + failureCode.getMessage(), input, index); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/LectureException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/LectureException.java new file mode 100644 index 00000000..5e05d9e0 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/LectureException.java @@ -0,0 +1,32 @@ +package org.sopt.makers.operation.exception; + +import static org.sopt.makers.operation.code.failure.lecture.LectureFailureCode.*; + +import org.sopt.makers.operation.code.failure.FailureCode; +import org.sopt.makers.operation.code.failure.lecture.LectureFailureCode; + +import lombok.Getter; + +@Getter +public class LectureException extends RuntimeException { + + private final FailureCode failureCode; + + public LectureException(FailureCode failureCode) { + super("[LectureException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } + + public LectureException(FailureCode failureCode, int round) { + super("[LectureException] : " + failureCode.getMessage()); + this.failureCode = getFailureCodeByRound(round); + } + + private LectureFailureCode getFailureCodeByRound(int round) { + return switch (round) { + case 1 -> ENDED_FIRST_ATTENDANCE; + case 2 -> ENDED_SECOND_ATTENDANCE; + default-> ENDED_ATTENDANCE; + }; + } +} \ No newline at end of file diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/MemberException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/MemberException.java new file mode 100644 index 00000000..7e0209f6 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/MemberException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class MemberException extends RuntimeException { + + private final FailureCode failureCode; + + public MemberException(FailureCode failureCode) { + super("[MemberException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/ScheduleException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/ScheduleException.java new file mode 100644 index 00000000..579e2506 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/ScheduleException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class ScheduleException extends RuntimeException { + + private final FailureCode failureCode; + + public ScheduleException(FailureCode failureCode) { + super("[ScheduleException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java new file mode 100644 index 00000000..881dc269 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class SubLectureException extends RuntimeException { + + private final FailureCode failureCode; + + public SubLectureException(FailureCode failureCode) { + super("[SubLectureException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/exception/TokenException.java b/operation-common/src/main/java/org/sopt/makers/operation/exception/TokenException.java new file mode 100644 index 00000000..dc555a28 --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/exception/TokenException.java @@ -0,0 +1,16 @@ +package org.sopt.makers.operation.exception; + +import org.sopt.makers.operation.code.failure.FailureCode; + +import lombok.Getter; + +@Getter +public class TokenException extends RuntimeException { + + private final FailureCode failureCode; + + public TokenException(FailureCode failureCode) { + super("[TokenException] : " + failureCode.getMessage()); + this.failureCode = failureCode; + } +} diff --git a/operation-common/src/main/java/org/sopt/makers/operation/util/ApiResponseUtil.java b/operation-common/src/main/java/org/sopt/makers/operation/util/ApiResponseUtil.java new file mode 100644 index 00000000..aa70a45e --- /dev/null +++ b/operation-common/src/main/java/org/sopt/makers/operation/util/ApiResponseUtil.java @@ -0,0 +1,35 @@ +package org.sopt.makers.operation.util; + +import org.sopt.makers.operation.code.failure.FailureCode; +import org.sopt.makers.operation.code.success.SuccessCode; +import org.sopt.makers.operation.dto.BaseResponse; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; + +public interface ApiResponseUtil { + + static ResponseEntity> success(SuccessCode code, T data) { + return ResponseEntity + .status(code.getStatus()) + .body(BaseResponse.of(code.getMessage(), data)); + } + + static ResponseEntity> success(SuccessCode code) { + return ResponseEntity + .status(code.getStatus()) + .body(BaseResponse.of(true, code.getMessage())); + } + + static ResponseEntity> success(SuccessCode code, HttpHeaders headers, T data) { + return ResponseEntity + .status(code.getStatus()) + .headers(headers) + .body(BaseResponse.of(code.getMessage(), data)); + } + + static ResponseEntity> failure(FailureCode code) { + return ResponseEntity + .status(code.getStatus()) + .body(BaseResponse.of(false, code.getMessage())); + } +} diff --git a/operation-domain/build.gradle b/operation-domain/build.gradle new file mode 100644 index 00000000..2c0374ea --- /dev/null +++ b/operation-domain/build.gradle @@ -0,0 +1,28 @@ +jar { + enabled = true +} + +bootJar { + enabled = false +} + +dependencies { + implementation project(path: ':operation-common') + + implementation 'org.springframework.boot:spring-boot-starter-security' + + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + // jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + + // db + runtimeOnly 'com.h2database:h2' + runtimeOnly 'org.postgresql:postgresql' + + // https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind + implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.14.0-rc1' +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/DomainRoot.java b/operation-domain/src/main/java/org/sopt/makers/operation/DomainRoot.java new file mode 100644 index 00000000..8b4c7f74 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/DomainRoot.java @@ -0,0 +1,4 @@ +package org.sopt.makers.operation; + +public interface DomainRoot { +} diff --git a/src/main/java/org/sopt/makers/operation/entity/Admin.java b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Admin.java similarity index 53% rename from src/main/java/org/sopt/makers/operation/entity/Admin.java rename to operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Admin.java index f6fd1bef..f4f406d0 100644 --- a/src/main/java/org/sopt/makers/operation/entity/Admin.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Admin.java @@ -1,21 +1,27 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.admin.domain; +import org.springframework.security.crypto.password.PasswordEncoder; + +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 lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.persistence.*; - -import static javax.persistence.GenerationType.IDENTITY; - @Getter @Setter @Entity @NoArgsConstructor public class Admin { + @Id - @GeneratedValue(strategy = IDENTITY) + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "admin_id") private Long id; @@ -49,4 +55,16 @@ public Admin(String email, String password, String name, Role role) { public void updateRefreshToken(String refreshToken) { this.refreshToken = refreshToken; } + + public boolean isNotAllowed() { + return this.status.equals(AdminStatus.NOT_CERTIFIED); + } + + public boolean isMatchRefreshToken(String refreshToken) { + return this.getRefreshToken().equals(refreshToken); + } + + public boolean checkPasswordMatched(PasswordEncoder passwordEncoder, String password) { + return passwordEncoder.matches(password, this.password); + } } diff --git a/src/main/java/org/sopt/makers/operation/entity/AdminStatus.java b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/AdminStatus.java similarity index 60% rename from src/main/java/org/sopt/makers/operation/entity/AdminStatus.java rename to operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/AdminStatus.java index 980530d3..74acd24c 100644 --- a/src/main/java/org/sopt/makers/operation/entity/AdminStatus.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/AdminStatus.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.admin.domain; public enum AdminStatus { DEVELOPER, SOPT, MAKERS, NOT_CERTIFIED diff --git a/src/main/java/org/sopt/makers/operation/entity/Role.java b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Role.java similarity index 74% rename from src/main/java/org/sopt/makers/operation/entity/Role.java rename to operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Role.java index a9709150..0da770ba 100644 --- a/src/main/java/org/sopt/makers/operation/entity/Role.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/admin/domain/Role.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.admin.domain; public enum Role { OPERATION_TEAM, PRESIDENT, VICE_PRESIDENT, AFFAIRS, MANAGE, MEDIA, PLAN, DESIGN, WEB, ANDROID, IOS, SERVER, MAKERS diff --git a/src/main/java/org/sopt/makers/operation/repository/AdminRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/admin/repository/AdminRepository.java similarity index 54% rename from src/main/java/org/sopt/makers/operation/repository/AdminRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/admin/repository/AdminRepository.java index 7bb0beac..f520b31a 100644 --- a/src/main/java/org/sopt/makers/operation/repository/AdminRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/admin/repository/AdminRepository.java @@ -1,14 +1,14 @@ -package org.sopt.makers.operation.repository; +package org.sopt.makers.operation.admin.repository; -import org.sopt.makers.operation.entity.Admin; +import java.util.Optional; + +import org.sopt.makers.operation.admin.domain.Admin; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; -import java.util.Optional; - @Repository public interface AdminRepository extends JpaRepository { - Optional findByEmail(String email); - boolean existsByEmail(String email); + Optional findByEmail(String email); + boolean existsByEmail(String email); } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Alarm.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Alarm.java new file mode 100644 index 00000000..9c33824b --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Alarm.java @@ -0,0 +1,116 @@ +package org.sopt.makers.operation.alarm.domain; + +import static java.util.Objects.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import org.sopt.makers.operation.common.domain.BaseEntity; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.schedule.converter.StringListConverter; + +import jakarta.persistence.Column; +import jakarta.persistence.Convert; +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 lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class Alarm extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "alarm_id") + private Long id; + + private int generation; + + private int generationAt; + + @Column(nullable = false) + @Enumerated(value = EnumType.STRING) + private Attribute attribute; + + private String title; + + @Column(columnDefinition = "TEXT") + private String content; + + private String link; + + private Boolean isActive; + + @Enumerated(value = EnumType.STRING) + private Part part; + + @Column(columnDefinition = "TEXT", nullable = false) + @Convert(converter = StringListConverter.class) + private List targetList; + + @Column(nullable = false) + @Enumerated(value = EnumType.STRING) + private Status status; + + private LocalDateTime sendAt; + + @Builder + public Alarm( + int generation, + int generationAt, + Attribute attribute, + String title, + String content, + String link, + Boolean isActive, + Part part, + List targetList + ) { + this.generation = generation; + this.generationAt = generationAt; + this.attribute = attribute; + this.title = title; + this.content = content; + setLink(link); + setTargetsInfo(isActive, part, targetList); + this.status = Status.BEFORE; + } + + private void setLink(String link) { + if (nonNull(link)) { + this.link = link; + } + } + + private void setTargetsInfo(Boolean isActive, Part part, List targetList) { + if (nonNull(targetList)) { + this.targetList = targetList; + } else { + this.isActive = isActive; + this.part = part; + this.targetList = new ArrayList<>(); + } + } + + public boolean isSent() { + return this.status.equals(Status.AFTER); + } + + public void updateToSent() { + this.status = Status.AFTER; + this.sendAt = LocalDateTime.now(); + } + + public boolean hasTargets() { + return Objects.isNull(this.isActive) || Objects.isNull(this.part); + } +} diff --git a/src/main/java/org/sopt/makers/operation/entity/alarm/Attribute.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Attribute.java similarity index 78% rename from src/main/java/org/sopt/makers/operation/entity/alarm/Attribute.java rename to operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Attribute.java index 0a64f842..86a15275 100644 --- a/src/main/java/org/sopt/makers/operation/entity/alarm/Attribute.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Attribute.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity.alarm; +package org.sopt.makers.operation.alarm.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/org/sopt/makers/operation/entity/alarm/Status.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Status.java similarity index 79% rename from src/main/java/org/sopt/makers/operation/entity/alarm/Status.java rename to operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Status.java index 682a4247..5f34a054 100644 --- a/src/main/java/org/sopt/makers/operation/entity/alarm/Status.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/domain/Status.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity.alarm; +package org.sopt.makers.operation.alarm.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmCustomRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmCustomRepository.java new file mode 100644 index 00000000..daa0c892 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmCustomRepository.java @@ -0,0 +1,13 @@ +package org.sopt.makers.operation.alarm.repository; + +import java.util.List; + +import org.sopt.makers.operation.alarm.domain.Alarm; +import org.sopt.makers.operation.alarm.domain.Status; +import org.sopt.makers.operation.common.domain.Part; +import org.springframework.data.domain.Pageable; + +public interface AlarmCustomRepository { + List findOrderByCreatedDate(Integer generation, Part part, Status status, Pageable pageable); + int count(int generation, Part part, Status status); +} diff --git a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepository.java similarity index 60% rename from src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepository.java index 9ca5e9d4..216a5e78 100644 --- a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepository.java @@ -1,6 +1,6 @@ -package org.sopt.makers.operation.repository.alarm; +package org.sopt.makers.operation.alarm.repository; -import org.sopt.makers.operation.entity.alarm.Alarm; +import org.sopt.makers.operation.alarm.domain.Alarm; import org.springframework.data.jpa.repository.JpaRepository; public interface AlarmRepository extends JpaRepository, AlarmCustomRepository { diff --git a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepositoryImpl.java b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepositoryImpl.java similarity index 73% rename from src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepositoryImpl.java rename to operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepositoryImpl.java index bacbba7c..7924b6f1 100644 --- a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmRepositoryImpl.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/alarm/repository/AlarmRepositoryImpl.java @@ -1,13 +1,13 @@ -package org.sopt.makers.operation.repository.alarm; +package org.sopt.makers.operation.alarm.repository; import static java.util.Objects.*; -import static org.sopt.makers.operation.entity.alarm.QAlarm.*; +import static org.sopt.makers.operation.alarm.domain.QAlarm.*; import java.util.List; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Alarm; -import org.sopt.makers.operation.entity.alarm.Status; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.alarm.domain.Alarm; +import org.sopt.makers.operation.alarm.domain.Status; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; @@ -22,7 +22,7 @@ public class AlarmRepositoryImpl implements AlarmCustomRepository { private final JPAQueryFactory queryFactory; @Override - public List getAlarms(Integer generation, Part part, Status status, Pageable pageable) { + public List findOrderByCreatedDate(Integer generation, Part part, Status status, Pageable pageable) { return queryFactory .selectFrom(alarm) .where( @@ -37,7 +37,7 @@ public List getAlarms(Integer generation, Part part, Status status, Pagea } @Override - public int countByGenerationAndPartAndStatus(int generation, Part part, Status status) { + public int count(int generation, Part part, Status status) { return Math.toIntExact(queryFactory .select(alarm.count()) .from(alarm) diff --git a/src/main/java/org/sopt/makers/operation/entity/Attendance.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/Attendance.java similarity index 57% rename from src/main/java/org/sopt/makers/operation/entity/Attendance.java rename to operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/Attendance.java index 7539f0f4..c2d7f64d 100644 --- a/src/main/java/org/sopt/makers/operation/entity/Attendance.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/Attendance.java @@ -1,27 +1,27 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.attendance.domain; -import static javax.persistence.GenerationType.*; -import static org.sopt.makers.operation.common.ExceptionMessage.*; -import static org.sopt.makers.operation.entity.AttendanceStatus.*; +import static jakarta.persistence.GenerationType.*; +import static org.sopt.makers.operation.code.failure.AttendanceFailureCode.*; +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EntityNotFoundException; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.sopt.makers.operation.entity.lecture.Lecture; - +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.member.domain.Member; +import org.sopt.makers.operation.exception.AttendanceException; + +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.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; import lombok.*; @Entity @@ -29,7 +29,8 @@ @Getter public class Attendance { - @Id @GeneratedValue(strategy = IDENTITY) + @Id + @GeneratedValue(strategy = IDENTITY) @Column(name = "attendance_id") private Long id; @@ -50,7 +51,23 @@ public class Attendance { public Attendance(Member member, Lecture lecture) { setMember(member); setLecture(lecture); - this.status = AttendanceStatus.ABSENT; + this.status = ABSENT; + } + + private void setMember(Member member) { + if (Objects.nonNull(this.member)) { + this.member.getAttendances().remove(this); + } + this.member = member; + member.getAttendances().add(this); + } + + private void setLecture(Lecture lecture) { + if (Objects.nonNull(this.lecture)) { + this.lecture.getAttendances().remove(this); + } + this.lecture = lecture; + lecture.getAttendances().add(this); } public void updateStatus() { @@ -60,17 +77,23 @@ public void updateStatus() { public AttendanceStatus getStatus() { val first = getSubAttendanceByRound(1); val second = getSubAttendanceByRound(2); + return switch (this.lecture.getAttribute()) { - case SEMINAR -> second.getStatus().equals(ATTENDANCE) - ? first.getStatus().equals(ATTENDANCE) ? ATTENDANCE : TARDY - : ABSENT; + case SEMINAR -> { + if (first.getStatus().equals(ATTENDANCE) && second.getStatus().equals(ATTENDANCE)) { + yield ATTENDANCE; + } + yield first.getStatus().equals(ABSENT) && second.getStatus().equals(ABSENT) ? ABSENT : TARDY; + } case EVENT -> second.getStatus().equals(ATTENDANCE) ? ATTENDANCE : ABSENT; case ETC -> second.getStatus().equals(ATTENDANCE) ? PARTICIPATE : NOT_PARTICIPATE; }; } public float getScore() { - return switch (this.lecture.getAttribute()) { + val lectureAttribute = this.lecture.getAttribute(); + + return switch (lectureAttribute) { case SEMINAR -> { if (this.status.equals(ABSENT)) { yield -1f; @@ -89,28 +112,18 @@ public void updateMemberScore() { this.member.updateScore(this.getScore()); } - public void revertMemberScore() { + public void restoreMemberScore() { this.member.updateScore((-1) * this.getScore()); } - private SubAttendance getSubAttendanceByRound(int round) { - return this.subAttendances.stream().filter(o -> o.getSubLecture().getRound() == round).findFirst() - .orElseThrow(() -> new EntityNotFoundException(INVALID_SUB_ATTENDANCE.getName())); - } - - private void setMember(Member member) { - if (Objects.nonNull(this.member)) { - this.member.getAttendances().remove(this); - } - this.member = member; - member.getAttendances().add(this); + public boolean isEnd() { + return this.lecture.isEnd(); } - private void setLecture(Lecture lecture) { - if (Objects.nonNull(this.lecture)) { - this.lecture.getAttendances().remove(this); - } - this.lecture = lecture; - lecture.getAttendances().add(this); + private SubAttendance getSubAttendanceByRound(int round) { + return this.subAttendances.stream() + .filter(o -> o.getSubLecture().getRound() == round) + .findFirst() + .orElseThrow(() -> new AttendanceException(INVALID_SUB_ATTENDANCE)); } } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/AttendanceStatus.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/AttendanceStatus.java new file mode 100644 index 00000000..1ad72922 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/AttendanceStatus.java @@ -0,0 +1,18 @@ +package org.sopt.makers.operation.attendance.domain; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@AllArgsConstructor +@Getter +public enum AttendanceStatus { + ATTENDANCE("출석"), + ABSENT("결석"), + TARDY("지각"), + LEAVE_EARLY("조퇴"), + PARTICIPATE("참여"), + NOT_PARTICIPATE("미참여") + ; + + final String name; +} diff --git a/src/main/java/org/sopt/makers/operation/entity/SubAttendance.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/SubAttendance.java similarity index 60% rename from src/main/java/org/sopt/makers/operation/entity/SubAttendance.java rename to operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/SubAttendance.java index 0099b00d..b9c0f667 100644 --- a/src/main/java/org/sopt/makers/operation/entity/SubAttendance.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/domain/SubAttendance.java @@ -1,19 +1,22 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.attendance.domain; -import static javax.persistence.GenerationType.*; +import static org.sopt.makers.operation.attendance.domain.AttendanceStatus.*; import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; +import org.sopt.makers.operation.common.domain.BaseEntity; +import org.sopt.makers.operation.lecture.domain.SubLecture; +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 lombok.Getter; import lombok.NoArgsConstructor; @@ -22,7 +25,8 @@ @NoArgsConstructor public class SubAttendance extends BaseEntity { - @Id @GeneratedValue(strategy = IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "sub_attendance_id") private Long id; @@ -40,7 +44,7 @@ public class SubAttendance extends BaseEntity { public SubAttendance(Attendance attendance, SubLecture subLecture) { setAttendance(attendance); setSubLecture(subLecture); - status = AttendanceStatus.ABSENT; + status = ABSENT; } private void setAttendance(Attendance attendance) { @@ -63,4 +67,8 @@ public void updateStatus(AttendanceStatus status) { this.status = status; this.attendance.updateStatus(); } + + public boolean isMatchRound(int round) { + return this.subLecture.getRound() == round; + } } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceCustomRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceCustomRepository.java new file mode 100644 index 00000000..2dee226d --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceCustomRepository.java @@ -0,0 +1,17 @@ +package org.sopt.makers.operation.attendance.repository.attendance; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.member.domain.Member; +import org.springframework.data.domain.Pageable; + +public interface AttendanceCustomRepository { + List findAttendanceByMemberId(Long memberId); + List findFetchJoin(Lecture lecture, Part part, Pageable pageable); + List findFetchJoin(Member member); + List findToday(long memberPlaygroundId); + int count(Lecture lecture, Part part); +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepository.java new file mode 100644 index 00000000..01bdf46e --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepository.java @@ -0,0 +1,14 @@ +package org.sopt.makers.operation.attendance.repository.attendance; + +import java.util.Optional; + +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface AttendanceRepository extends JpaRepository, AttendanceCustomRepository { + Optional findByLectureAndMember(Lecture lecture, Member member); + void deleteByLecture(Lecture lecture); +} diff --git a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepositoryImpl.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepositoryImpl.java similarity index 53% rename from src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepositoryImpl.java rename to operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepositoryImpl.java index 497ae2f9..9020a32e 100644 --- a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepositoryImpl.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/attendance/AttendanceRepositoryImpl.java @@ -1,25 +1,25 @@ -package org.sopt.makers.operation.repository.attendance; +package org.sopt.makers.operation.attendance.repository.attendance; import static com.querydsl.core.types.dsl.Expressions.*; import static java.util.Objects.*; -import static org.sopt.makers.operation.entity.QAttendance.*; -import static org.sopt.makers.operation.entity.QMember.*; -import static org.sopt.makers.operation.entity.QSubAttendance.*; -import static org.sopt.makers.operation.entity.QSubLecture.*; -import static org.sopt.makers.operation.entity.lecture.QLecture.*; - +import static org.sopt.makers.operation.attendance.domain.QAttendance.*; +import static org.sopt.makers.operation.attendance.domain.QSubAttendance.*; +import static org.sopt.makers.operation.common.domain.Part.*; +import static org.sopt.makers.operation.lecture.domain.QLecture.*; +import static org.sopt.makers.operation.lecture.domain.QSubLecture.*; +import static org.sopt.makers.operation.member.domain.QMember.*; + +import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.List; -import lombok.val; - -import org.sopt.makers.operation.config.GenerationConfig; -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.SubAttendance; -import org.sopt.makers.operation.entity.lecture.LectureStatus; +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.LectureStatus; +import org.sopt.makers.operation.member.domain.Member; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Repository; @@ -27,39 +27,38 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; +import lombok.val; @Repository @RequiredArgsConstructor public class AttendanceRepositoryImpl implements AttendanceCustomRepository { private final JPAQueryFactory queryFactory; - private final GenerationConfig generationConfig; + private final ValueConfig valueConfig; @Override public List findAttendanceByMemberId(Long memberId) { - return queryFactory .select(attendance) .from(attendance) .leftJoin(attendance.lecture, lecture) .where(attendance.member.id.eq(memberId), lecture.lectureStatus.eq(LectureStatus.END), - lecture.generation.eq(generationConfig.getCurrentGeneration()) + lecture.generation.eq(valueConfig.getGENERATION()) ) .orderBy(attendance.lecture.startDate.desc()) .fetch(); } @Override - public List findByLecture(Long lectureId, Part part, Pageable pageable) { + public List findFetchJoin(Lecture lecture, Part part, Pageable pageable) { return queryFactory .selectFrom(attendance) .leftJoin(attendance.subAttendances, subAttendance).fetchJoin() .leftJoin(subAttendance.subLecture, subLecture).fetchJoin() - .leftJoin(attendance.lecture, lecture).fetchJoin() .leftJoin(attendance.member, member).fetchJoin() .where( - attendance.lecture.id.eq(lectureId), + attendance.lecture.eq(lecture), partEq(part) ) .orderBy(stringTemplate("SUBSTR({0}, 1, 1)", member.name).asc()) @@ -69,7 +68,7 @@ public List findByLecture(Long lectureId, Part part, Pageable pageab } @Override - public List findByMember(Member member) { + public List findFetchJoin(Member member) { return queryFactory .selectFrom(attendance) .leftJoin(attendance.subAttendances, subAttendance).fetchJoin().distinct() @@ -81,54 +80,39 @@ public List findByMember(Member member) { } @Override - public List findCurrentAttendanceByMember(Long playGroundId) { - val now = LocalDateTime.now(); - val today = now.toLocalDate(); + public List findToday(long memberPlaygroundId) { + val today = LocalDate.now(); val startOfDay = today.atStartOfDay(); val endOfDay = LocalDateTime.of(today, LocalTime.MAX); - return queryFactory - .select(attendance) - .from(attendance) + .selectFrom(attendance) .leftJoin(attendance.lecture, lecture).fetchJoin() .leftJoin(attendance.member, member).fetchJoin() - .where( - lecture.part.eq(member.part).or(lecture.part.eq(Part.ALL)), - lecture.startDate.between(startOfDay, endOfDay), - member.playgroundId.eq(playGroundId), - member.generation.eq(generationConfig.getCurrentGeneration()) - ) - .orderBy(lecture.startDate.asc()) - .fetch(); - } - - @Override - public List findSubAttendanceByAttendanceId(Long attendanceId) { - return queryFactory - .select(subAttendance) - .from(subAttendance) + .leftJoin(attendance.subAttendances, subAttendance).fetchJoin().distinct() .leftJoin(subAttendance.subLecture, subLecture).fetchJoin() .where( - subAttendance.attendance.id.eq(attendanceId) - ) - .orderBy(subAttendance.createdDate.asc()) + member.playgroundId.eq(memberPlaygroundId), + member.generation.eq(valueConfig.getGENERATION()), + lecture.part.eq(member.part).or(lecture.part.eq(ALL)), + lecture.startDate.between(startOfDay, endOfDay)) + .orderBy(lecture.startDate.asc()) .fetch(); } @Override - public int countByLectureIdAndPart(long lectureId, Part part) { + public int count(Lecture lecture, Part part) { return Math.toIntExact(queryFactory .select(attendance.count()) .from(attendance) .where( - attendance.lecture.id.eq(lectureId), - nonNull(part) ? attendance.member.part.eq(part) : null + attendance.lecture.eq(lecture), + partEq(part) ) .fetchFirst() ); } private BooleanExpression partEq(Part part) { - return nonNull(part) ? member.part.eq(part) : null; + return (isNull(part) || part.equals(ALL)) ? null : attendance.member.part.eq(part); } } diff --git a/src/main/java/org/sopt/makers/operation/repository/SubAttendanceRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/subAttendance/SubAttendanceRepository.java similarity index 71% rename from src/main/java/org/sopt/makers/operation/repository/SubAttendanceRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/subAttendance/SubAttendanceRepository.java index 8362cbfe..a3cf761f 100644 --- a/src/main/java/org/sopt/makers/operation/repository/SubAttendanceRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/attendance/repository/subAttendance/SubAttendanceRepository.java @@ -1,9 +1,9 @@ -package org.sopt.makers.operation.repository; +package org.sopt.makers.operation.attendance.repository.subAttendance; import java.util.List; -import org.sopt.makers.operation.entity.SubAttendance; -import org.sopt.makers.operation.entity.SubLecture; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +import org.sopt.makers.operation.lecture.domain.SubLecture; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; diff --git a/src/main/java/org/sopt/makers/operation/config/JpaAuditingConfig.java b/operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaAuditingConfig.java similarity index 80% rename from src/main/java/org/sopt/makers/operation/config/JpaAuditingConfig.java rename to operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaAuditingConfig.java index b6561dd0..72be7d31 100644 --- a/src/main/java/org/sopt/makers/operation/config/JpaAuditingConfig.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaAuditingConfig.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.config; +package org.sopt.makers.operation.common.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/org/sopt/makers/operation/config/JpaQueryFactoryConfig.java b/operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaQueryFactoryConfig.java similarity index 77% rename from src/main/java/org/sopt/makers/operation/config/JpaQueryFactoryConfig.java rename to operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaQueryFactoryConfig.java index 06d4078c..355cdb28 100644 --- a/src/main/java/org/sopt/makers/operation/config/JpaQueryFactoryConfig.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/common/config/JpaQueryFactoryConfig.java @@ -1,12 +1,12 @@ -package org.sopt.makers.operation.config; - -import javax.persistence.EntityManager; +package org.sopt.makers.operation.common.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; + @Configuration public class JpaQueryFactoryConfig { diff --git a/src/main/java/org/sopt/makers/operation/entity/BaseEntity.java b/operation-domain/src/main/java/org/sopt/makers/operation/common/domain/BaseEntity.java similarity index 77% rename from src/main/java/org/sopt/makers/operation/entity/BaseEntity.java rename to operation-domain/src/main/java/org/sopt/makers/operation/common/domain/BaseEntity.java index b68b25db..ec685458 100644 --- a/src/main/java/org/sopt/makers/operation/entity/BaseEntity.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/common/domain/BaseEntity.java @@ -1,14 +1,13 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.common.domain; import java.time.LocalDateTime; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; - import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import lombok.Getter; @EntityListeners(AuditingEntityListener.class) diff --git a/src/main/java/org/sopt/makers/operation/entity/Part.java b/operation-domain/src/main/java/org/sopt/makers/operation/common/domain/Part.java similarity index 84% rename from src/main/java/org/sopt/makers/operation/entity/Part.java rename to operation-domain/src/main/java/org/sopt/makers/operation/common/domain/Part.java index 6ccfe2e2..e39f073c 100644 --- a/src/main/java/org/sopt/makers/operation/entity/Part.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/common/domain/Part.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.common.domain; import lombok.AllArgsConstructor; import lombok.Getter; diff --git a/src/main/java/org/sopt/makers/operation/entity/lecture/Attribute.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Attribute.java similarity index 56% rename from src/main/java/org/sopt/makers/operation/entity/lecture/Attribute.java rename to operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Attribute.java index b5869d72..1a46a21e 100644 --- a/src/main/java/org/sopt/makers/operation/entity/lecture/Attribute.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Attribute.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity.lecture; +package org.sopt.makers.operation.lecture.domain; import lombok.AllArgsConstructor; import lombok.Getter; @@ -6,7 +6,10 @@ @Getter @AllArgsConstructor public enum Attribute { - SEMINAR("세미나"), EVENT("행사"), ETC("기타"); + SEMINAR("세미나"), + EVENT("행사"), + ETC("기타"), + ; private final String name; } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Lecture.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Lecture.java new file mode 100644 index 00000000..7d412863 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/Lecture.java @@ -0,0 +1,104 @@ +package org.sopt.makers.operation.lecture.domain; + +import static org.sopt.makers.operation.lecture.domain.LectureStatus.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +import org.sopt.makers.operation.common.domain.BaseEntity; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.attendance.domain.Attendance; + +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.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class Lecture extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "lecture_id") + private Long id; + + private String name; + + @Enumerated(EnumType.STRING) + private Part part; + + private int generation; + + private String place; + + private LocalDateTime startDate; + + private LocalDateTime endDate; + + @Enumerated(EnumType.STRING) + private Attribute attribute; + + @Enumerated(EnumType.STRING) + private LectureStatus lectureStatus; + + @OneToMany(mappedBy = "lecture") + List subLectures = new ArrayList<>(); + + @OneToMany(mappedBy = "lecture") + List attendances = new ArrayList<>(); + + @Builder + public Lecture( + String name, + Part part, + int generation, + String place, + LocalDateTime startDate, + LocalDateTime endDate, + Attribute attribute + ) { + this.name = name; + this.part = part; + this.generation = generation; + this.place = place; + this.startDate = startDate; + this.endDate = endDate; + this.attribute = attribute; + this.lectureStatus = BEFORE; + } + + public void updateStatus(LectureStatus status) { + this.lectureStatus = status; + } + + public void updateToEnd() { + this.lectureStatus = END; + attendances.forEach(Attendance::updateMemberScore); + } + + public boolean isEnd() { + return this.lectureStatus.equals(END); + } + + public boolean isBefore() { + return this.lectureStatus.equals(BEFORE); + } + + public boolean isFirst() { + return this.lectureStatus.equals(FIRST); + } + + public boolean isNotYetToEnd() { + return this.endDate.isAfter(LocalDateTime.now()); + } +} diff --git a/src/main/java/org/sopt/makers/operation/entity/lecture/LectureStatus.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/LectureStatus.java similarity index 54% rename from src/main/java/org/sopt/makers/operation/entity/lecture/LectureStatus.java rename to operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/LectureStatus.java index a121b08f..246827ff 100644 --- a/src/main/java/org/sopt/makers/operation/entity/lecture/LectureStatus.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/LectureStatus.java @@ -1,4 +1,4 @@ -package org.sopt.makers.operation.entity.lecture; +package org.sopt.makers.operation.lecture.domain; public enum LectureStatus { BEFORE, FIRST, SECOND, END diff --git a/src/main/java/org/sopt/makers/operation/entity/SubLecture.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/SubLecture.java similarity index 52% rename from src/main/java/org/sopt/makers/operation/entity/SubLecture.java rename to operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/SubLecture.java index b38dfca7..47b85b0f 100644 --- a/src/main/java/org/sopt/makers/operation/entity/SubLecture.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/domain/SubLecture.java @@ -1,25 +1,23 @@ -package org.sopt.makers.operation.entity; +package org.sopt.makers.operation.lecture.domain; -import static javax.persistence.GenerationType.*; -import static org.sopt.makers.operation.entity.lecture.LectureStatus.*; +import static java.util.Objects.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; - -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.sopt.makers.operation.entity.lecture.LectureStatus; +import org.sopt.makers.operation.attendance.domain.SubAttendance; +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 lombok.Getter; import lombok.NoArgsConstructor; @@ -28,7 +26,8 @@ @Getter public class SubLecture { - @Id @GeneratedValue(strategy = IDENTITY) + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "sub_lecture_id") private Long id; @@ -49,25 +48,37 @@ public SubLecture(Lecture lecture, int round) { this.round = round; } - public void startAttendance(String code) { - this.startAt = LocalDateTime.now(); + private void setLecture(Lecture lecture) { + if (Objects.nonNull(this.lecture)) { + this.lecture.getSubLectures().remove(this); + } + this.lecture = lecture; + lecture.getSubLectures().add(this); + } + + public void updateCode(String code) { this.code = code; + this.startAt = LocalDateTime.now(); this.lecture.updateStatus(getUpdatedStatus()); } private LectureStatus getUpdatedStatus() { return switch (this.round) { - case 1 -> FIRST; - case 2 -> SECOND; + case 1 -> LectureStatus.FIRST; + case 2 -> LectureStatus.SECOND; default -> this.lecture.getLectureStatus(); }; } - private void setLecture(Lecture lecture) { - if (Objects.nonNull(this.lecture)) { - this.lecture.getSubLectures().remove(this); - } - this.lecture = lecture; - lecture.getSubLectures().add(this); + public boolean isNotStarted() { + return isNull(this.startAt) || isNull(this.code) || this.startAt.isAfter(LocalDateTime.now()); + } + + public boolean isEnded(int attendanceMinute) { + return this.startAt.plusMinutes(attendanceMinute).isBefore(LocalDateTime.now()); + } + + public boolean isMatchCode(String code) { + return this.code.equals(code); } } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureCustomRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureCustomRepository.java new file mode 100644 index 00000000..738e1679 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureCustomRepository.java @@ -0,0 +1,12 @@ +package org.sopt.makers.operation.lecture.repository.lecture; + +import java.util.List; +import java.util.Optional; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.lecture.domain.Lecture; + +public interface LectureCustomRepository { + List find(int generation, Part part); + List findLecturesReadyToEnd(); +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepository.java new file mode 100644 index 00000000..210f9f7a --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepository.java @@ -0,0 +1,7 @@ +package org.sopt.makers.operation.lecture.repository.lecture; + +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LectureRepository extends JpaRepository, LectureCustomRepository { +} diff --git a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepositoryImpl.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepositoryImpl.java similarity index 56% rename from src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepositoryImpl.java rename to operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepositoryImpl.java index 85772ee1..281831ae 100644 --- a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepositoryImpl.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/lecture/LectureRepositoryImpl.java @@ -1,28 +1,29 @@ -package org.sopt.makers.operation.repository.lecture; +package org.sopt.makers.operation.lecture.repository.lecture; -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.springframework.stereotype.Repository; +import static java.util.Objects.*; +import static org.sopt.makers.operation.attendance.domain.QAttendance.*; +import static org.sopt.makers.operation.lecture.domain.QLecture.*; +import static org.sopt.makers.operation.member.domain.QMember.*; import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; -import static java.util.Objects.*; -import static org.sopt.makers.operation.entity.QAttendance.*; -import static org.sopt.makers.operation.entity.QMember.*; -import static org.sopt.makers.operation.entity.lecture.LectureStatus.*; -import static org.sopt.makers.operation.entity.lecture.QLecture.*; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.LectureStatus; +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; @Repository @RequiredArgsConstructor public class LectureRepositoryImpl implements LectureCustomRepository { private final JPAQueryFactory queryFactory; @Override - public List findLectures(int generation, Part part) { + public List find(int generation, Part part) { return queryFactory .selectFrom(lecture) .leftJoin(lecture.attendances, attendance).fetchJoin().distinct() @@ -35,28 +36,18 @@ public List findLectures(int generation, Part part) { } @Override - public List findLecturesToBeEnd() { + public List findLecturesReadyToEnd() { return queryFactory .selectFrom(lecture) .leftJoin(lecture.attendances, attendance).fetchJoin().distinct() .leftJoin(attendance.member, member).fetchJoin().distinct() .where( lecture.endDate.before(LocalDateTime.now()), - lecture.lectureStatus.ne(END) + lecture.lectureStatus.ne(LectureStatus.END) ) .fetch(); } - @Override - public Optional find(Long lectureId) { - return queryFactory - .selectFrom(lecture) - .leftJoin(lecture.attendances, attendance).fetchJoin().distinct() - .leftJoin(attendance.member, member).fetchJoin().distinct() - .where(lecture.id.eq(lectureId)) - .stream().findFirst(); - } - private BooleanExpression partEq(Part part) { return nonNull(part) ? lecture.part.eq(part) : null; } diff --git a/src/main/java/org/sopt/makers/operation/repository/lecture/SubLectureRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/subLecture/SubLectureRepository.java similarity index 64% rename from src/main/java/org/sopt/makers/operation/repository/lecture/SubLectureRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/subLecture/SubLectureRepository.java index 4eeb7d85..f34934ea 100644 --- a/src/main/java/org/sopt/makers/operation/repository/lecture/SubLectureRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/lecture/repository/subLecture/SubLectureRepository.java @@ -1,16 +1,12 @@ -package org.sopt.makers.operation.repository.lecture; +package org.sopt.makers.operation.lecture.repository.subLecture; -import org.sopt.makers.operation.entity.SubLecture; -import org.sopt.makers.operation.entity.lecture.Lecture; +import org.sopt.makers.operation.lecture.domain.Lecture; +import org.sopt.makers.operation.lecture.domain.SubLecture; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; -import java.util.Optional; - public interface SubLectureRepository extends JpaRepository { - Optional findById(Long subLectureId); - @Modifying(clearAutomatically = true, flushAutomatically = true) @Query("delete from SubLecture sl where sl.lecture = :lecture") void deleteAllByLecture(Lecture lecture); diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/Member.java b/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/Member.java new file mode 100644 index 00000000..caafb2c5 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/Member.java @@ -0,0 +1,62 @@ +package org.sopt.makers.operation.member.domain; + +import java.util.ArrayList; +import java.util.List; + +import org.sopt.makers.operation.attendance.domain.Attendance; +import org.sopt.makers.operation.common.domain.Part; + +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.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class Member { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "member_id") + private Long id; + + private Long playgroundId; + + private String name; + private int generation; + + @Enumerated(EnumType.STRING) + private ObYb obyb; + + @Enumerated(EnumType.STRING) + private Part part; + + private String university; + private float score; + private String phone; + + @OneToMany(mappedBy = "member") + List attendances = new ArrayList<>(); + + public void updateScore(float score) { + this.score += score; + } + + public void updateTotalScore() { + this.score = calcAllAttendances(); + } + + private float calcAllAttendances() { + return (float) (2 + this.attendances.stream() + .filter(Attendance::isEnd) + .mapToDouble(Attendance::getScore) + .sum()); + } +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/ObYb.java b/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/ObYb.java new file mode 100644 index 00000000..672c84e6 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/member/domain/ObYb.java @@ -0,0 +1,5 @@ +package org.sopt.makers.operation.member.domain; + +public enum ObYb { + OB, YB +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberCustomRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberCustomRepository.java new file mode 100644 index 00000000..93493bbe --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberCustomRepository.java @@ -0,0 +1,13 @@ +package org.sopt.makers.operation.member.repository; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.member.domain.Member; +import org.springframework.data.domain.Pageable; + +public interface MemberCustomRepository { + int count(int generation, Part part); + List find(int generation, Part part); + List find(int generation, Part part, Pageable pageable); +} diff --git a/src/main/java/org/sopt/makers/operation/repository/member/MemberRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepository.java similarity index 65% rename from src/main/java/org/sopt/makers/operation/repository/member/MemberRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepository.java index ec0c0cbe..45184292 100644 --- a/src/main/java/org/sopt/makers/operation/repository/member/MemberRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepository.java @@ -1,11 +1,10 @@ -package org.sopt.makers.operation.repository.member; - -import org.sopt.makers.operation.entity.Member; -import org.springframework.data.jpa.repository.JpaRepository; +package org.sopt.makers.operation.member.repository; import java.util.Optional; +import org.sopt.makers.operation.member.domain.Member; +import org.springframework.data.jpa.repository.JpaRepository; + public interface MemberRepository extends JpaRepository, MemberCustomRepository { Optional getMemberByPlaygroundIdAndGeneration(Long id, int generation); - boolean existsByPlaygroundId(Long id); } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepositoryImpl.java b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepositoryImpl.java new file mode 100644 index 00000000..4f4a17f8 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/member/repository/MemberRepositoryImpl.java @@ -0,0 +1,67 @@ +package org.sopt.makers.operation.member.repository; + +import static java.util.Objects.*; +import static org.sopt.makers.operation.common.domain.Part.*; +import static org.sopt.makers.operation.member.domain.QMember.*; + +import java.util.List; + +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.member.domain.Member; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class MemberRepositoryImpl implements MemberCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public int count(int generation, Part part) { + return Math.toIntExact(queryFactory + .select(member.count()) + .from(member) + .where( + member.generation.eq(generation), + partEq(part) + ) + .fetchFirst()); + } + + @Override + public List find(int generation, Part part) { + return queryFactory + .selectFrom(member) + .where( + member.generation.eq(generation), + partEq(part)) + .fetch(); + } + + private BooleanExpression partEq(Part part) { + return (isNull(part) || part.equals(ALL)) ? null : member.part.eq(part); + } + + @Override + public List find(int generation, Part part, Pageable pageable) { + StringExpression firstName = Expressions.stringTemplate("SUBSTR({0}, 1, 1)", member.name); + return queryFactory + .selectFrom(member) + .where( + partEq(part), + member.generation.eq(generation) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(firstName.asc()) + .fetch(); + } +} diff --git a/src/main/java/org/sopt/makers/operation/converter/StringListConverter.java b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/converter/StringListConverter.java similarity index 83% rename from src/main/java/org/sopt/makers/operation/converter/StringListConverter.java rename to operation-domain/src/main/java/org/sopt/makers/operation/schedule/converter/StringListConverter.java index fe244329..4f0fdc2d 100644 --- a/src/main/java/org/sopt/makers/operation/converter/StringListConverter.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/converter/StringListConverter.java @@ -1,15 +1,14 @@ -package org.sopt.makers.operation.converter; +package org.sopt.makers.operation.schedule.converter; import static com.fasterxml.jackson.databind.DeserializationFeature.*; -import java.util.List; import java.io.IOException; +import java.util.List; -import javax.persistence.AttributeConverter; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import jakarta.persistence.AttributeConverter; public class StringListConverter implements AttributeConverter, String> { private static final ObjectMapper mapper = new ObjectMapper() @@ -20,7 +19,7 @@ public class StringListConverter implements AttributeConverter, Str public String convertToDatabaseColumn(List attribute) { try { return mapper.writeValueAsString(attribute); - } catch (JsonProcessingException ex) { + } catch (IOException ex) { throw new IllegalArgumentException(ex.getMessage()); } } diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/schedule/domain/Schedule.java b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/domain/Schedule.java new file mode 100644 index 00000000..6852ef09 --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/domain/Schedule.java @@ -0,0 +1,39 @@ +package org.sopt.makers.operation.schedule.domain; + +import java.time.LocalDateTime; + +import org.sopt.makers.operation.common.domain.BaseEntity; +import org.sopt.makers.operation.lecture.domain.Attribute; + +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 lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@NoArgsConstructor +@Getter +public class Schedule extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "schedule_id") + private Long id; + + private LocalDateTime startDate; + + private LocalDateTime endDate; + + @Column(nullable = false) + @Enumerated(value = EnumType.STRING) + private Attribute attribute; + + private String title; + + private String location; +} diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleCustomRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleCustomRepository.java new file mode 100644 index 00000000..ce77b40d --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleCustomRepository.java @@ -0,0 +1,10 @@ +package org.sopt.makers.operation.schedule.repository; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.operation.schedule.domain.Schedule; + +public interface ScheduleCustomRepository { + List findBetween(LocalDateTime startDate, LocalDateTime endDate); +} diff --git a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepository.java b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepository.java similarity index 60% rename from src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepository.java rename to operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepository.java index c651e850..2226c637 100644 --- a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepository.java +++ b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepository.java @@ -1,6 +1,6 @@ -package org.sopt.makers.operation.repository.schedule; +package org.sopt.makers.operation.schedule.repository; -import org.sopt.makers.operation.entity.Member; +import org.sopt.makers.operation.member.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface ScheduleRepository extends JpaRepository, ScheduleCustomRepository { diff --git a/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepositoryImpl.java b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepositoryImpl.java new file mode 100644 index 00000000..92090bab --- /dev/null +++ b/operation-domain/src/main/java/org/sopt/makers/operation/schedule/repository/ScheduleRepositoryImpl.java @@ -0,0 +1,37 @@ +package org.sopt.makers.operation.schedule.repository; + +import static org.sopt.makers.operation.schedule.domain.QSchedule.*; + +import java.time.LocalDateTime; +import java.util.List; + +import org.sopt.makers.operation.schedule.domain.Schedule; +import org.springframework.stereotype.Repository; + +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ScheduleRepositoryImpl implements ScheduleCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findBetween(LocalDateTime startDate, LocalDateTime endDate) { + return queryFactory + .select(schedule) + .from(schedule) + .where( + schedule.startDate.eq(startDate) + .or(schedule.startDate.between(startDate, endDate)) + .or(schedule.startDate.eq(endDate)) + .or(schedule.endDate.eq(startDate)) + .or(schedule.endDate.between(startDate, endDate)) + .or(schedule.endDate.eq(endDate)) + ) + .orderBy(schedule.startDate.asc()) + .fetch(); + } +} diff --git a/operation-external/build.gradle b/operation-external/build.gradle new file mode 100644 index 00000000..4c29cb51 --- /dev/null +++ b/operation-external/build.gradle @@ -0,0 +1,18 @@ +jar { + enabled = true +} + +bootJar { + enabled = false +} + +dependencies { + implementation project(path: ':operation-common') + implementation project(path: ':operation-domain') + + implementation 'org.springframework.boot:spring-boot-starter-web' +} + +test { + useJUnitPlatform() +} \ No newline at end of file diff --git a/operation-external/src/main/java/org/sopt/makers/operation/ExternalRoot.java b/operation-external/src/main/java/org/sopt/makers/operation/ExternalRoot.java new file mode 100644 index 00000000..a3168705 --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/ExternalRoot.java @@ -0,0 +1,4 @@ +package org.sopt.makers.operation; + +public interface ExternalRoot { +} diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSender.java b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSender.java new file mode 100644 index 00000000..60e39472 --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSender.java @@ -0,0 +1,7 @@ +package org.sopt.makers.operation.client.alarm; + +import org.sopt.makers.operation.client.alarm.dto.AlarmSenderRequest; + +public interface AlarmSender { + void send(AlarmSenderRequest request); +} diff --git a/src/main/java/org/sopt/makers/operation/external/api/AlarmSenderImpl.java b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSenderImpl.java similarity index 51% rename from src/main/java/org/sopt/makers/operation/external/api/AlarmSenderImpl.java rename to operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSenderImpl.java index d6bf50ff..8f3e59f4 100644 --- a/src/main/java/org/sopt/makers/operation/external/api/AlarmSenderImpl.java +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/AlarmSenderImpl.java @@ -1,20 +1,17 @@ -package org.sopt.makers.operation.external.api; +package org.sopt.makers.operation.client.alarm; import static java.util.Objects.*; import static java.util.UUID.*; -import static org.sopt.makers.operation.common.ExceptionMessage.*; +import static org.sopt.makers.operation.code.failure.AlarmFailureCode.*; import static org.springframework.http.MediaType.*; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; -import org.sopt.makers.operation.dto.alarm.AlarmSendResponseDTO; -import org.sopt.makers.operation.dto.alarm.AlarmSenderDTO; +import org.sopt.makers.operation.client.alarm.dto.AlarmSenderRequest; +import org.sopt.makers.operation.config.ValueConfig; import org.sopt.makers.operation.exception.AlarmException; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; @@ -27,59 +24,48 @@ @Component @RequiredArgsConstructor public class AlarmSenderImpl implements AlarmSender { - private final RestTemplate restTemplate; - @Value("${notification.key}") - private String key; - @Value("${notification.url}") - private String host; - - private final List appLinkList = Arrays.asList( - "home", - "home/notification", - "home/mypage", - "home/attendance", - "home/attendance/attendance-modal", - "home/soptamp", - "home/soptamp/entire-ranking", - "home/soptamp/current-generation-ranking" - ); - private final List webLinkList = Arrays.asList( - "https://playground.sopt.org/members", - "https://playground.sopt.org/group" - ); + private final RestTemplate restTemplate; + private final ValueConfig valueConfig; @Override - public void send(AlarmSenderDTO alarmSenderDTO) { - val alarmRequest = getAlarmRequest(alarmSenderDTO); - val headers = getHeaders(); - val entity = new HttpEntity<>(alarmRequest, headers); - + public void send(AlarmSenderRequest request) { try { - restTemplate.postForEntity(host, entity, AlarmSendResponseDTO.class); + val host = valueConfig.getNOTIFICATION_URL(); + val entity = getEntity(request); + restTemplate.postForEntity(host, entity, AlarmSenderRequest.class); } catch (HttpClientErrorException e) { - throw new AlarmException(FAIL_SEND_ALARM.getName()); + throw new AlarmException(FAIL_SEND_ALARM); } } - private Map getAlarmRequest(AlarmSenderDTO alarmSenderDTO) { + private HttpEntity> getEntity(AlarmSenderRequest request) { + val alarmRequest = getAlarmRequest(request); + val headers = getHeaders(); + return new HttpEntity<>(alarmRequest, headers); + } + + private Map getAlarmRequest(AlarmSenderRequest request) { val alarmRequest = new HashMap<>(); - val link = alarmSenderDTO.link(); + val link = request.link(); val linkKey = getLinkKey(link); if (!linkKey.isEmpty()) { alarmRequest.put(linkKey, link); } - alarmRequest.put("userIds", alarmSenderDTO.targetList()); - alarmRequest.put("title", alarmSenderDTO.title()); - alarmRequest.put("content", alarmSenderDTO.content()); - alarmRequest.put("category", alarmSenderDTO.attribute()); + alarmRequest.put("userIds", request.targetList()); + alarmRequest.put("title", request.title()); + alarmRequest.put("content", request.content()); + alarmRequest.put("category", request.attribute()); return alarmRequest; } private String getLinkKey(String link) { + val appLinkList = valueConfig.getAPP_LINK_LIST(); + val webLinkList = valueConfig.getWEB_LINK_LIST(); + if (nonNull(link)) { if (appLinkList.contains(link)) { return "appLink"; @@ -92,6 +78,7 @@ private String getLinkKey(String link) { private HttpHeaders getHeaders() { val headers = new HttpHeaders(); + val key = valueConfig.getNOTIFICATION_KEY(); headers.setContentType(APPLICATION_JSON); headers.setAccept(Collections.singletonList(APPLICATION_JSON)); diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/dto/AlarmSenderRequest.java b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/dto/AlarmSenderRequest.java new file mode 100644 index 00000000..7043fc9c --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/alarm/dto/AlarmSenderRequest.java @@ -0,0 +1,54 @@ +package org.sopt.makers.operation.client.alarm.dto; + +import static org.sopt.makers.operation.alarm.domain.Attribute.*; + +import java.util.List; + +import org.sopt.makers.operation.config.ValueConfig; +import org.sopt.makers.operation.alarm.domain.Alarm; +import org.sopt.makers.operation.alarm.domain.Attribute; +import org.sopt.makers.operation.lecture.domain.Lecture; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.val; + +@Builder(access = AccessLevel.PRIVATE) +public record AlarmSenderRequest( + String title, + String content, + List targetList, + Attribute attribute, + String link +) { + + public static AlarmSenderRequest of(Alarm alarm, List targetList) { + return AlarmSenderRequest.builder() + .title(alarm.getTitle()) + .content(alarm.getContent()) + .targetList(targetList) + .attribute(alarm.getAttribute()) + .link(alarm.getLink()) + .build(); + } + + public static AlarmSenderRequest of(Lecture lecture, ValueConfig valueConfig) { + val title = lecture.getName() + " " + valueConfig.getALARM_MESSAGE_TITLE(); + val content = valueConfig.getALARM_MESSAGE_CONTENT(); + val targetList = getTargetsFromLecture(lecture); + return AlarmSenderRequest.builder() + .title(title) + .content(content) + .targetList(targetList) + .attribute(NEWS) + .build(); + } + + private static List getTargetsFromLecture(Lecture lecture) { + return lecture.getAttendances().stream() + .map(attendance -> String.valueOf(attendance.getMember().getPlaygroundId())) + .filter(id -> !id.equals("null")) + .toList(); + } + +} diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServer.java b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServer.java new file mode 100644 index 00000000..9230281e --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServer.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.client.playground; + +import org.sopt.makers.operation.client.playground.dto.MemberListGetResponse; +import org.sopt.makers.operation.common.domain.Part; + +public interface PlayGroundServer { + MemberListGetResponse getMembers(int generation, Part part); +} diff --git a/src/main/java/org/sopt/makers/operation/external/api/PlayGroundServerImpl.java b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServerImpl.java similarity index 55% rename from src/main/java/org/sopt/makers/operation/external/api/PlayGroundServerImpl.java rename to operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServerImpl.java index 3710228f..c48cd3f7 100644 --- a/src/main/java/org/sopt/makers/operation/external/api/PlayGroundServerImpl.java +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/PlayGroundServerImpl.java @@ -1,13 +1,13 @@ -package org.sopt.makers.operation.external.api; +package org.sopt.makers.operation.client.playground; -import static org.sopt.makers.operation.common.ExceptionMessage.*; -import static org.sopt.makers.operation.entity.Part.*; +import static org.sopt.makers.operation.code.failure.AlarmFailureCode.*; +import static org.sopt.makers.operation.common.domain.Part.*; import static org.springframework.http.HttpMethod.*; -import org.sopt.makers.operation.dto.alarm.AlarmInactiveListResponseDTO; -import org.sopt.makers.operation.entity.Part; +import org.sopt.makers.operation.client.playground.dto.MemberListGetResponse; +import org.sopt.makers.operation.common.domain.Part; +import org.sopt.makers.operation.config.ValueConfig; import org.sopt.makers.operation.exception.AlarmException; -import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Component; @@ -19,31 +19,29 @@ @Component @RequiredArgsConstructor public class PlayGroundServerImpl implements PlayGroundServer { - private final RestTemplate restTemplate; - @Value("${sopt.makers.playground.server}") - private String playGroundURI; - @Value("${sopt.makers.playground.token}") - private String playGroundToken; + private final RestTemplate restTemplate; + private final ValueConfig valueConfig; @Override - public AlarmInactiveListResponseDTO getInactiveMembers(int generation, Part part) { - val uri = getInactiveUserURI(part, generation); + public MemberListGetResponse getMembers(int generation, Part part) { + val uri = getPlaygroundMemberUri(part, generation); val headers = getHeaders(); val entity = new HttpEntity<>(null, headers); try { - val response = restTemplate.exchange(uri, GET, entity, AlarmInactiveListResponseDTO.class); + val response = restTemplate.exchange(uri, GET, entity, MemberListGetResponse.class); return response.getBody(); } catch (Exception e) { - throw new AlarmException(FAIL_INACTIVE_USERS.getName()); + throw new AlarmException(FAIL_INACTIVE_USERS); } } - private String getInactiveUserURI(Part part, int generation) { + private String getPlaygroundMemberUri(Part part, int generation) { val uri = new StringBuilder(); + val playGroundUri = valueConfig.getPlayGroundURI(); - uri.append(playGroundURI) + uri.append(playGroundUri) .append("/internal/api/v1/members/inactivity?generation=") .append(generation); @@ -56,8 +54,11 @@ private String getInactiveUserURI(Part part, int generation) { private HttpHeaders getHeaders() { val headers = new HttpHeaders(); + val playGroundToken = valueConfig.getPlayGroundToken(); + headers.add("content-type", "application/json;charset=UTF-8"); headers.add("Authorization", playGroundToken); + return headers; } } diff --git a/operation-external/src/main/java/org/sopt/makers/operation/client/playground/dto/MemberListGetResponse.java b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/dto/MemberListGetResponse.java new file mode 100644 index 00000000..15fab5f9 --- /dev/null +++ b/operation-external/src/main/java/org/sopt/makers/operation/client/playground/dto/MemberListGetResponse.java @@ -0,0 +1,8 @@ +package org.sopt.makers.operation.client.playground.dto; + +import java.util.List; + +public record MemberListGetResponse( + List memberIds +) { +} diff --git a/src/main/java/org/sopt/makers/operation/config/RestTemplateConfig.java b/operation-external/src/main/java/org/sopt/makers/operation/config/RestTemplateConfig.java similarity index 100% rename from src/main/java/org/sopt/makers/operation/config/RestTemplateConfig.java rename to operation-external/src/main/java/org/sopt/makers/operation/config/RestTemplateConfig.java diff --git a/scripts/health_check.sh b/scripts/health_check.sh index 9f72a26a..a6390126 100644 --- a/scripts/health_check.sh +++ b/scripts/health_check.sh @@ -13,12 +13,12 @@ else fi -echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}' ..." +echo "> Start health check of WAS at 'http://127.0.0.1:${TARGET_PORT}/api/v1/test' ..." for RETRY_COUNT in {1..10} do echo "> #${RETRY_COUNT} trying..." - RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${TARGET_PORT}) + RESPONSE_CODE=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:${TARGET_PORT}/api/v1/test) if [ ${RESPONSE_CODE} -eq 200 ]; then echo "> New WAS successfully running" diff --git a/scripts/run_new_was.sh b/scripts/run_new_was.sh index 195b050b..ac5c1dab 100644 --- a/scripts/run_new_was.sh +++ b/scripts/run_new_was.sh @@ -22,13 +22,13 @@ fi if [ "$DEPLOYMENT_GROUP_NAME" == "prod" ] then - nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=prod /home/ubuntu/operation/build/libs/operation-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null & + nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=prod /home/ubuntu/operation/operation-api/build/libs/operation-api-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null & echo "> Now new WAS runs at ${TARGET_PORT}." fi if [ "$DEPLOYMENT_GROUP_NAME" == "dev" ] then - nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=dev /home/ubuntu/operation/build/libs/operation-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null & + nohup java -jar -Dserver.port=${TARGET_PORT} -Dspring.profiles.active=dev /home/ubuntu/operation/operation-api/build/libs/operation-api-0.0.1-SNAPSHOT.jar > /dev/null 2> /dev/null < /dev/null & echo "> Now new WAS runs at ${TARGET_PORT}." fi diff --git a/settings.gradle b/settings.gradle index fbee0ab1..c0c519a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,7 @@ rootProject.name = 'operation' +include 'operation-api' +include 'operation-auth' +include 'operation-common' +include 'operation-domain' +include 'operation-external' + diff --git a/src/main/java/org/sopt/makers/operation/OperationServerApplication.java b/src/main/java/org/sopt/makers/operation/OperationServerApplication.java deleted file mode 100644 index 432aa9b8..00000000 --- a/src/main/java/org/sopt/makers/operation/OperationServerApplication.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.makers.operation; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class OperationServerApplication { - - public static void main(String[] args) { - SpringApplication.run(OperationServerApplication.class, args); - } - -} diff --git a/src/main/java/org/sopt/makers/operation/common/ApiResponse.java b/src/main/java/org/sopt/makers/operation/common/ApiResponse.java deleted file mode 100644 index 0b6e12cb..00000000 --- a/src/main/java/org/sopt/makers/operation/common/ApiResponse.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.sopt.makers.operation.common; - -public record ApiResponse( - boolean success, - String message, - Object data -) { - - public static ApiResponse success(String message, Object data) { - return new ApiResponse(true, message, data); - } - - public static ApiResponse success(String message) { - return new ApiResponse(true, message, null); - } - - public static ApiResponse fail(String message) { - return new ApiResponse(false, message, null); - } -} diff --git a/src/main/java/org/sopt/makers/operation/common/ExceptionMessage.java b/src/main/java/org/sopt/makers/operation/common/ExceptionMessage.java deleted file mode 100644 index b9226037..00000000 --- a/src/main/java/org/sopt/makers/operation/common/ExceptionMessage.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.sopt.makers.operation.common; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public enum ExceptionMessage { - INVALID_MEMBER("존재하지 않는 회원입니다."), - INVALID_LECTURE("존재하지 않는 세션입니다."), - INVALID_SUB_LECTURE("존재하지 않는 세션입니다."), - INVALID_ATTENDANCE("존재하지 않는 출석 세션입니다."), - INVALID_SUB_ATTENDANCE("존재하지 않는 N차 출석입니다."), - INVALID_TOKEN("유효하지 않은 토큰입니다."), - EXPIRED_TOKEN("만료된 토큰입니다."), - INVALID_SIGNATURE("유효하지 않은 서명입니다."), - INVALID_AUTH_REQUEST("빈 토큰입니다."), - NOT_STARTED_PRE_ATTENDANCE("이전의 출석체크가 시작되지 않았습니다."), - NO_SESSION("오늘 세션이 없습니다."), - NOT_STARTED_ATTENDANCE("출석 시작 전입니다."), - NOT_STARTED_NTH_ATTENDANCE("차 출석 시작 전입니다."), - ENDED_ATTENDANCE("차 출석이 이미 종료되었습니다."), - INVALID_COUNT_SESSION("세션의 개수가 올바르지 않습니다."), - INVALID_CODE("코드가 일치하지 않아요!"), - NOT_END_TIME_YET("세션 종료 시간이 지나지 않았습니다."), - END_LECTURE("이미 종료된 세션입니다."), - NO_SUB_LECTURE_EQUAL_ROUND("해당 라운드와 일치하는 출석 세션이 없습니다."), - FAULT_DATE_FORMATTER("잘못된 날짜 형식입니다."), - DUPLICATED_MEMBER("이미 존재하는 회원입니다."), - INVALID_ALARM("알림이 존재하지 않습니다."), - ALREADY_SEND_ALARM("이미 전송된 알림입니다."), - INVALID_LINK("유효하지 않는 링크입니다."), - FAIL_SEND_ALARM("알림 전송에 실패하였습니다."), - FAIL_INACTIVE_USERS("비활동 유저 불러오기에 실패하였습니다."); - - private final String name; -} diff --git a/src/main/java/org/sopt/makers/operation/common/ResponseMessage.java b/src/main/java/org/sopt/makers/operation/common/ResponseMessage.java deleted file mode 100644 index b40126da..00000000 --- a/src/main/java/org/sopt/makers/operation/common/ResponseMessage.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sopt.makers.operation.common; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor -public enum ResponseMessage { - - /** auth **/ - SUCCESS_SIGN_UP("회원 가입 성공"), - SUCCESS_LOGIN_UP("로그인 성공"), - SUCCESS_GET_REFRESH_TOKEN("토큰 재발급 성공"), - - /** app **/ - SUCCESS_SINGLE_GET_LECTURE("세션 조회 성공"), - SUCCESS_TOTAL_ATTENDANCE("전체 출석정보 조회 성공"), - SUCCESS_GET_ATTENDANCE_SCORE("출석 점수 조회 성공"), - SUCCESS_GET_LECTURE_ROUND("출석 차수 조회 성공"), - SUCCESS_ATTEND("출석 성공"), - SUCCESS_CREATE_MEMBER("회원 등록 성공"), - - /** attendance **/ - SUCCESS_UPDATE_ATTENDANCE_STATUS("출석 상태 변경 성공"), - SUCCESS_GET_MEMBER_ATTENDANCE("회원 출석 정보 조회 성공"), - SUCCESS_UPDATE_MEMBER_SCORE("회원 출석 점수 갱신 성공"), - SUCCESS_GET_ATTENDANCES("출석 리스트 조회 성공"), - - /** lecture **/ - SUCCESS_CREATE_LECTURE("세션 생성 성공"), - SUCCESS_GET_LECTURES("세션 리스트 조회 성공"), - SUCCESS_GET_LECTURE("세션 상세 조회 성공"), - SUCCESS_START_ATTENDANCE("출석 시작 성공"), - SUCCESS_GET_MEMBERS("유저 리스트 조회 성공"), - SUCCESS_DELETE_LECTURE("세션 삭제 성공"), - - /** alarm **/ - SUCCESS_SEND_ALARM("알림 전송 성공"), - SUCCESS_CREATE_ALARM("알림 생성 성공"), - SUCCESS_GET_ALARMS("알림 리스트 조회 성공"), - SUCCESS_GET_ALARM("알림 상세 조회 성공"), - SUCCESS_DELETE_ALARM("알림 삭제 성공"), - - /** schedule **/ - SUCCESS_GET_SCHEDULES("일정 리스트 조회 성공") - ; - - private final String message; -} diff --git a/src/main/java/org/sopt/makers/operation/config/GenerationConfig.java b/src/main/java/org/sopt/makers/operation/config/GenerationConfig.java deleted file mode 100644 index 890de985..00000000 --- a/src/main/java/org/sopt/makers/operation/config/GenerationConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.makers.operation.config; - -import lombok.Getter; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Configuration; - - -@Getter -@Configuration -public class GenerationConfig { - @Value("${sopt.current.generation}") - private int currentGeneration; -} diff --git a/src/main/java/org/sopt/makers/operation/config/SwaggerConfig.java b/src/main/java/org/sopt/makers/operation/config/SwaggerConfig.java deleted file mode 100644 index 626fc3ec..00000000 --- a/src/main/java/org/sopt/makers/operation/config/SwaggerConfig.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.sopt.makers.operation.config; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.EnableWebMvc; - -import springfox.documentation.builders.ApiInfoBuilder; -import springfox.documentation.builders.PathSelectors; -import springfox.documentation.builders.RequestHandlerSelectors; -import springfox.documentation.service.ApiInfo; -import springfox.documentation.service.ApiKey; -import springfox.documentation.service.AuthorizationScope; -import springfox.documentation.service.SecurityReference; -import springfox.documentation.spi.DocumentationType; -import springfox.documentation.spi.service.contexts.SecurityContext; -import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; - -@Configuration -@EnableSwagger2 -@EnableWebMvc -public class SwaggerConfig { - - private ApiInfo swaggerInfo() { - return new ApiInfoBuilder().title("Makers Operation API") - .description("Makers Operation API Docs").build(); - } - - @Bean - public Docket swaggerApi() { - return new Docket(DocumentationType.SWAGGER_2) - .securityContexts(List.of(securityContext())) - .securitySchemes(List.of(apiKey())) - .consumes(getConsumeContentTypes()) - .produces(getProduceContentTypes()) - .apiInfo(swaggerInfo()).select() - .apis(RequestHandlerSelectors.basePackage("org.sopt.makers.operation.controller")) - .paths(PathSelectors.any()) - .build() - .useDefaultResponseMessages(false); - } - - private Set getConsumeContentTypes() { - Set consumes = new HashSet<>(); - consumes.add("application/json"); - return consumes; - } - - private Set getProduceContentTypes() { - Set produces = new HashSet<>(); - produces.add("application/json"); - return produces; - } - - private SecurityContext securityContext() { - return SecurityContext.builder() - .securityReferences(defaultAuth()) - .build(); - } - - private List defaultAuth() { - AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); - AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; - authorizationScopes[0] = authorizationScope; - return List.of(new SecurityReference("Authorization", authorizationScopes)); - } - - private ApiKey apiKey() { - return new ApiKey("Authorization", "Authorization", "header"); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/ExceptionController.java b/src/main/java/org/sopt/makers/operation/controller/ExceptionController.java deleted file mode 100644 index 6e9e4f83..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/ExceptionController.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.sopt.makers.operation.controller; - -import static org.sopt.makers.operation.common.ApiResponse.*; -import static org.sopt.makers.operation.common.ExceptionMessage.*; -import static org.springframework.http.HttpStatus.*; - -import java.time.format.DateTimeParseException; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.exception.*; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.bind.annotation.RestControllerAdvice; -import lombok.extern.slf4j.Slf4j; - -@Slf4j -@RestControllerAdvice -public class ExceptionController { - @ExceptionHandler(AdminFailureException.class) - public ResponseEntity authFailureException (AdminFailureException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(BAD_REQUEST).body(fail(ex.getMessage())); - } - - @ExceptionHandler(TokenException.class) - public ResponseEntity tokenException (TokenException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(BAD_REQUEST).body(fail(ex.getMessage())); - } - - @ExceptionHandler(IllegalStateException.class) - public ResponseEntity illegalStateExceptionException (IllegalStateException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(BAD_REQUEST).body(fail(ex.getMessage())); - } - - @ExceptionHandler(MemberException.class) - public ResponseEntity memberException (MemberException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(NOT_FOUND).body(fail(ex.getMessage())); - } - - @ExceptionHandler(LectureException.class) - public ResponseEntity lectureException (LectureException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(NOT_FOUND).body(fail(ex.getMessage())); - } - - @ExceptionHandler(SubLectureException.class) - public ResponseEntity subLectureException (SubLectureException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(NOT_FOUND).body(fail(ex.getMessage())); - } - - @ExceptionHandler(DateTimeParseException.class) - public ResponseEntity dateTimeParseException () { - return ResponseEntity.status(BAD_REQUEST).body(fail(FAULT_DATE_FORMATTER.getName())); - } - - @ExceptionHandler(AlarmException.class) - public ResponseEntity AlarmException (AlarmException ex) { - log.error(ex.getMessage()); - return ResponseEntity.status(BAD_REQUEST).body(fail(ex.getMessage())); - } - -} diff --git a/src/main/java/org/sopt/makers/operation/controller/InternalOpenApiController.java b/src/main/java/org/sopt/makers/operation/controller/InternalOpenApiController.java deleted file mode 100644 index f2c332bd..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/InternalOpenApiController.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sopt.makers.operation.controller; - -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.member.MemberRequestDTO; -import org.sopt.makers.operation.service.MemberService; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestHeader; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/internal/api/v1") -public class InternalOpenApiController { - - private final MemberService memberService; - - @Value("${spring.secretKey.playground}") - private String playgroundKey; - - @ApiOperation(value = "회원 등록") - @PostMapping("/idp/members") - public ResponseEntity createMember( - @RequestHeader("x-api-key") String apiKey, - @RequestHeader("x-request-from") String serviceName, - @RequestBody MemberRequestDTO requestDTO - ) throws IllegalAccessException { - checkApiKey(apiKey, serviceName); - memberService.createMember(requestDTO); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_CREATE_MEMBER.getMessage())); - } - - private void checkApiKey(String apiKey, String serviceName) throws IllegalAccessException { - if (serviceName.equals("playground") && apiKey.equals(playgroundKey)) { - return; - } else { - throw new IllegalAccessException("잘못된 API Key 입니다."); - } - } - -} diff --git a/src/main/java/org/sopt/makers/operation/controller/ScheduleController.java b/src/main/java/org/sopt/makers/operation/controller/ScheduleController.java deleted file mode 100644 index 8ede0853..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/ScheduleController.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.sopt.makers.operation.controller; - -import org.sopt.makers.operation.service.LectureService; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.web.bind.annotation.RestController; - -import lombok.RequiredArgsConstructor; - -@RestController -@EnableScheduling -@RequiredArgsConstructor -public class ScheduleController { - - private final LectureService lectureService; - - @Scheduled(cron = "0 0 0 ? * SUN") - public void endLecture() { - lectureService.finishLecture(); - } - -} diff --git a/src/main/java/org/sopt/makers/operation/controller/app/AppAttendanceController.java b/src/main/java/org/sopt/makers/operation/controller/app/AppAttendanceController.java deleted file mode 100644 index 70fe59f5..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/app/AppAttendanceController.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.sopt.makers.operation.controller.app; - -import static java.util.Objects.*; -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.attendance.AttendRequestDTO; -import org.sopt.makers.operation.service.AttendanceService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; - -import java.security.Principal; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/app/attendances") -public class AppAttendanceController { - private final AttendanceService attendanceService; - @ApiOperation(value = "출석 하기") - @PostMapping("/attend") - public ResponseEntity attend(@RequestBody AttendRequestDTO requestDTO, @ApiIgnore Principal principal) { - val response = attendanceService.attend(getMemberId(principal), requestDTO); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_ATTEND.getMessage(), response)); - } - - private Long getMemberId(Principal principal) { - return nonNull(principal) ? Long.valueOf(principal.getName()) : null; - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/app/AppLectureController.java b/src/main/java/org/sopt/makers/operation/controller/app/AppLectureController.java deleted file mode 100644 index 663aa109..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/app/AppLectureController.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.sopt.makers.operation.controller.app; - - -import static java.util.Objects.*; -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.service.LectureService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; -import springfox.documentation.annotations.ApiIgnore; - -import java.security.Principal; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/app/lectures") -public class AppLectureController { - private final LectureService lectureService; - @ApiOperation(value = "단일 세미나 상태 조회") - @GetMapping - public ResponseEntity getLecture(@ApiIgnore Principal principal) { - val response = lectureService.getCurrentLecture(getMemberId(principal)); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_SINGLE_GET_LECTURE.getMessage(), response)); - } - - @ApiOperation(value = "출석 차수 조회") - @GetMapping("/round/{lectureId}") - public ResponseEntity getRound(@PathVariable("lectureId") Long lectureId) { - val response = lectureService.getCurrentLectureRound(lectureId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_LECTURE_ROUND.getMessage(), response)); - } - - private Long getMemberId(Principal principal) { - return nonNull(principal) ? Long.valueOf(principal.getName()) : null; - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/app/AppMemberController.java b/src/main/java/org/sopt/makers/operation/controller/app/AppMemberController.java deleted file mode 100644 index 52e94977..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/app/AppMemberController.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.sopt.makers.operation.controller.app; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.service.MemberService; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; -import springfox.documentation.annotations.ApiIgnore; - -import java.security.Principal; - -import static java.util.Objects.nonNull; -import static org.sopt.makers.operation.common.ResponseMessage.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/app/members") -public class AppMemberController { - private final MemberService memberService; - @ApiOperation(value = "전체 출석 정보 조회") - @GetMapping("/attendances") - public ResponseEntity getMemberTotalAttendance(@ApiIgnore Principal principal) { - val response = memberService.getMemberTotalAttendance(getMemberId(principal)); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_TOTAL_ATTENDANCE.getMessage(), response)); - } - - @ApiOperation(value = "출석 점수 조회") - @GetMapping("/score") - public ResponseEntity getScore(@ApiIgnore Principal principal) { - val response = memberService.getMemberScore(getMemberId(principal)); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_ATTENDANCE_SCORE.getMessage(), response)); - } - - private Long getMemberId(Principal principal) { - return nonNull(principal) ? Long.valueOf(principal.getName()) : null; - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/app/AppScheduleController.java b/src/main/java/org/sopt/makers/operation/controller/app/AppScheduleController.java deleted file mode 100644 index 1d187ac5..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/app/AppScheduleController.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.sopt.makers.operation.controller.app; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.service.ScheduleService; -import org.springframework.format.annotation.DateTimeFormat; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import java.time.LocalDateTime; - -import static org.sopt.makers.operation.common.ApiResponse.success; -import static org.sopt.makers.operation.common.ResponseMessage.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/app/schedules") -public class AppScheduleController { - private final ScheduleService scheduleService; - - @ApiOperation("일정 리스트 조회") - @GetMapping - public ResponseEntity getAlarms( - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start, - @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime end - ) { - val response = scheduleService.getSchedules(start, end); - return ResponseEntity.ok(success(SUCCESS_GET_SCHEDULES.getMessage(), response)); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/web/AdminController.java b/src/main/java/org/sopt/makers/operation/controller/web/AdminController.java deleted file mode 100644 index 918a7d5a..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/web/AdminController.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.sopt.makers.operation.controller.web; - -import static org.sopt.makers.operation.common.ResponseMessage.*; -import static org.springframework.http.HttpStatus.*; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.admin.*; -import org.sopt.makers.operation.service.AdminServiceImpl; -import org.sopt.makers.operation.util.Cookie; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/auth") -public class AdminController { - private final AdminServiceImpl authService; - private final Cookie cookie; - - @ApiOperation(value = "회원가입") - @PostMapping("/signup") - public ResponseEntity signup(@RequestBody SignUpRequestDTO signUpRequestDTO) { - val response = authService.signUp(signUpRequestDTO); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_SIGN_UP.getMessage(), response)); - } - - @ApiOperation(value = "로그인") - @PostMapping("/login") - public ResponseEntity login(@RequestBody LoginRequestDTO userLoginRequestDTO) { - val response = authService.login(userLoginRequestDTO); - val headers = cookie.setRefreshToken(response.refreshToken()); - - return ResponseEntity.status(OK) - .headers(headers) - .body(ApiResponse.success(SUCCESS_LOGIN_UP.getMessage(), response.loginResponseVO())); - } - - @ApiOperation(value = "토큰 재발급") - @PatchMapping("/refresh") - public ResponseEntity refresh(@CookieValue String refreshToken) { - val response = authService.refresh(refreshToken); - val headers = cookie.setRefreshToken(response.refreshToken()); - - return ResponseEntity.status(OK).headers(headers) - .body(ApiResponse.success(SUCCESS_GET_REFRESH_TOKEN.getMessage(), response.accessToken())); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/web/AlarmController.java b/src/main/java/org/sopt/makers/operation/controller/web/AlarmController.java deleted file mode 100644 index 21929a09..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/web/AlarmController.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.sopt.makers.operation.controller.web; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.alarm.AlarmSendRequestDTO; -import org.sopt.makers.operation.service.AlarmService; -import org.springframework.http.ResponseEntity; -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 static org.sopt.makers.operation.common.ResponseMessage.SUCCESS_SEND_ALARM; -import static org.sopt.makers.operation.common.ApiResponse.*; -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import java.net.URI; - -import org.sopt.makers.operation.dto.alarm.AlarmRequestDTO; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Status; -import org.springframework.data.domain.Pageable; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - -import lombok.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/alarms") -public class AlarmController { - private final AlarmService alarmService; - - @ApiOperation(value = "알림 전송") - @PostMapping("/send") - public ResponseEntity sendAlarm(@RequestBody AlarmSendRequestDTO requestDTO) { - alarmService.sendByAdmin(requestDTO); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_SEND_ALARM.getMessage())); - } - - @ApiOperation("알림 생성") - @PostMapping - public ResponseEntity createAlarm(@RequestBody AlarmRequestDTO requestDTO) { - val alarmId = alarmService.createAlarm(requestDTO); - return ResponseEntity - .created(getURI(alarmId)) - .body(success(SUCCESS_CREATE_ALARM.getMessage(), alarmId)); - } - - @ApiOperation("알림 리스트 조회") - @GetMapping - public ResponseEntity getAlarms( - @RequestParam(required = false) Integer generation, - @RequestParam(required = false) Part part, - @RequestParam(required = false) Status status, - Pageable pageable - ) { - val response = alarmService.getAlarms(generation, part, status, pageable); - return ResponseEntity.ok(success(SUCCESS_GET_ALARMS.getMessage(), response)); - } - - @ApiOperation("알림 상세 조회") - @GetMapping("/{alarmId}") - public ResponseEntity getAlarm(@PathVariable Long alarmId) { - val response = alarmService.getAlarm(alarmId); - return ResponseEntity.ok(success(SUCCESS_GET_ALARM.getMessage(), response)); - } - - @ApiOperation("알림 삭제") - @DeleteMapping("/{alarmId}") - public ResponseEntity deleteAlarm(@PathVariable Long alarmId) { - alarmService.deleteAlarm(alarmId); - return ResponseEntity.ok(success(SUCCESS_DELETE_ALARM.getMessage())); - } - - private URI getURI(Long alarmId) { - return ServletUriComponentsBuilder - .fromCurrentRequest() - .path("/{alarmId}") - .buildAndExpand(alarmId) - .toUri(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/web/AttendanceController.java b/src/main/java/org/sopt/makers/operation/controller/web/AttendanceController.java deleted file mode 100644 index e5432011..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/web/AttendanceController.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.sopt.makers.operation.controller.web; - -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.attendance.SubAttendanceUpdateRequestDTO; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.service.AttendanceService; -import org.springframework.data.domain.Pageable; -import org.springframework.http.ResponseEntity; -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.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/attendances") -public class AttendanceController { - private final AttendanceService attendanceService; - - @ApiOperation(value = "출석 상태 변경") - @PatchMapping - public ResponseEntity updateSubAttendance(@RequestBody SubAttendanceUpdateRequestDTO requestDTO) { - val response = attendanceService.updateSubAttendance(requestDTO); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_UPDATE_ATTENDANCE_STATUS.getMessage(), response)); - } - - @ApiOperation(value = "회원별 출석 정보 조회") - @GetMapping("/{memberId}") - public ResponseEntity findAttendancesByMember(@PathVariable Long memberId) { - val response = attendanceService.findAttendancesByMember(memberId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_MEMBER_ATTENDANCE.getMessage(), response)); - } - - @ApiOperation(value = "출석 점수 갱신") - @PatchMapping("/member/{memberId}") - public ResponseEntity updateMemberScore(@PathVariable Long memberId) { - val response = attendanceService.updateMemberScore(memberId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_UPDATE_MEMBER_SCORE.getMessage(), response)); - } - - @ApiOperation(value = "세션별 출석 정보 조회") - @GetMapping("/lecture/{lectureId}") - public ResponseEntity findAttendancesByLecture( - @PathVariable Long lectureId, @RequestParam(required = false) Part part, Pageable pageable) { - val response = attendanceService.findAttendancesByLecture(lectureId, part, pageable); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_ATTENDANCES.getMessage(), response)); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/web/LectureController.java b/src/main/java/org/sopt/makers/operation/controller/web/LectureController.java deleted file mode 100644 index 1fdd49db..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/web/LectureController.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.sopt.makers.operation.controller.web; - -import static org.sopt.makers.operation.common.ResponseMessage.*; - -import java.net.URI; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.dto.lecture.AttendanceRequestDTO; -import org.sopt.makers.operation.dto.lecture.LectureRequestDTO; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.service.LectureService; -import org.springframework.http.ResponseEntity; -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.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.servlet.support.ServletUriComponentsBuilder; - -import io.swagger.annotations.ApiOperation; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.val; - -@Tag(name = "Lecture", description = "세션 API") -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/lectures") -public class LectureController { - private final LectureService lectureService; - - @ApiOperation(value = "세션 생성") - @PostMapping - public ResponseEntity createLecture(@RequestBody LectureRequestDTO requestDTO) { - val lectureId = lectureService.createLecture(requestDTO); - return ResponseEntity - .created(getURI(lectureId)) - .body(ApiResponse.success(SUCCESS_CREATE_LECTURE.getMessage(), lectureId)); - } - - @ApiOperation(value = "세션 리스트 조회") - @GetMapping - public ResponseEntity getLecturesByGeneration( - @RequestParam("generation") int generation, @RequestParam(required = false) Part part) { - val response = lectureService.getLecturesByGeneration(generation, part); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_LECTURES.getMessage(), response)); - } - - @ApiOperation(value = "세션 상세 조회") - @GetMapping("/{lectureId}") - public ResponseEntity getLecture(@PathVariable Long lectureId) { - val response = lectureService.getLecture(lectureId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_LECTURE.getMessage(), response)); - } - - @ApiOperation(value = "출석 시작") - @PatchMapping("/attendance") - public ResponseEntity startAttendance(@RequestBody AttendanceRequestDTO requestDTO) { - val response = lectureService.startAttendance(requestDTO); - return ResponseEntity - .created(getURI(requestDTO.lectureId())) - .body(ApiResponse.success(SUCCESS_START_ATTENDANCE.getMessage(), response)); - } - - @ApiOperation(value = "출석 점수 갱신 트리거 (출석 종료)") - @PatchMapping("/{lectureId}") - public ResponseEntity finishLecture(@PathVariable("lectureId") Long lectureId) { - lectureService.finishLecture(lectureId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_UPDATE_MEMBER_SCORE.getMessage())); - } - - @ApiOperation(value = "세션 삭제") - @DeleteMapping("/{lectureId}") - public ResponseEntity deleteLecture(@PathVariable Long lectureId) { - lectureService.deleteLecture(lectureId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_DELETE_LECTURE.getMessage())); - } - - @ApiOperation(value = "세션 상세 조회 (팝업)") - @GetMapping("/detail/{lectureId}") - public ResponseEntity getLectureDetail(@PathVariable Long lectureId) { - val response = lectureService.getLectureDetail(lectureId); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_LECTURE.getMessage(), response)); - } - - private URI getURI(Long lectureId) { - return ServletUriComponentsBuilder - .fromCurrentRequest() - .path("/{lectureId}") - .buildAndExpand(lectureId) - .toUri(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/controller/web/MemberController.java b/src/main/java/org/sopt/makers/operation/controller/web/MemberController.java deleted file mode 100644 index 961dd544..00000000 --- a/src/main/java/org/sopt/makers/operation/controller/web/MemberController.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.sopt.makers.operation.controller.web; - -import io.swagger.annotations.ApiOperation; -import lombok.RequiredArgsConstructor; -import lombok.val; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.service.MemberService; -import org.springframework.data.domain.Pageable; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.*; - -import static org.sopt.makers.operation.common.ResponseMessage.SUCCESS_GET_MEMBERS; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/api/v1/members") -public class MemberController { - private final MemberService memberService; - - @ApiOperation(value = "멤버 리스트 조회") - @GetMapping("/list") - public ResponseEntity getMemberList( - @RequestParam(required = false) Part part, @RequestParam(required = false) Integer generation, Pageable pageable) { - val memberList = memberService.getMemberList(part, generation, pageable); - return ResponseEntity.ok(ApiResponse.success(SUCCESS_GET_MEMBERS.getMessage(), memberList)); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/admin/LoginRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/admin/LoginRequestDTO.java deleted file mode 100644 index 6cfdf3a1..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/admin/LoginRequestDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.sopt.makers.operation.dto.admin; - -public record LoginRequestDTO(String email, String password) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/admin/LoginResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/admin/LoginResponseDTO.java deleted file mode 100644 index 64fb28c3..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/admin/LoginResponseDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sopt.makers.operation.dto.admin; - -import lombok.Builder; -import org.sopt.makers.operation.entity.Admin; -import org.sopt.makers.operation.entity.AdminStatus; - -@Builder -public record LoginResponseDTO(LoginResponseVO loginResponseVO, String refreshToken) { - public static LoginResponseDTO of(Admin admin, String accessToken, String refreshToken) { - return LoginResponseDTO.builder() - .loginResponseVO(LoginResponseVO.of(admin.getId(), admin.getName(), admin.getStatus(), accessToken)) - .refreshToken(refreshToken) - .build(); - } -} - -@Builder -record LoginResponseVO(Long id, String name, AdminStatus adminStatus, String accessToken) { - public static LoginResponseVO of(Long id, String name, AdminStatus adminStatus, String accessToken) { - return LoginResponseVO.builder() - .id(id) - .name(name) - .adminStatus(adminStatus) - .accessToken(accessToken) - .build(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/admin/RefreshResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/admin/RefreshResponseDTO.java deleted file mode 100644 index bced1f4d..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/admin/RefreshResponseDTO.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.operation.dto.admin; - -public record RefreshResponseDTO(String accessToken, String refreshToken) { - public static RefreshResponseDTO of(String accessToken, String refreshToken) { - return new RefreshResponseDTO( - accessToken, - refreshToken - ); - } -} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/dto/admin/SignUpRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/admin/SignUpRequestDTO.java deleted file mode 100644 index 9ef629b9..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/admin/SignUpRequestDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.makers.operation.dto.admin; - -import org.sopt.makers.operation.entity.Role; - -public record SignUpRequestDTO( - String email, - String password, - String name, - Role role -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/admin/SignUpResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/admin/SignUpResponseDTO.java deleted file mode 100644 index a2dd5574..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/admin/SignUpResponseDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.makers.operation.dto.admin; - -import org.sopt.makers.operation.entity.Role; - -public record SignUpResponseDTO( - Long id, - String email, - String name, - Role role -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmInactiveListResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmInactiveListResponseDTO.java deleted file mode 100644 index 9d1cdcfd..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmInactiveListResponseDTO.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -import java.util.List; - -public record AlarmInactiveListResponseDTO(List memberIds) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmRequestDTO.java deleted file mode 100644 index cf1972ce..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmRequestDTO.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -import java.util.List; - -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Alarm; -import org.sopt.makers.operation.entity.alarm.Attribute; - -public record AlarmRequestDTO( - int generation, - int generationAt, - Attribute attribute, - String title, - String content, - String link, - Boolean isActive, - Part part, - List targetList -) { - public Alarm toEntity() { - return new Alarm(this); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmResponseDTO.java deleted file mode 100644 index ee5e86d1..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmResponseDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -import static java.util.Objects.*; - -import org.sopt.makers.operation.entity.alarm.Alarm; - -import lombok.Builder; - -@Builder -public record AlarmResponseDTO( - String attribute, - String part, - Boolean isActive, - String title, - String content, - String link, - String createdAt, - String sendAt -) { - public static AlarmResponseDTO of(Alarm alarm) { - return AlarmResponseDTO.builder() - .attribute(alarm.getAttribute().getName()) - .part(nonNull(alarm.getPart()) ? alarm.getPart().getName() : null) - .isActive(alarm.getIsActive()) - .title(alarm.getTitle()) - .content(alarm.getContent()) - .link(alarm.getLink()) - .createdAt(alarm.getCreatedDate().toString()) - .sendAt(nonNull(alarm.getSendAt()) ? alarm.getSendAt().toString() : null) - .build(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendRequestDTO.java deleted file mode 100644 index c02f3278..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendRequestDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -public record AlarmSendRequestDTO(Long alarmId) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendResponseDTO.java deleted file mode 100644 index 7b4d9f87..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSendResponseDTO.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -public record AlarmSendResponseDTO( - int status, - boolean success, - String message -) { -} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSenderDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSenderDTO.java deleted file mode 100644 index d9889844..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmSenderDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -import java.util.List; - -import org.sopt.makers.operation.entity.alarm.Alarm; -import org.sopt.makers.operation.entity.alarm.Attribute; - -import lombok.Builder; - -@Builder -public record AlarmSenderDTO( - String title, - String content, - List targetList, - Attribute attribute, - String link -) { - public static AlarmSenderDTO of (Alarm alarm, List targetList) { - return AlarmSenderDTO.builder() - .title(alarm.getTitle()) - .content(alarm.getContent()) - .targetList(targetList) - .attribute(alarm.getAttribute()) - .link(alarm.getLink()) - .build(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmsResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmsResponseDTO.java deleted file mode 100644 index 0f90fa92..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/alarm/AlarmsResponseDTO.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.sopt.makers.operation.dto.alarm; - -import static java.util.Objects.*; - -import java.util.List; - -import org.sopt.makers.operation.entity.alarm.Alarm; - -import lombok.Builder; - -public record AlarmsResponseDTO( - List alarms, - int totalCount -) { - public static AlarmsResponseDTO of(List alarms, int totalCount) { - return new AlarmsResponseDTO(alarms.stream().map(AlarmVO::of).toList(), totalCount); - } - - @Builder - record AlarmVO( - Long alarmId, - String part, - String attribute, - String title, - String content, - String sendAt, - String status - ) { - static AlarmVO of(Alarm alarm) { - return AlarmVO.builder() - .alarmId(alarm.getId()) - .part(nonNull(alarm.getPart()) ? alarm.getPart().getName() : null) - .attribute(alarm.getAttribute().getName()) - .title(alarm.getTitle()) - .content(alarm.getContent()) - .sendAt(nonNull(alarm.getSendAt()) ? alarm.getSendAt().toString() : null) - .status(alarm.getStatus().getName()) - .build(); - } - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendRequestDTO.java deleted file mode 100644 index baa571f1..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendRequestDTO.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -public record AttendRequestDTO( - Long subLectureId, - String code -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendResponseDTO.java deleted file mode 100644 index 2f392886..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendResponseDTO.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -public record AttendResponseDTO( - Long subLectureId -) { - public static AttendResponseDTO of(Long subLectureId){ - return new AttendResponseDTO( - subLectureId - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceMemberResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceMemberResponseDTO.java deleted file mode 100644 index ed0909b9..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceMemberResponseDTO.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import java.util.List; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.SubAttendance; - -public record AttendanceMemberResponseDTO( - String name, - float score, - String part, - String university, - String phone, - List lectures -) { - public static AttendanceMemberResponseDTO of(Member member, List attendances) { - return new AttendanceMemberResponseDTO( - member.getName(), - member.getScore(), - member.getPart().getName(), - member.getUniversity(), - member.getPhone(), - attendances.stream().map(LectureVO::of).toList()); - } -} - -record LectureVO( - String lecture, - float additiveScore, - String status, - List attendances -) { - public static LectureVO of(Attendance attendance) { - return new LectureVO( - attendance.getLecture().getName(), - attendance.getScore(), - attendance.getStatus().getName(), - attendance.getSubAttendances().stream().map(AttendanceVO::of).toList()); - } -} - -record AttendanceVO( - int round, - String status, - String date -) { - public static AttendanceVO of(SubAttendance subAttendance) { - return new AttendanceVO( - subAttendance.getSubLecture().getRound(), - subAttendance.getStatus().getName(), - subAttendance.getLastModifiedDate().toString() - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalCountVO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalCountVO.java deleted file mode 100644 index c88a2df0..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalCountVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -public record AttendanceTotalCountVO( - int attendance, - int absent, - int tardy, - int participate -) { - public static AttendanceTotalCountVO of(int attendance, int absent, int tardy, int participate){ - return new AttendanceTotalCountVO( - attendance, - absent, - tardy, - participate - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalResponseDTO.java deleted file mode 100644 index 13cecdd6..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendanceTotalResponseDTO.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; - -import java.util.List; - -public record AttendanceTotalResponseDTO( - Part part, - int generation, - String name, - float score, - AttendanceTotalCountVO total, - List attendances -) -{ - public static AttendanceTotalResponseDTO of(Member member, AttendanceTotalCountVO total, List attendances){ - return new AttendanceTotalResponseDTO( - member.getPart(), - member.getGeneration(), - member.getName(), - member.getScore(), - total, - attendances - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendancesResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/AttendancesResponseDTO.java deleted file mode 100644 index 10852bf6..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/AttendancesResponseDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import java.util.List; - -import org.sopt.makers.operation.entity.Attendance; - -public record AttendancesResponseDTO( - List attendances, - int totalCount -) { - public static AttendancesResponseDTO of(List attendances, int totalCount) { - return new AttendancesResponseDTO( - attendances.stream().map(MemberResponseDTO::of).toList(), - totalCount); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/MemberResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/MemberResponseDTO.java deleted file mode 100644 index 39e24970..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/MemberResponseDTO.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import java.util.List; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.SubAttendance; - -public record MemberResponseDTO ( - Long attendanceId, - MemberVO member, - List attendances, - float updatedScore -) { - - public static MemberResponseDTO of(Attendance attendance) { - return new MemberResponseDTO( - attendance.getId(), - MemberVO.of(attendance.getMember()), - attendance.getSubAttendances().stream().map(SubAttendanceVO::of).toList(), - attendance.getScore()); - } -} - -record MemberVO(Long memberId, String name, String university, String part) { - static MemberVO of(Member member) { - return new MemberVO( - member.getId(), - member.getName(), - member.getUniversity(), - member.getPart().getName()); - } -} - -record SubAttendanceVO(Long subAttendanceId, int round, AttendanceStatus status, String updateAt) { - static SubAttendanceVO of(SubAttendance subAttendance) { - return new SubAttendanceVO( - subAttendance.getId(), - subAttendance.getSubLecture().getRound(), - subAttendance.getStatus(), - subAttendance.getLastModifiedDate().toString()); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateRequestDTO.java deleted file mode 100644 index ba430b0d..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateRequestDTO.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import org.sopt.makers.operation.entity.AttendanceStatus; - -public record SubAttendanceUpdateRequestDTO( - Long subAttendanceId, - AttendanceStatus status -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateResponseDTO.java deleted file mode 100644 index 482b529a..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/attendance/SubAttendanceUpdateResponseDTO.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.makers.operation.dto.attendance; - -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.SubAttendance; - -public record SubAttendanceUpdateResponseDTO( - Long subAttendanceId, - AttendanceStatus status -) { - public static SubAttendanceUpdateResponseDTO of(SubAttendance subAttendance) { - return new SubAttendanceUpdateResponseDTO(subAttendance.getId(), subAttendance.getStatus()); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceRequestDTO.java deleted file mode 100644 index 8afd941a..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceRequestDTO.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -public record AttendanceRequestDTO( - Long lectureId, - int round, - String code -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceResponseDTO.java deleted file mode 100644 index 9376a41a..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendanceResponseDTO.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -public record AttendanceResponseDTO(Long lectureId, Long subLectureId) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendancesStatusVO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/AttendancesStatusVO.java deleted file mode 100644 index b2a33c87..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/AttendancesStatusVO.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import static org.sopt.makers.operation.entity.AttendanceStatus.*; - -import java.time.LocalDateTime; - -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.lecture.Lecture; - -import lombok.Builder; - -@Builder -public record AttendancesStatusVO( - int attendance, - int absent, - int tardy, - int unknown -) { - public static AttendancesStatusVO of(Lecture lecture) { - return AttendancesStatusVO.builder() - .attendance(getCount(lecture, ATTENDANCE)) - .absent(isEnd(lecture) ? getCount(lecture, ABSENT) : 0) - .tardy(getCount(lecture, TARDY)) - .unknown(isEnd(lecture) ? 0 : getCount(lecture, ABSENT)) - .build(); - } - - public static int getCount(Lecture lecture, AttendanceStatus status) { - return (int)lecture.getAttendances().stream() - .filter(attendance -> attendance.getStatus().equals(status)) - .count(); - } - - public static boolean isEnd(Lecture lecture) { - return lecture.getEndDate().isBefore(LocalDateTime.now()); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureCurrentRoundResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LectureCurrentRoundResponseDTO.java deleted file mode 100644 index d07f21c2..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureCurrentRoundResponseDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import org.sopt.makers.operation.entity.SubLecture; - -public record LectureCurrentRoundResponseDTO( - Long id, - int round -) { - public static LectureCurrentRoundResponseDTO of(SubLecture subLecture){ - return new LectureCurrentRoundResponseDTO( - subLecture.getId(), - subLecture.getRound() - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureGetResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LectureGetResponseDTO.java deleted file mode 100644 index 7b1f9696..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureGetResponseDTO.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import lombok.Builder; -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.SubAttendance; -import org.sopt.makers.operation.entity.lecture.Lecture; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.List; -import java.util.stream.Collectors; - -@Builder -public record LectureGetResponseDTO( - LectureResponseType type, - Long id, - String location, - String name, - String startDate, - String endDate, - String message, - List attendances -) { - public static LectureGetResponseDTO of(LectureResponseType type, Lecture lecture, String message, List attendances) { - - return LectureGetResponseDTO.builder() - .type(type) - .id(lecture.getId()) - .location(lecture.getPlace()) - .name(lecture.getName()) - .startDate(lecture.getStartDate().format(convertFormat())) - .endDate(lecture.getEndDate().format(convertFormat())) - .message(message) - .attendances(attendances.stream() - .map(subAttendance -> LectureGetResponseVO.of(subAttendance.getStatus(), subAttendance.getLastModifiedDate())) - .collect(Collectors.toList())) - .build(); - } - - private static DateTimeFormatter convertFormat() { - return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); - } -} - -@Builder -record LectureGetResponseVO( - AttendanceStatus status, - String attendedAt - -) { - public static LectureGetResponseVO of(AttendanceStatus status, LocalDateTime attendedAt) { - return LectureGetResponseVO.builder() - .status(status) - .attendedAt(attendedAt.format((convertFormat()))) - .build(); - } - - private static DateTimeFormatter convertFormat() { - return DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LectureRequestDTO.java deleted file mode 100644 index 970f1f76..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureRequestDTO.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import java.time.LocalDateTime; -import java.time.format.DateTimeParseException; - -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.sopt.makers.operation.entity.Part; - -import lombok.*; - -public record LectureRequestDTO( - @NonNull Part part, - @NonNull String name, - int generation, - String place, - String startDate, - String endDate, - @NonNull Attribute attribute -) { - - public Lecture toEntity() { - return Lecture.builder() - .name(this.name) - .part(this.part) - .generation(this.generation) - .place(this.place) - .startDate(convertLocalDateTime(this.startDate)) - .endDate(convertLocalDateTime(this.endDate)) - .attribute(this.attribute) - .build(); - } - - private LocalDateTime convertLocalDateTime(String date) throws DateTimeParseException { - return LocalDateTime.parse(date); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseDTO.java deleted file mode 100644 index 69e90313..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureResponseDTO.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import static java.util.Objects.*; -import java.util.List; - -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.SubLecture; -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.sopt.makers.operation.entity.lecture.LectureStatus; - -import lombok.*; - -@Builder -public record LectureResponseDTO( - Long lectureId, - String name, - int generation, - Part part, - Attribute attribute, - List subLectures, - AttendancesStatusVO attendances, - LectureStatus status - -) { - public static LectureResponseDTO of(Lecture lecture) { - return LectureResponseDTO.builder() - .lectureId(lecture.getId()) - .name(lecture.getName()) - .generation(lecture.getGeneration()) - .part(lecture.getPart()) - .attribute(lecture.getAttribute()) - .subLectures(lecture.getSubLectures().stream().map(SubLectureVO::of).toList()) - .attendances(AttendancesStatusVO.of(lecture)) - .status(lecture.getLectureStatus()) - .build(); - } -} - -record SubLectureVO( - Long subLectureId, - int round, - String startAt, - String code -) { - static SubLectureVO of(SubLecture subLecture) { - val startAt = nonNull(subLecture.getStartAt()) ? subLecture.getStartAt().toString() : null; - return new SubLectureVO(subLecture.getId(), subLecture.getRound(), startAt, subLecture.getCode()); - } -} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureSearchCondition.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LectureSearchCondition.java deleted file mode 100644 index 3ac25110..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LectureSearchCondition.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; - -public record LectureSearchCondition(Part part, int generation, Long memberId) { - public static LectureSearchCondition of(Member member) { - return new LectureSearchCondition( - member.getPart(), - member.getGeneration(), - member.getId() - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/lecture/LecturesResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/lecture/LecturesResponseDTO.java deleted file mode 100644 index 253a9f25..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/lecture/LecturesResponseDTO.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.sopt.makers.operation.dto.lecture; - -import java.util.List; - -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.entity.lecture.Lecture; - -import lombok.*; - -public record LecturesResponseDTO( - int generation, - List lectures -) { - - public static LecturesResponseDTO of(int generation, List lectures) { - return new LecturesResponseDTO( - generation, - lectures.stream().map(LectureVO::of).toList() - ); - } -} - -@Builder -record LectureVO( - Long lectureId, - String name, - Part partValue, - String partName, - String startDate, - String endDate, - Attribute attributeValue, - String attributeName, - String place, - AttendancesStatusVO attendances -) { - public static LectureVO of(Lecture lecture) { - return LectureVO.builder() - .lectureId(lecture.getId()) - .name(lecture.getName()) - .partValue(lecture.getPart()) - .partName(lecture.getPart().getName()) - .startDate(lecture.getStartDate().toString()) - .endDate(lecture.getEndDate().toString()) - .attributeValue(lecture.getAttribute()) - .attributeName(lecture.getAttribute().getName()) - .place(lecture.getPlace()) - .attendances(AttendancesStatusVO.of(lecture)) - .build(); - } -} - diff --git a/src/main/java/org/sopt/makers/operation/dto/member/MemberListGetResponse.java b/src/main/java/org/sopt/makers/operation/dto/member/MemberListGetResponse.java deleted file mode 100644 index db40b34e..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/member/MemberListGetResponse.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.sopt.makers.operation.dto.member; - -import org.sopt.makers.operation.dto.attendance.AttendanceTotalCountVO; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; - -public record MemberListGetResponse( - Long id, - String name, - String university, - Part part, - float score, - AttendanceTotalCountVO total -) -{ - public static MemberListGetResponse of(Member member, AttendanceTotalCountVO total){ - return new MemberListGetResponse( - member.getId(), - member.getName(), - member.getUniversity(), - member.getPart(), - member.getScore(), - total - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/member/MemberRequestDTO.java b/src/main/java/org/sopt/makers/operation/dto/member/MemberRequestDTO.java deleted file mode 100644 index 423bb1f1..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/member/MemberRequestDTO.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.sopt.makers.operation.dto.member; - -import org.sopt.makers.operation.entity.ObYb; -import org.sopt.makers.operation.entity.Part; - -public record MemberRequestDTO( - Long playgroundId, - String name, - int generation, - ObYb obyb, - Part part, - String university, - String phone -) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/member/MemberScoreGetResponse.java b/src/main/java/org/sopt/makers/operation/dto/member/MemberScoreGetResponse.java deleted file mode 100644 index 7e1d56a8..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/member/MemberScoreGetResponse.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.makers.operation.dto.member; - -public record MemberScoreGetResponse( - float score -) { - public static MemberScoreGetResponse of(float score){ - return new MemberScoreGetResponse( - score - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/member/MemberSearchCondition.java b/src/main/java/org/sopt/makers/operation/dto/member/MemberSearchCondition.java deleted file mode 100644 index f4636d28..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/member/MemberSearchCondition.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.makers.operation.dto.member; - -import org.sopt.makers.operation.entity.Part; - -public record MemberSearchCondition(Part part, int generation) { -} diff --git a/src/main/java/org/sopt/makers/operation/dto/member/MembersResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/member/MembersResponseDTO.java deleted file mode 100644 index 07b81d1e..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/member/MembersResponseDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sopt.makers.operation.dto.member; - -import java.util.List; - -public record MembersResponseDTO( - List members, - int totalCount -) { - public static MembersResponseDTO of(List members, int totalCount) { - return new MembersResponseDTO(members, totalCount); - } -} diff --git a/src/main/java/org/sopt/makers/operation/dto/schedule/SchedulesResponseDTO.java b/src/main/java/org/sopt/makers/operation/dto/schedule/SchedulesResponseDTO.java deleted file mode 100644 index 9c83d9b3..00000000 --- a/src/main/java/org/sopt/makers/operation/dto/schedule/SchedulesResponseDTO.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.sopt.makers.operation.dto.schedule; - -import lombok.*; -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.entity.schedule.Schedule; - -import java.time.LocalDate; -import java.time.format.TextStyle; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -public record SchedulesResponseDTO( - List dates -) { - public static SchedulesResponseDTO of(Map> scheduleMap) { - return new SchedulesResponseDTO( - scheduleMap.keySet().stream().sorted() - .map(key -> DateVO.of(key, scheduleMap.get(key))) - .toList() - ); - } - - @Builder - record DateVO( - String date, - String dayOfWeek, - List schedules - ) { - static DateVO of(LocalDate date, List schedules) { - return DateVO.builder() - .date(date.toString()) - .dayOfWeek(date.getDayOfWeek().getDisplayName(TextStyle.SHORT, Locale.KOREAN)) - .schedules(schedules.stream().map(ScheduleVO::of).toList()) - .build(); - } - } - - @Builder - record ScheduleVO( - Long scheduleId, - String startDate, - String endDate, - Attribute attribute, - String title - ) { - static ScheduleVO of(Schedule schedule) { - return ScheduleVO.builder() - .scheduleId(schedule.getId()) - .startDate(schedule.getStartDate().toString()) - .endDate(schedule.getEndDate().toString()) - .attribute(schedule.getAttribute()) - .title(schedule.getTitle()) - .build(); - } - } -} diff --git a/src/main/java/org/sopt/makers/operation/entity/AttendanceStatus.java b/src/main/java/org/sopt/makers/operation/entity/AttendanceStatus.java deleted file mode 100644 index acd573cb..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/AttendanceStatus.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sopt.makers.operation.entity; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -@AllArgsConstructor -@Getter -public enum AttendanceStatus { - ATTENDANCE("출석"), ABSENT("결석"), TARDY("지각"), LEAVE_EARLY("조퇴"), PARTICIPATE("참여"), NOT_PARTICIPATE("미참여"); - - final String name; -} diff --git a/src/main/java/org/sopt/makers/operation/entity/Member.java b/src/main/java/org/sopt/makers/operation/entity/Member.java deleted file mode 100644 index 6cf13b7e..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/Member.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.sopt.makers.operation.entity; - -import static org.sopt.makers.operation.entity.lecture.LectureStatus.*; - -import java.util.ArrayList; -import java.util.List; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -import org.sopt.makers.operation.dto.member.MemberRequestDTO; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@NoArgsConstructor -@Getter -public class Member { - - @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "member_id") - private Long id; - - private Long playgroundId; - - private String name; - private int generation; - - @Enumerated(EnumType.STRING) - private ObYb obyb; - - @Enumerated(EnumType.STRING) - private Part part; - - private String university; - private float score; - private String phone; - - @OneToMany(mappedBy = "member") - List attendances = new ArrayList<>(); - - public Member(MemberRequestDTO requestDTO) { - this.playgroundId = requestDTO.playgroundId(); - this.name = requestDTO.name(); - this.generation = requestDTO.generation(); - this.obyb = requestDTO.obyb(); - this.part = requestDTO.part(); - this.university = requestDTO.university(); - this.phone = requestDTO.phone(); - this.score = 2; - } - - public void updateScore(float score) { - this.score += score; - } - - public void updateTotalScore() { - this.score = (float) (2 + this.attendances.stream() - .filter(attendance -> attendance.getLecture().getLectureStatus().equals(END)) - .mapToDouble(Attendance::getScore) - .sum()); - } -} diff --git a/src/main/java/org/sopt/makers/operation/entity/ObYb.java b/src/main/java/org/sopt/makers/operation/entity/ObYb.java deleted file mode 100644 index 2103662d..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/ObYb.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.sopt.makers.operation.entity; - -public enum ObYb { - OB, YB -} diff --git a/src/main/java/org/sopt/makers/operation/entity/alarm/Alarm.java b/src/main/java/org/sopt/makers/operation/entity/alarm/Alarm.java deleted file mode 100644 index 7ae69ccd..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/alarm/Alarm.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.sopt.makers.operation.entity.alarm; - -import static java.util.Objects.*; -import static javax.persistence.EnumType.*; -import static javax.persistence.GenerationType.*; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import javax.persistence.Column; -import javax.persistence.Convert; -import javax.persistence.Entity; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; - -import org.sopt.makers.operation.converter.StringListConverter; -import org.sopt.makers.operation.dto.alarm.AlarmRequestDTO; -import org.sopt.makers.operation.entity.BaseEntity; -import org.sopt.makers.operation.entity.Part; - -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@NoArgsConstructor -@Getter -public class Alarm extends BaseEntity { - @Id - @GeneratedValue(strategy = IDENTITY) - @Column(name = "alarm_id") - private Long id; - - private int generation; - - private int generationAt; - - @Column(nullable = false) - @Enumerated(value = STRING) - private Attribute attribute; - - private String title; - - @Column(columnDefinition = "TEXT") - private String content; - - private String link; - - private Boolean isActive; - - @Enumerated(value = STRING) - private Part part; - - @Column(columnDefinition = "TEXT", nullable = false) - @Convert(converter = StringListConverter.class) - private List targetList; - - @Column(nullable = false) - @Enumerated(value = STRING) - private Status status; - - private LocalDateTime sendAt; - - public void updateStatus() { - this.status = Status.AFTER; - } - - public void updateSendAt() { - this.sendAt = LocalDateTime.now(); - } - - public Alarm(AlarmRequestDTO requestDTO) { - this.generation = requestDTO.generation(); - this.generationAt = requestDTO.generationAt(); - this.attribute = requestDTO.attribute(); - this.title = requestDTO.title(); - this.content = requestDTO.content(); - if (nonNull(requestDTO.link())) { - this.link = requestDTO.link(); - } - if (nonNull(requestDTO.isActive())) { - this.isActive = requestDTO.isActive(); - } - if (nonNull(requestDTO.part())) { - this.part = requestDTO.part(); - } - this.targetList = nonNull(requestDTO.targetList()) ? requestDTO.targetList() : new ArrayList<>(); - this.status = Status.BEFORE; - } -} diff --git a/src/main/java/org/sopt/makers/operation/entity/lecture/Lecture.java b/src/main/java/org/sopt/makers/operation/entity/lecture/Lecture.java deleted file mode 100644 index 29201733..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/lecture/Lecture.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.sopt.makers.operation.entity.lecture; - -import static javax.persistence.GenerationType.*; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; -import javax.persistence.OneToMany; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.BaseEntity; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.SubLecture; - -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@NoArgsConstructor -@Getter -public class Lecture extends BaseEntity { - - @Id @GeneratedValue(strategy = IDENTITY) - @Column(name = "lecture_id") - private Long id; - - private String name; - - @Enumerated(EnumType.STRING) - private Part part; - - private int generation; - - private String place; - - private LocalDateTime startDate; - - private LocalDateTime endDate; - - @Enumerated(EnumType.STRING) - private Attribute attribute; - - @Enumerated(EnumType.STRING) - private LectureStatus lectureStatus; - - @OneToMany(mappedBy = "lecture") - List subLectures = new ArrayList<>(); - - @OneToMany(mappedBy = "lecture") - List attendances = new ArrayList<>(); - - @Builder - public Lecture(String name, Part part, int generation, String place, LocalDateTime startDate, LocalDateTime endDate, - Attribute attribute) { - this.name = name; - this.part = part; - this.generation = generation; - this.place = place; - this.startDate = startDate; - this.endDate = endDate; - this.attribute = attribute; - this.lectureStatus = LectureStatus.BEFORE; - } - - public void updateStatus(LectureStatus status) { - this.lectureStatus = status; - } - - public void finish() { - this.lectureStatus = LectureStatus.END; - attendances.forEach(Attendance::updateMemberScore); - } -} diff --git a/src/main/java/org/sopt/makers/operation/entity/schedule/Schedule.java b/src/main/java/org/sopt/makers/operation/entity/schedule/Schedule.java deleted file mode 100644 index b376878b..00000000 --- a/src/main/java/org/sopt/makers/operation/entity/schedule/Schedule.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.sopt.makers.operation.entity.schedule; - -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.sopt.makers.operation.entity.BaseEntity; -import org.sopt.makers.operation.entity.lecture.Attribute; - -import javax.persistence.*; -import java.time.LocalDateTime; - -import static javax.persistence.EnumType.STRING; -import static javax.persistence.GenerationType.IDENTITY; - -@Entity -@NoArgsConstructor -@Getter -public class Schedule extends BaseEntity { - @Id - @GeneratedValue(strategy = IDENTITY) - @Column(name = "schedule_id") - private Long id; - - private LocalDateTime startDate; - - private LocalDateTime endDate; - - @Column(nullable = false) - @Enumerated(value = STRING) - private Attribute attribute; - - private String title; - - private String location; -} diff --git a/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java b/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java deleted file mode 100644 index 6e254a7d..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/AdminFailureException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.exception; - -public class AdminFailureException extends RuntimeException { - public AdminFailureException(String message) { - super("[AuthFailureException] : " + message); - } -} diff --git a/src/main/java/org/sopt/makers/operation/exception/AlarmException.java b/src/main/java/org/sopt/makers/operation/exception/AlarmException.java deleted file mode 100644 index d27e1e2c..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/AlarmException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.exception; - -public class AlarmException extends RuntimeException { - public AlarmException(String message) { - super("[AlarmException] : " + message); - } -} diff --git a/src/main/java/org/sopt/makers/operation/exception/LectureException.java b/src/main/java/org/sopt/makers/operation/exception/LectureException.java deleted file mode 100644 index d5040bf5..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/LectureException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.exception; - -public class LectureException extends RuntimeException { - public LectureException(String message) { - super("[LectureException] : " + message); - } -} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/exception/MemberException.java b/src/main/java/org/sopt/makers/operation/exception/MemberException.java deleted file mode 100644 index 9aceb62e..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/MemberException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.exception; - -public class MemberException extends RuntimeException { - public MemberException(String message) { - super("[MemberException] : " + message); - } -} diff --git a/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java b/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java deleted file mode 100644 index 3d9d4612..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/SubLectureException.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.exception; - -public class SubLectureException extends RuntimeException { - public SubLectureException(String message) { - super("[SubLectureException] : " + message); - } -} diff --git a/src/main/java/org/sopt/makers/operation/exception/TokenException.java b/src/main/java/org/sopt/makers/operation/exception/TokenException.java deleted file mode 100644 index a1bcc83f..00000000 --- a/src/main/java/org/sopt/makers/operation/exception/TokenException.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.sopt.makers.operation.exception; -public class TokenException extends RuntimeException { - public TokenException(String message) { - super("[TokenException] : " + message); - } -} diff --git a/src/main/java/org/sopt/makers/operation/external/api/AlarmSender.java b/src/main/java/org/sopt/makers/operation/external/api/AlarmSender.java deleted file mode 100644 index e45a2fe5..00000000 --- a/src/main/java/org/sopt/makers/operation/external/api/AlarmSender.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.external.api; - -import org.sopt.makers.operation.dto.alarm.AlarmSenderDTO; - -public interface AlarmSender { - void send(AlarmSenderDTO alarmSenderDTO); -} diff --git a/src/main/java/org/sopt/makers/operation/external/api/PlayGroundServer.java b/src/main/java/org/sopt/makers/operation/external/api/PlayGroundServer.java deleted file mode 100644 index 0c11ef79..00000000 --- a/src/main/java/org/sopt/makers/operation/external/api/PlayGroundServer.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.makers.operation.external.api; - -import org.sopt.makers.operation.dto.alarm.AlarmInactiveListResponseDTO; -import org.sopt.makers.operation.entity.Part; - -public interface PlayGroundServer { - AlarmInactiveListResponseDTO getInactiveMembers(int generation, Part part); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmCustomRepository.java b/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmCustomRepository.java deleted file mode 100644 index 70dc812c..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/alarm/AlarmCustomRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.sopt.makers.operation.repository.alarm; - -import java.util.List; - -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Alarm; -import org.sopt.makers.operation.entity.alarm.Status; -import org.springframework.data.domain.Pageable; - -public interface AlarmCustomRepository { - List getAlarms(Integer generation, Part part, Status status, Pageable pageable); - int countByGenerationAndPartAndStatus(int generation, Part part, Status status); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceCustomRepository.java b/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceCustomRepository.java deleted file mode 100644 index dcbd655e..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceCustomRepository.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.sopt.makers.operation.repository.attendance; - -import java.util.List; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.SubAttendance; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.springframework.data.domain.Pageable; - -public interface AttendanceCustomRepository { - List findAttendanceByMemberId(Long memberId); - List findByLecture(Long lectureId, Part part, Pageable pageable); - List findByMember(Member member); - List findCurrentAttendanceByMember(Long playGroundId); - List findSubAttendanceByAttendanceId(Long attendanceId); - int countByLectureIdAndPart(long lectureId, Part part); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepository.java b/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepository.java deleted file mode 100644 index 3e5cc2f6..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/attendance/AttendanceRepository.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.sopt.makers.operation.repository.attendance; - -import org.sopt.makers.operation.entity.Attendance; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; - -import java.util.Optional; - -public interface AttendanceRepository extends JpaRepository, AttendanceCustomRepository { - Optional findAttendanceByLectureIdAndMemberId(Long lectureId, Long memberId); - - @Modifying(clearAutomatically = true, flushAutomatically = true) - @Query("delete from Attendance a where a.lecture = :lecture") - void deleteAllByLecture(Lecture lecture); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureCustomRepository.java b/src/main/java/org/sopt/makers/operation/repository/lecture/LectureCustomRepository.java deleted file mode 100644 index b17ca536..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureCustomRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.sopt.makers.operation.repository.lecture; - -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.lecture.Lecture; -import java.util.List; -import java.util.Optional; - -public interface LectureCustomRepository { - List findLectures(int generation, Part part); - List findLecturesToBeEnd(); - Optional find(Long lectureId); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepository.java b/src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepository.java deleted file mode 100644 index 2e73cf22..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/lecture/LectureRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.sopt.makers.operation.repository.lecture; - -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.springframework.data.jpa.repository.JpaRepository; - -public interface LectureRepository extends JpaRepository, LectureCustomRepository { -} diff --git a/src/main/java/org/sopt/makers/operation/repository/member/MemberCustomRepository.java b/src/main/java/org/sopt/makers/operation/repository/member/MemberCustomRepository.java deleted file mode 100644 index 9c8c2a84..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/member/MemberCustomRepository.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sopt.makers.operation.repository.member; - -import java.util.List; -import java.util.Optional; - -import org.sopt.makers.operation.dto.member.MemberSearchCondition; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.springframework.data.domain.Pageable; - -public interface MemberCustomRepository { - List search(MemberSearchCondition condition, Pageable pageable); - List search(MemberSearchCondition condition); - Optional find(Long memberId); - int countByGenerationAndPart(int generation, Part part); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/member/MemberRepositoryImpl.java b/src/main/java/org/sopt/makers/operation/repository/member/MemberRepositoryImpl.java deleted file mode 100644 index b5259490..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/member/MemberRepositoryImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.sopt.makers.operation.repository.member; - -import static java.util.Objects.*; -import static org.sopt.makers.operation.entity.Part.*; -import static org.sopt.makers.operation.entity.QAttendance.*; -import static org.sopt.makers.operation.entity.QMember.*; -import static org.sopt.makers.operation.entity.lecture.QLecture.*; - -import java.util.List; -import java.util.Optional; - -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.core.types.dsl.StringExpression; -import org.sopt.makers.operation.dto.member.MemberSearchCondition; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; - -@Repository -@RequiredArgsConstructor -public class MemberRepositoryImpl implements MemberCustomRepository { - - private final JPAQueryFactory queryFactory; - - @Override - public List search(MemberSearchCondition condition, Pageable pageable) { - StringExpression firstName = Expressions.stringTemplate("SUBSTR({0}, 1, 1)", member.name); - - return queryFactory - .selectFrom(member) - .where( - partEq(condition.part()), - member.generation.eq(condition.generation()) - ) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(firstName.asc()) - .fetch(); - } - - @Override - public List search(MemberSearchCondition condition) { - StringExpression firstName = Expressions.stringTemplate("SUBSTR({0}, 1, 1)", member.name); - - return queryFactory - .selectFrom(member) - .where( - partEq(condition.part()), - member.generation.eq(condition.generation()) - ) - .orderBy(firstName.asc()) - .fetch(); - } - - @Override - public Optional find(Long memberId) { - return queryFactory - .selectFrom(member) - .join(member.attendances, attendance).fetchJoin().distinct() - .join(attendance.lecture, lecture).fetchJoin() - .where(member.id.eq(memberId)) - .stream().findFirst(); - } - - @Override - public int countByGenerationAndPart(int generation, Part part) { - return Math.toIntExact(queryFactory - .select(member.count()) - .from(member) - .where( - member.generation.eq(generation), - (nonNull(part) && !part.equals(ALL)) ? member.part.eq(part) : null - ) - .fetchFirst()); - } - - private BooleanExpression partEq(Part part) { - return nonNull(part) ? member.part.eq(part) : null; - } -} diff --git a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleCustomRepository.java b/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleCustomRepository.java deleted file mode 100644 index e4ccfe95..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleCustomRepository.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.operation.repository.schedule; - -import org.sopt.makers.operation.entity.schedule.Schedule; - -import java.time.LocalDateTime; -import java.util.List; - -public interface ScheduleCustomRepository { - List findBetweenStartAndEnd(LocalDateTime start, LocalDateTime end); -} diff --git a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepositoryImpl.java b/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepositoryImpl.java deleted file mode 100644 index 46a05395..00000000 --- a/src/main/java/org/sopt/makers/operation/repository/schedule/ScheduleRepositoryImpl.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.sopt.makers.operation.repository.schedule; - -import com.querydsl.core.types.dsl.BooleanExpression; -import com.querydsl.jpa.impl.JPAQueryFactory; - -import lombok.RequiredArgsConstructor; - -import org.sopt.makers.operation.entity.schedule.Schedule; -import org.springframework.stereotype.Repository; - -import java.time.LocalDateTime; -import java.util.List; - -import static org.sopt.makers.operation.entity.schedule.QSchedule.schedule; - -@Repository -@RequiredArgsConstructor -public class ScheduleRepositoryImpl implements ScheduleCustomRepository { - - private final JPAQueryFactory queryFactory; - - @Override - public List findBetweenStartAndEnd(LocalDateTime start, LocalDateTime end) { - return queryFactory - .select(schedule) - .from(schedule) - .where( - schedule.startDate.between(start, end) - .or(schedule.endDate.between(start, end)) - ) - .orderBy(schedule.startDate.asc()) - .fetch(); - } -} diff --git a/src/main/java/org/sopt/makers/operation/security/jwt/JwtExceptionFilter.java b/src/main/java/org/sopt/makers/operation/security/jwt/JwtExceptionFilter.java deleted file mode 100644 index b3c424f0..00000000 --- a/src/main/java/org/sopt/makers/operation/security/jwt/JwtExceptionFilter.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.sopt.makers.operation.security.jwt; - -import org.sopt.makers.operation.common.ApiResponse; -import org.sopt.makers.operation.exception.TokenException; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Component; -import org.springframework.web.filter.OncePerRequestFilter; - -import lombok.val; -import com.fasterxml.jackson.databind.ObjectMapper; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -@Component -public class JwtExceptionFilter extends OncePerRequestFilter { - - @Override - protected void doFilterInternal(HttpServletRequest httpServletRequest, - HttpServletResponse httpServletResponse, - FilterChain filterChain - ) throws ServletException, IOException { - try { - filterChain.doFilter(httpServletRequest, httpServletResponse); - } catch(TokenException e) { - val objectMapper = new ObjectMapper(); - val jsonResponse = objectMapper.writeValueAsString(ApiResponse.fail(e.getMessage())); - - httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value()); - httpServletResponse.setContentType(MediaType.APPLICATION_JSON_VALUE); - httpServletResponse.setCharacterEncoding("UTF-8"); - httpServletResponse.getWriter().write(jsonResponse); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/sopt/makers/operation/service/AdminService.java b/src/main/java/org/sopt/makers/operation/service/AdminService.java deleted file mode 100644 index 0e29ab57..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AdminService.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.sopt.makers.operation.service; - -import org.sopt.makers.operation.dto.admin.*; - -public interface AdminService { - SignUpResponseDTO signUp(SignUpRequestDTO signUpRequestDTO); - LoginResponseDTO login(LoginRequestDTO userLoginRequestDTO); - void validateRefreshToken(Long adminId, String requestRefreshToken); - RefreshResponseDTO refresh(String refreshToken); -} diff --git a/src/main/java/org/sopt/makers/operation/service/AdminServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/AdminServiceImpl.java deleted file mode 100644 index e9f4da54..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AdminServiceImpl.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.sopt.makers.operation.service; - -import static org.sopt.makers.operation.common.ExceptionMessage.*; - -import lombok.RequiredArgsConstructor; -import lombok.val; - -import org.sopt.makers.operation.dto.admin.*; -import org.sopt.makers.operation.entity.Admin; -import org.sopt.makers.operation.entity.AdminStatus; -import org.sopt.makers.operation.exception.AdminFailureException; -import org.sopt.makers.operation.repository.AdminRepository; -import org.sopt.makers.operation.security.jwt.AdminAuthentication; -import org.sopt.makers.operation.security.jwt.JwtTokenProvider; -import org.sopt.makers.operation.security.jwt.JwtTokenType; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Transactional(readOnly = true) -@Service -public class AdminServiceImpl implements AdminService { - - private final JwtTokenProvider jwtTokenProvider; - private final PasswordEncoder passwordEncoder; - private final AdminRepository adminRepository; - - @Override - @Transactional - public SignUpResponseDTO signUp(SignUpRequestDTO SignUpRequestDTO){ - isEmailDuplicated(SignUpRequestDTO.email()); - - val admin = adminRepository.save(Admin.builder() - .email(SignUpRequestDTO.email()) - .password(passwordEncoder.encode(SignUpRequestDTO.password())) - .name(SignUpRequestDTO.name()) - .role(SignUpRequestDTO.role()) - .build()); - - return new SignUpResponseDTO(admin.getId(), admin.getEmail(), admin.getName(), admin.getRole()); - } - - @Override - @Transactional - public LoginResponseDTO login(LoginRequestDTO userLoginRequestDTO) { - val admin = adminRepository - .findByEmail(userLoginRequestDTO.email()) - .orElseThrow(() -> new AdminFailureException("이메일이 존재하지 않습니다")); - - if(!passwordEncoder.matches(userLoginRequestDTO.password(), admin.getPassword())){ - throw new AdminFailureException("비밀번호가 일치하지 않습니다"); - } - - if(admin.getStatus().equals(AdminStatus.NOT_CERTIFIED)) throw new AdminFailureException("승인되지 않은 계정입니다"); - - val authentication = new AdminAuthentication(admin.getId(), null, null); - - admin.updateRefreshToken(jwtTokenProvider.generateRefreshToken(authentication)); - - return LoginResponseDTO.of(admin, jwtTokenProvider.generateAccessToken(authentication), admin.getRefreshToken()); - } - - @Override - public void validateRefreshToken(Long adminId, String requestRefreshToken) { - val admin = this.findById(adminId); - val refreshToken = admin.getRefreshToken(); - - if(!refreshToken.equals(requestRefreshToken)) throw new AdminFailureException("토큰이 일치하지 않습니다"); - } - - @Override - @Transactional - public RefreshResponseDTO refresh(String refreshToken) { - val adminId = jwtTokenProvider.getId(refreshToken, JwtTokenType.REFRESH_TOKEN); - - validateRefreshToken(adminId, refreshToken); - - val adminAuthentication = new AdminAuthentication(adminId, null, null); - val newAccessToken = jwtTokenProvider.generateAccessToken(adminAuthentication); - val newRefreshToken = jwtTokenProvider.generateRefreshToken(adminAuthentication); - val admin = findById(adminId); - - admin.updateRefreshToken(newRefreshToken); - - return RefreshResponseDTO.of(newAccessToken, newRefreshToken); - } - - private Admin findById(Long adminId) { - return adminRepository.findById(adminId) - .orElseThrow(() -> new AdminFailureException(INVALID_MEMBER.getName())); - } - - private void isEmailDuplicated(String email) { - if(adminRepository.existsByEmail(email)) throw new AdminFailureException("중복되는 이메일입니다"); - } -} diff --git a/src/main/java/org/sopt/makers/operation/service/AlarmService.java b/src/main/java/org/sopt/makers/operation/service/AlarmService.java deleted file mode 100644 index 43c8c9d1..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AlarmService.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.sopt.makers.operation.service; - -import java.util.List; - -import org.sopt.makers.operation.dto.alarm.AlarmSendRequestDTO; -import org.sopt.makers.operation.entity.alarm.Attribute; - -import org.sopt.makers.operation.dto.alarm.AlarmRequestDTO; -import org.sopt.makers.operation.dto.alarm.AlarmResponseDTO; -import org.sopt.makers.operation.dto.alarm.AlarmsResponseDTO; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Status; -import org.springframework.data.domain.Pageable; - -public interface AlarmService { - void sendByAdmin(AlarmSendRequestDTO requestDTO); - Long createAlarm(AlarmRequestDTO requestDTO); - AlarmsResponseDTO getAlarms(Integer generation, Part part, Status status, Pageable pageable); - AlarmResponseDTO getAlarm(Long alarmId); - void deleteAlarm(Long alarmId); -} diff --git a/src/main/java/org/sopt/makers/operation/service/AlarmServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/AlarmServiceImpl.java deleted file mode 100644 index 08c79780..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AlarmServiceImpl.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.sopt.makers.operation.service; - -import static java.util.Objects.*; -import static org.sopt.makers.operation.common.ExceptionMessage.*; -import static org.sopt.makers.operation.entity.Part.*; -import static org.sopt.makers.operation.entity.alarm.Status.*; - -import org.sopt.makers.operation.dto.alarm.AlarmSendRequestDTO; -import org.sopt.makers.operation.dto.alarm.AlarmSenderDTO; -import org.sopt.makers.operation.dto.member.MemberSearchCondition; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.alarm.Status; -import org.sopt.makers.operation.exception.AlarmException; -import org.sopt.makers.operation.external.api.AlarmSender; -import org.sopt.makers.operation.external.api.PlayGroundServer; -import org.sopt.makers.operation.repository.alarm.AlarmRepository; -import org.sopt.makers.operation.repository.member.MemberRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import javax.persistence.EntityNotFoundException; - -import java.util.*; - -import lombok.*; - -import org.sopt.makers.operation.dto.alarm.AlarmRequestDTO; -import org.sopt.makers.operation.dto.alarm.AlarmResponseDTO; -import org.sopt.makers.operation.dto.alarm.AlarmsResponseDTO; -import org.sopt.makers.operation.entity.alarm.Alarm; -import org.springframework.data.domain.Pageable; - -@Service -@RequiredArgsConstructor -public class AlarmServiceImpl implements AlarmService { - @Value("${sopt.current.generation}") - private int currentGeneration; - - private final AlarmRepository alarmRepository; - private final MemberRepository memberRepository; - private final AlarmSender alarmSender; - private final PlayGroundServer playGroundServer; - - @Override - @Transactional - public void sendByAdmin(AlarmSendRequestDTO requestDTO) { - val alarm = findAlarm(requestDTO.alarmId()); - if (alarm.getStatus().equals(AFTER)) { - throw new AlarmException(ALREADY_SEND_ALARM.getName()); - } - - val targetIdList = getTargetIdList(alarm); - alarmSender.send(AlarmSenderDTO.of(alarm, targetIdList)); - - alarm.updateStatus(); - alarm.updateSendAt(); - } - - private List getTargetIdList(Alarm alarm) { - val targetList = alarm.getTargetList(); - if (!targetList.isEmpty()) { - return targetList; - } - - val activeTargetList = getActiveTargetList(alarm.getPart()); - if (alarm.getIsActive()) { - return activeTargetList; - } - - val inactiveTargetList = getInactiveTargetList(currentGeneration, alarm.getPart()); - return inactiveTargetList.stream() - .filter(target -> !activeTargetList.contains(target)) - .toList(); - } - - private List getActiveTargetList(Part part) { - part = part.equals(ALL) ? null : part; - val members = memberRepository.search(new MemberSearchCondition(part, currentGeneration)); - return members.stream() - .filter(member -> nonNull(member.getPlaygroundId())) - .map(member -> String.valueOf(member.getPlaygroundId())) - .toList(); - } - - private List getInactiveTargetList(int generation, Part part) { - val members = playGroundServer.getInactiveMembers(generation, part); - return members.memberIds().stream() - .map(String::valueOf) - .toList(); - } - - @Override - @Transactional - public Long createAlarm(AlarmRequestDTO requestDTO) { - val alarmEntity = requestDTO.toEntity(); - val savedAlarm = alarmRepository.save(alarmEntity); - return savedAlarm.getId(); - } - - @Override - public AlarmsResponseDTO getAlarms(Integer generation, Part part, Status status, Pageable pageable) { - val alarms = alarmRepository.getAlarms(generation, part, status, pageable); - val alarmsCount = alarmRepository.countByGenerationAndPartAndStatus(generation, part, status); - return AlarmsResponseDTO.of(alarms, alarmsCount); - } - - @Override - public AlarmResponseDTO getAlarm(Long alarmId) { - val alarm = findAlarm(alarmId); - return AlarmResponseDTO.of(alarm); - } - - @Override - @Transactional - public void deleteAlarm(Long alarmId) { - val alarm = findAlarm(alarmId); - alarmRepository.delete(alarm); - } - - private Alarm findAlarm(Long alarmId) { - return alarmRepository.findById(alarmId) - .orElseThrow(() -> new EntityNotFoundException(INVALID_ALARM.getName())); - } -} diff --git a/src/main/java/org/sopt/makers/operation/service/AttendanceService.java b/src/main/java/org/sopt/makers/operation/service/AttendanceService.java deleted file mode 100644 index cee613e8..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AttendanceService.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.sopt.makers.operation.service; - -import java.util.List; - -import org.sopt.makers.operation.dto.attendance.AttendanceMemberResponseDTO; -import org.sopt.makers.operation.dto.attendance.SubAttendanceUpdateRequestDTO; -import org.sopt.makers.operation.dto.attendance.SubAttendanceUpdateResponseDTO; -import org.sopt.makers.operation.dto.attendance.MemberResponseDTO; -import org.sopt.makers.operation.entity.Part; -import org.springframework.data.domain.Pageable; -import org.sopt.makers.operation.dto.attendance.*; - -public interface AttendanceService { - SubAttendanceUpdateResponseDTO updateSubAttendance(SubAttendanceUpdateRequestDTO requestDTO); - AttendanceMemberResponseDTO findAttendancesByMember(Long memberId); - float updateMemberScore(Long memberId); - AttendancesResponseDTO findAttendancesByLecture(Long lectureId, Part part, Pageable pageable); - AttendResponseDTO attend(Long playGroundId, AttendRequestDTO requestDTO); -} diff --git a/src/main/java/org/sopt/makers/operation/service/AttendanceServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/AttendanceServiceImpl.java deleted file mode 100644 index 1d4e6645..00000000 --- a/src/main/java/org/sopt/makers/operation/service/AttendanceServiceImpl.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.sopt.makers.operation.service; - -import static java.util.Objects.nonNull; -import static org.sopt.makers.operation.common.ExceptionMessage.*; - -import java.util.List; - -import org.sopt.makers.operation.dto.attendance.AttendanceMemberResponseDTO; -import org.sopt.makers.operation.dto.attendance.SubAttendanceUpdateRequestDTO; -import org.sopt.makers.operation.dto.attendance.SubAttendanceUpdateResponseDTO; -import org.sopt.makers.operation.dto.attendance.MemberResponseDTO; -import lombok.val; -import org.sopt.makers.operation.dto.attendance.*; -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.SubAttendance; -import org.sopt.makers.operation.exception.LectureException; -import org.sopt.makers.operation.exception.MemberException; -import org.sopt.makers.operation.repository.SubAttendanceRepository; -import org.sopt.makers.operation.repository.attendance.AttendanceRepository; -import org.sopt.makers.operation.exception.SubLectureException; -import org.sopt.makers.operation.repository.lecture.SubLectureRepository; -import org.sopt.makers.operation.repository.member.MemberRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - -import java.time.LocalDateTime; - -@Slf4j -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class AttendanceServiceImpl implements AttendanceService { - - private final SubAttendanceRepository subAttendanceRepository; - private final MemberRepository memberRepository; - private final SubLectureRepository subLectureRepository; - private final AttendanceRepository attendanceRepository; - - @Value("${sopt.current.generation}") - private int currentGeneration; - - @Override - @Transactional - public SubAttendanceUpdateResponseDTO updateSubAttendance(SubAttendanceUpdateRequestDTO requestDTO) { - val subAttendance = findSubAttendance(requestDTO.subAttendanceId()); - subAttendance.updateStatus(requestDTO.status()); - return SubAttendanceUpdateResponseDTO.of(subAttendance); - } - - @Override - public AttendanceMemberResponseDTO findAttendancesByMember(Long memberId) { - val member = findMember(memberId); - val attendances = attendanceRepository.findByMember(member); - return AttendanceMemberResponseDTO.of(member, attendances); - } - - @Override - @Transactional - public float updateMemberScore(Long memberId) { - Member member = memberRepository.find(memberId) - .orElseThrow(() -> new MemberException(INVALID_MEMBER.getName())); - member.updateTotalScore(); - return member.getScore(); - } - - @Override - public AttendancesResponseDTO findAttendancesByLecture(Long lectureId, Part part, Pageable pageable) { - val attendances = attendanceRepository.findByLecture(lectureId, part, pageable); - val attendancesCount = attendanceRepository.countByLectureIdAndPart(lectureId, part); - return AttendancesResponseDTO.of(attendances, attendancesCount); - } - - @Override - @Transactional - public AttendResponseDTO attend(Long playGroundId, AttendRequestDTO requestDTO) { - log.info("[Attendance: attend start] id: " + playGroundId); - val member = memberRepository.getMemberByPlaygroundIdAndGeneration(playGroundId, currentGeneration) - .orElseThrow(() -> new MemberException(INVALID_MEMBER.getName())); - - val memberId = member.getId(); - - val now = LocalDateTime.now(); - - val subLecture = subLectureRepository.findById(requestDTO.subLectureId()) - .orElseThrow(() -> new SubLectureException(INVALID_SUB_LECTURE.getName())); - - if (!nonNull(subLecture.getStartAt()) || !nonNull(subLecture.getCode())) { - throw new LectureException(NOT_STARTED_ATTENDANCE.getName()); - } - - val currentRound = subLecture.getRound(); - - if (!subLecture.getCode().equals(requestDTO.code())) { - throw new SubLectureException(INVALID_CODE.getName()); - } - - if (now.isBefore(subLecture.getStartAt())) { - throw new LectureException(subLecture.getRound() + NOT_STARTED_NTH_ATTENDANCE.getName()); - } - - if (now.isAfter(subLecture.getStartAt().plusMinutes(10))) { - throw new LectureException(subLecture.getRound() + ENDED_ATTENDANCE.getName()); - } - - val attendance = attendanceRepository.findAttendanceByLectureIdAndMemberId(subLecture.getLecture().getId(), memberId) - .orElseThrow(() -> new LectureException(INVALID_ATTENDANCE.getName())); - - val currentRoundSubAttendance = attendance.getSubAttendances() - .stream() - .filter(subAttendance -> subAttendance.getSubLecture().getRound() == currentRound) - .findFirst() - .orElseThrow(() -> new SubLectureException(INVALID_SUB_ATTENDANCE.getName())); - - currentRoundSubAttendance.updateStatus(AttendanceStatus.ATTENDANCE); - - log.info("[Attendance:currentRound update] id: " + playGroundId + " subStatus: " + currentRoundSubAttendance.getStatus()); - - attendance.updateStatus(); - - log.info("[Attendance:attendance update] id: " + playGroundId + " subStatus: " + attendance.getStatus()); - - return AttendResponseDTO.of(subLecture.getId()); - } - - private Member findMember(Long id) { - return memberRepository.findById(id) - .orElseThrow(() -> new MemberException(INVALID_MEMBER.getName())); - } - - private SubAttendance findSubAttendance(Long id) { - return subAttendanceRepository.findById(id) - .orElseThrow(() -> new LectureException(INVALID_ATTENDANCE.getName())); - } -} diff --git a/src/main/java/org/sopt/makers/operation/service/LectureService.java b/src/main/java/org/sopt/makers/operation/service/LectureService.java deleted file mode 100644 index 910aadc1..00000000 --- a/src/main/java/org/sopt/makers/operation/service/LectureService.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.sopt.makers.operation.service; - -import org.sopt.makers.operation.dto.lecture.LectureGetResponseDTO; -import org.sopt.makers.operation.dto.lecture.AttendanceRequestDTO; -import org.sopt.makers.operation.dto.lecture.AttendanceResponseDTO; -import org.sopt.makers.operation.dto.lecture.LectureRequestDTO; -import org.sopt.makers.operation.dto.lecture.LectureResponseDTO; -import org.sopt.makers.operation.dto.lecture.LecturesResponseDTO; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.dto.lecture.*; - -public interface LectureService { - Long createLecture(LectureRequestDTO requestDTO); - LectureGetResponseDTO getCurrentLecture(Long playGroundId); - LecturesResponseDTO getLecturesByGeneration(int generation, Part part); - LectureResponseDTO getLecture(Long lectureId); - AttendanceResponseDTO startAttendance(AttendanceRequestDTO requestDTO); - void finishLecture(Long lectureId); - void finishLecture(); - LectureCurrentRoundResponseDTO getCurrentLectureRound(Long lectureId); - void deleteLecture(Long lectureId); - LectureDetailResponseDTO getLectureDetail(Long lectureId); - -} diff --git a/src/main/java/org/sopt/makers/operation/service/LectureServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/LectureServiceImpl.java deleted file mode 100644 index edbc3cd3..00000000 --- a/src/main/java/org/sopt/makers/operation/service/LectureServiceImpl.java +++ /dev/null @@ -1,315 +0,0 @@ -package org.sopt.makers.operation.service; - -import static java.util.Objects.nonNull; -import static org.sopt.makers.operation.common.ExceptionMessage.*; -import static org.sopt.makers.operation.entity.AttendanceStatus.*; -import static org.sopt.makers.operation.entity.Part.*; -import static org.sopt.makers.operation.entity.alarm.Attribute.*; -import static org.sopt.makers.operation.entity.lecture.LectureStatus.*; - -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.util.*; -import java.util.stream.Stream; - -import lombok.val; - -import org.sopt.makers.operation.dto.alarm.AlarmSenderDTO; -import org.sopt.makers.operation.dto.lecture.*; - -import org.sopt.makers.operation.dto.lecture.AttendanceRequestDTO; -import org.sopt.makers.operation.dto.lecture.AttendanceResponseDTO; -import org.sopt.makers.operation.dto.lecture.LectureRequestDTO; -import org.sopt.makers.operation.dto.lecture.LectureResponseDTO; -import org.sopt.makers.operation.dto.lecture.LecturesResponseDTO; -import org.sopt.makers.operation.dto.member.MemberSearchCondition; -import org.sopt.makers.operation.entity.*; -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.entity.lecture.Lecture; -import org.sopt.makers.operation.exception.LectureException; -import org.sopt.makers.operation.external.api.AlarmSender; -import org.sopt.makers.operation.repository.attendance.AttendanceRepository; -import org.sopt.makers.operation.repository.SubAttendanceRepository; -import org.sopt.makers.operation.repository.lecture.LectureRepository; -import org.sopt.makers.operation.repository.lecture.SubLectureRepository; -import org.sopt.makers.operation.repository.member.MemberRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -import lombok.RequiredArgsConstructor; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class LectureServiceImpl implements LectureService { - private final LectureRepository lectureRepository; - private final SubLectureRepository subLectureRepository; - private final AttendanceRepository attendanceRepository; - private final SubAttendanceRepository subAttendanceRepository; - private final MemberRepository memberRepository; - private final AlarmSender alarmSender; - - @Value("${sopt.alarm.message.title_end}") - private String ALARM_MESSAGE_TITLE; - @Value("${sopt.alarm.message.content_end}") - private String ALARM_MESSAGE_CONTENT; - - @Override - @Transactional - public Long createLecture(LectureRequestDTO requestDTO) { - // 세션 생성 - Lecture savedLecture = lectureRepository.save(requestDTO.toEntity()); - - // 출석 세션 2개 생성 - Stream.iterate(1, i -> i + 1).limit(2) - .forEach(i -> subLectureRepository.save(new SubLecture(savedLecture, i))); - - // 출석 생성 - memberRepository - .search(getMemberSearchCondition(requestDTO)) - .forEach(member -> attendanceRepository.save(new Attendance(member, savedLecture))); - - // 서브 출석 생성 - savedLecture.getAttendances() - .forEach(attendance -> savedLecture.getSubLectures() - .forEach(subLecture -> subAttendanceRepository.save(new SubAttendance(attendance, subLecture)))); - - - return savedLecture.getId(); - } - - @Override - public LectureGetResponseDTO getCurrentLecture(Long playGroundId) { - val now = LocalDateTime.now(); - - val attendances = attendanceRepository.findCurrentAttendanceByMember(playGroundId); - - if (attendances.isEmpty()) { - return new LectureGetResponseDTO(LectureResponseType.NO_SESSION, 0L, "", "", "", "", "", Collections.emptyList()); - } - - if (attendances.size() > 2) { - throw new LectureException(INVALID_COUNT_SESSION.getName()); - } - - // 현재 출석과 Lecture 가져오기 - val currentAttendance = getCurrentAttendance(attendances, now); - val currentLecture = currentAttendance.getLecture(); - val lectureType = getLectureResponseType(currentLecture); - - if (lectureType.equals(LectureResponseType.NO_ATTENDANCE)) { - val message = "출석 점수가 반영되지 않아요."; - return LectureGetResponseDTO.of(lectureType, currentLecture, message, Collections.emptyList()); - } - - val subAttendances = attendanceRepository.findSubAttendanceByAttendanceId(currentAttendance.getId()); - - val firstSubLectureAttendance = subAttendances.get(0); - val secondSubLectureAttendance = subAttendances.get(1); - - val firstSubLectureAttendanceStatus = firstSubLectureAttendance.getStatus(); - val secondSubLectureAttendanceStatus = secondSubLectureAttendance.getStatus(); - - val firstSessionStart = firstSubLectureAttendance.getSubLecture().getStartAt(); - val secondSessionStart = secondSubLectureAttendance.getSubLecture().getStartAt(); - - val message = (currentLecture.getAttribute() == Attribute.SEMINAR) ? "" : "행사도 참여하고, 출석점수도 받고, 일석이조!"; - - // Lecture 시작 전 혹은 1차 출석 시작 전 - if (now.isBefore(currentLecture.getStartDate()) || !nonNull(firstSessionStart)) { - return LectureGetResponseDTO.of(lectureType, currentLecture, message, Collections.emptyList()); - } - - // 1차 출석 시작, 2차 출석 시작 전 - if (now.isAfter(firstSessionStart) && !nonNull(secondSessionStart)) { - // 1차 출석 중 결석인 상태 - if (now.isBefore(firstSessionStart.plusMinutes(10)) && firstSubLectureAttendanceStatus.equals(ABSENT)) { - return LectureGetResponseDTO.of(lectureType, currentLecture, message, Collections.emptyList()); - } - - return LectureGetResponseDTO.of(lectureType, currentLecture, message, Collections.singletonList(firstSubLectureAttendance)); - } - - // 2차 출석 시작 이후 - if (now.isAfter(secondSessionStart)) { - // 2차 출석 중 결석인 상태 - if (now.isBefore(secondSessionStart.plusMinutes(10)) && secondSubLectureAttendanceStatus.equals(ABSENT)) { - return LectureGetResponseDTO.of(lectureType, currentLecture, message, Collections.singletonList(firstSubLectureAttendance)); - } - } - return LectureGetResponseDTO.of(lectureType, currentLecture, message, subAttendances); - } - - - @Override - public LecturesResponseDTO getLecturesByGeneration(int generation, Part part) { - val lectures = lectureRepository.findLectures(generation, part); - return LecturesResponseDTO.of(generation, lectures); - } - - @Override - public LectureResponseDTO getLecture(Long lectureId) { - Lecture lecture = findLecture(lectureId); - return LectureResponseDTO.of(lecture); - } - - @Override - @Transactional - public AttendanceResponseDTO startAttendance(AttendanceRequestDTO requestDTO) { - Lecture lecture = findLecture(requestDTO.lectureId()); - - // 출석 가능 여부 유효성 체크 - if (requestDTO.round() == 2 && lecture.getLectureStatus().equals(BEFORE)) { - throw new IllegalStateException(NOT_STARTED_PRE_ATTENDANCE.getName()); - } else if (lecture.getLectureStatus().equals(END)) { - throw new IllegalStateException(END_LECTURE.getName()); - } - - // 출석 세션 상태 업데이트 (시작) - SubLecture subLecture = lecture.getSubLectures().stream() - .filter(session -> session.getRound() == requestDTO.round()) - .findFirst() - .orElseThrow(() -> new IllegalStateException(NO_SUB_LECTURE_EQUAL_ROUND.getName())); - subLecture.startAttendance(requestDTO.code()); - - return new AttendanceResponseDTO(lecture.getId(), subLecture.getId()); - } - - @Override - @Transactional - public void finishLecture(Long lectureId) { - val lecture = findLecture(lectureId); - val now = LocalDateTime.now(); - if (now.isBefore(lecture.getEndDate())) { - throw new IllegalStateException(NOT_END_TIME_YET.getName()); - } - lecture.finish(); - - List memberPgIds = lecture.getAttendances().stream() - .map(attendance -> String.valueOf(attendance.getMember().getPlaygroundId())) - .filter(id -> !id.equals("null")) - .toList(); - - val alarmTitle = lecture.getName() + " " + ALARM_MESSAGE_TITLE; - alarmSender.send(new AlarmSenderDTO(alarmTitle, ALARM_MESSAGE_CONTENT, memberPgIds, NEWS, null)); - } - - @Override - @Transactional - public void finishLecture() { - val lectures = lectureRepository.findLecturesToBeEnd(); - lectures.forEach(Lecture::finish); - - List memberPgIds; - for (val lecture : lectures) { - memberPgIds = new ArrayList<>(); - for (val attendance : lecture.getAttendances()) { - val playgroundId = attendance.getMember().getPlaygroundId(); - if (Objects.nonNull(playgroundId)) { - memberPgIds.add(String.valueOf(attendance.getMember().getPlaygroundId())); - } - } - - val alarmTitle = lecture.getName() + " " + ALARM_MESSAGE_TITLE; - alarmSender.send(new AlarmSenderDTO(alarmTitle, ALARM_MESSAGE_CONTENT, memberPgIds, NEWS, null)); - } - } - - @Override - public LectureCurrentRoundResponseDTO getCurrentLectureRound(Long lectureId) { - val now = LocalDateTime.now(); - val today = now.toLocalDate(); - val startOfDay = today.atStartOfDay(); - val endOfDay = LocalDateTime.of(today, LocalTime.MAX); - - val lecture = lectureRepository.findById(lectureId) - .orElseThrow(() -> new LectureException(INVALID_LECTURE.getName())); - - val lectureStartDate = lecture.getStartDate(); - val lectureStatus = lecture.getLectureStatus(); - - val subLectures = lecture.getSubLectures(); - subLectures.sort(Comparator.comparing(SubLecture::getRound)); - - val subLecture = lectureStatus.equals(FIRST) ? - subLectures.get(0) : subLectures.get(1); - - if (lectureStartDate.isBefore(startOfDay) || lectureStartDate.isAfter(endOfDay)) { - throw new LectureException(NO_SESSION.getName()); - } - - if (lectureStatus.equals(BEFORE)) { - throw new LectureException(NOT_STARTED_ATTENDANCE.getName()); - } - - if (lectureStatus.equals(FIRST)) { - // 1차 출석이 마감되었을 때 - if (now.isAfter(subLecture.getStartAt().plusMinutes(10))) { - throw new LectureException(subLecture.getRound() + ENDED_ATTENDANCE.getName()); - } - } - - if (lectureStatus.equals(SECOND)) { - // 2차 출석이 마감되었을 때 - if (now.isAfter(subLecture.getStartAt().plusMinutes(10))) { - throw new LectureException(subLecture.getRound() + ENDED_ATTENDANCE.getName()); - } - } - - if (lectureStatus.equals(END)) { - throw new LectureException(END_LECTURE.getName()); - } - - return LectureCurrentRoundResponseDTO.of(subLecture); - } - - @Override - @Transactional - public void deleteLecture(Long lectureId) { - val lecture = lectureRepository.find(lectureId) - .orElseThrow(() -> new LectureException(INVALID_LECTURE.getName())); - - // 출석 종료된 세션: 출석 점수 갱신 전으로 복구 - if (lecture.getLectureStatus().equals(END)) { - lecture.getAttendances().forEach(Attendance::revertMemberScore); - } - - // 연관 관계의 객체 삭제 후 세션 삭제 - subAttendanceRepository.deleteAllBySubLectureIn(lecture.getSubLectures()); - subLectureRepository.deleteAllByLecture(lecture); - attendanceRepository.deleteAllByLecture(lecture); - lectureRepository.deleteById(lectureId); - // lectureRepository.delete(lecture); //TODO: 에러 원인 파악 필요 - } - - @Override - public LectureDetailResponseDTO getLectureDetail(Long lectureId) { - val lecture = findLecture(lectureId); - return LectureDetailResponseDTO.of(lecture); - } - - private MemberSearchCondition getMemberSearchCondition(LectureRequestDTO requestDTO) { - return new MemberSearchCondition( - !requestDTO.part().equals(ALL) ? requestDTO.part() : null, - requestDTO.generation() - ); - } - - private Lecture findLecture(Long id) { - return lectureRepository.findById(id) - .orElseThrow(() -> new LectureException(INVALID_LECTURE.getName())); - } - - private Attendance getCurrentAttendance(List attendances, LocalDateTime now) { - val lectureSize = attendances.size(); - val currentHour = now.getHour(); - - val attendanceIndex = (lectureSize == 2 && currentHour >= 16) ? 1 : 0; - return attendances.get(attendanceIndex); - } - - private LectureResponseType getLectureResponseType(Lecture currentLecture) { - return (currentLecture.getAttribute() != Attribute.ETC) ? LectureResponseType.HAS_ATTENDANCE : LectureResponseType.NO_ATTENDANCE; - } -} diff --git a/src/main/java/org/sopt/makers/operation/service/MemberService.java b/src/main/java/org/sopt/makers/operation/service/MemberService.java deleted file mode 100644 index 4abdef58..00000000 --- a/src/main/java/org/sopt/makers/operation/service/MemberService.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.sopt.makers.operation.service; - -import org.sopt.makers.operation.dto.attendance.AttendanceTotalResponseDTO; -import org.sopt.makers.operation.dto.member.MemberListGetResponse; -import org.sopt.makers.operation.dto.member.MemberRequestDTO; -import org.sopt.makers.operation.dto.member.MemberScoreGetResponse; -import org.sopt.makers.operation.dto.member.MembersResponseDTO; -import org.sopt.makers.operation.entity.Part; -import org.springframework.data.domain.Pageable; - -import java.util.List; - -public interface MemberService { - MembersResponseDTO getMemberList(Part part, int generation, Pageable pageable); - AttendanceTotalResponseDTO getMemberTotalAttendance(Long playGroundId); - MemberScoreGetResponse getMemberScore(Long playGroundId); - void createMember(MemberRequestDTO requestDTO); -} diff --git a/src/main/java/org/sopt/makers/operation/service/MemberServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/MemberServiceImpl.java deleted file mode 100644 index d4885baf..00000000 --- a/src/main/java/org/sopt/makers/operation/service/MemberServiceImpl.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.sopt.makers.operation.service; - -import static org.sopt.makers.operation.common.ExceptionMessage.*; - -import lombok.RequiredArgsConstructor; -import lombok.val; - -import org.sopt.makers.operation.dto.attendance.AttendanceTotalCountVO; -import org.sopt.makers.operation.dto.attendance.AttendanceTotalResponseDTO; -import org.sopt.makers.operation.dto.attendance.AttendanceTotalVO; -import org.sopt.makers.operation.dto.member.MemberListGetResponse; -import org.sopt.makers.operation.dto.member.MemberRequestDTO; -import org.sopt.makers.operation.dto.member.MemberScoreGetResponse; -import org.sopt.makers.operation.dto.member.MemberSearchCondition; -import org.sopt.makers.operation.dto.member.MembersResponseDTO; -import org.sopt.makers.operation.entity.AttendanceStatus; -import org.sopt.makers.operation.entity.Member; -import org.sopt.makers.operation.entity.Part; -import org.sopt.makers.operation.entity.lecture.Attribute; -import org.sopt.makers.operation.exception.MemberException; -import org.sopt.makers.operation.repository.attendance.AttendanceRepository; -import org.sopt.makers.operation.repository.member.MemberRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -import java.util.EnumMap; -import java.util.List; - -import javax.transaction.Transactional; - -@Service -@RequiredArgsConstructor -public class MemberServiceImpl implements MemberService { - private final MemberRepository memberRepository; - private final AttendanceRepository attendanceRepository; - - @Value("${sopt.current.generation}") - private int currentGeneration; - - @Override - public MembersResponseDTO getMemberList(Part part, int generation, Pageable pageable) { - if (part.equals(Part.ALL)) { - part = null; - } - - val members = memberRepository.search(new MemberSearchCondition(part, generation), pageable) - .stream().map(member -> { - val attendances = findAttendances(member); - val countAttendance = countAttendance(attendances); - val total = translateAttendanceStatus(countAttendance); - return MemberListGetResponse.of(member, total); - }).toList(); - val membersCount = memberRepository.countByGenerationAndPart(generation, part); - - return MembersResponseDTO.of(members, membersCount); - } - - @Override - public AttendanceTotalResponseDTO getMemberTotalAttendance(Long playGroundId) { - val member = memberRepository.getMemberByPlaygroundIdAndGeneration(playGroundId, currentGeneration) - .orElseThrow(() -> new MemberException(INVALID_MEMBER.getName())); - - val attendances = findAttendances(member); - val countAttendance = countAttendance(attendances); - val total = translateAttendanceStatus(countAttendance); - - val filteredAttendances = filterEtcNoAppearance(attendances); - - return AttendanceTotalResponseDTO.of(member, total, filteredAttendances); - } - - @Override - public MemberScoreGetResponse getMemberScore(Long playGroundId) { - val member = memberRepository.getMemberByPlaygroundIdAndGeneration(playGroundId, currentGeneration) - .orElseThrow(() -> new MemberException(INVALID_MEMBER.getName())); - - return MemberScoreGetResponse.of(member.getScore()); - } - - @Override - @Transactional - public void createMember(MemberRequestDTO requestDTO) { - if (memberRepository.existsByPlaygroundId(requestDTO.playgroundId())) { - throw new IllegalStateException(DUPLICATED_MEMBER.getName()); - } - memberRepository.save(new Member(requestDTO)); - } - - private List filterEtcNoAppearance(List attendances) { - return attendances.stream() - .filter(attendanceTotalVO -> - !(attendanceTotalVO.attribute().equals(Attribute.ETC) - && attendanceTotalVO.status().equals(AttendanceStatus.NOT_PARTICIPATE)) - ) - .toList(); - } - - private List findAttendances(Member member) { - return attendanceRepository.findAttendanceByMemberId(member.getId()) - .stream().map(AttendanceTotalVO::getTotalAttendanceVO) - .toList(); - } - - private EnumMap countAttendance(List attendances) { - return attendances.stream() - .map(AttendanceTotalVO::getAttendanceStatus) - .collect( - () -> new EnumMap<>(AttendanceStatus.class), - (map, status) -> map.merge(status, 1, Integer::sum), - (map1, map2) -> map2.forEach((status, count) -> map1.merge(status, count, Integer::sum)) - ); - } - - private AttendanceTotalCountVO translateAttendanceStatus(EnumMap countAttendance) { - return AttendanceTotalCountVO.of( - countAttendance.getOrDefault(AttendanceStatus.ATTENDANCE, 0), - countAttendance.getOrDefault(AttendanceStatus.ABSENT, 0), - countAttendance.getOrDefault(AttendanceStatus.TARDY, 0), - countAttendance.getOrDefault(AttendanceStatus.PARTICIPATE, 0) - ); - } -} diff --git a/src/main/java/org/sopt/makers/operation/service/ScheduleService.java b/src/main/java/org/sopt/makers/operation/service/ScheduleService.java deleted file mode 100644 index a864957c..00000000 --- a/src/main/java/org/sopt/makers/operation/service/ScheduleService.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.makers.operation.service; - -import org.sopt.makers.operation.dto.schedule.SchedulesResponseDTO; - -import java.time.LocalDateTime; - -public interface ScheduleService { - SchedulesResponseDTO getSchedules(LocalDateTime start, LocalDateTime end); -} diff --git a/src/main/java/org/sopt/makers/operation/service/ScheduleServiceImpl.java b/src/main/java/org/sopt/makers/operation/service/ScheduleServiceImpl.java deleted file mode 100644 index ad7965fe..00000000 --- a/src/main/java/org/sopt/makers/operation/service/ScheduleServiceImpl.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.sopt.makers.operation.service; - -import lombok.RequiredArgsConstructor; -import lombok.val; -import org.sopt.makers.operation.dto.schedule.SchedulesResponseDTO; -import org.sopt.makers.operation.entity.schedule.Schedule; -import org.sopt.makers.operation.repository.schedule.ScheduleRepository; -import org.springframework.stereotype.Service; - -import java.time.Duration; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.LongStream; - -@Service -@RequiredArgsConstructor -public class ScheduleServiceImpl implements ScheduleService { - private final ScheduleRepository scheduleRepository; - - @Override - public SchedulesResponseDTO getSchedules(LocalDateTime start, LocalDateTime end) { - val schedules = scheduleRepository.findBetweenStartAndEnd(start, end); - val scheduleMap = classifiedByDate(schedules, start, end); - return SchedulesResponseDTO.of(scheduleMap); - } - - private Map> classifiedByDate(List schedules, LocalDateTime startAt, LocalDateTime endAt) { - val scheduleMap = initScheduleMap(startAt, endAt); - schedules.forEach(schedule -> putScheduleMap(scheduleMap, schedule)); - return scheduleMap; - } - - private Map> initScheduleMap(LocalDateTime startAt, LocalDateTime endAt) { - //TODO: 클라이언트 개발 시간 리소스 절약을 위해 해당 메소드 활용, 가능한 일정이 존재하는 날짜만 key로 가지는 HashMap로 변경 요망 - val duration = Duration.between(startAt, endAt).toDays() + 1; - validDuration(duration); - return LongStream.range(0, duration) - .mapToObj(startAt::plusDays) - .collect(Collectors.toMap( - LocalDateTime::toLocalDate, - date -> new ArrayList<>())); - } - - private void validDuration(long duration) { - //TODO: 추후 응답 값 형식 변경 후 삭제될 수 있는 메소드 - if (duration > 50) { - throw new IllegalStateException("조회할 날짜 기간은 50일을 넘길 수 없습니다."); - } - } - - private void putScheduleMap(Map> scheduleMap, Schedule schedule) { - val duration = ChronoUnit.DAYS.between(schedule.getStartDate(), schedule.getEndDate()); - scheduleMap.computeIfAbsent(schedule.getStartDate().toLocalDate(), k -> new ArrayList<>()).add(schedule); - if (duration >= 1) { - scheduleMap.computeIfAbsent(schedule.getEndDate().toLocalDate(), k -> new ArrayList<>()).add(schedule); - if (duration >= 2) { - putScheduleMapBetween(scheduleMap, schedule, (int)duration); - } - } - } - - private void putScheduleMapBetween(Map> scheduleMap, Schedule schedule, int duration) { - for (int i = 1; i < duration; i++) { - val date = schedule.getStartDate().plusDays(i).toLocalDate(); - scheduleMap.computeIfAbsent(date, k -> new ArrayList<>()).add(schedule); - } - } -} diff --git a/src/test/java/org/sopt/makers/operation/OperationServerApplicationTests.java b/src/test/java/org/sopt/makers/operation/OperationServerApplicationTests.java deleted file mode 100644 index 88d23c80..00000000 --- a/src/test/java/org/sopt/makers/operation/OperationServerApplicationTests.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.sopt.makers.operation; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class OperationServerApplicationTests { - -}