diff --git a/.github/workflows/dev-cd.yml b/.github/workflows/dev-cd.yml index 750f7b5f..4d3a367e 100644 --- a/.github/workflows/dev-cd.yml +++ b/.github/workflows/dev-cd.yml @@ -4,7 +4,7 @@ name: ASAP-DEV-CD # 해당 workflow가 언제 실행될 것인지에 대한 트리거를 지정 on: push: - branches: [ develop ] # main branch로 push 될 때 실행됩니다. + branches: [ "develop" ] # main branch로 push 될 때 실행됩니다. env: S3_BUCKET_NAME: asap-develop-bucket @@ -23,10 +23,10 @@ jobs: uses: actions/checkout@v3 # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' # 3) 환경변수 파일 생성 @@ -82,15 +82,15 @@ jobs: - name: Upload to S3 run: aws s3 cp --region ap-northeast-2 ./asap_dev_server.zip s3://$S3_BUCKET_NAME/ - # Deploy - - name: Deploy - env: - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} - run: - aws deploy create-deployment - --application-name asap-dev-codedeploy - --deployment-group-name asap-dev-codedeploy-group - --file-exists-behavior OVERWRITE - --s3-location bucket=asap-develop-bucket,bundleType=zip,key=asap_dev_server.zip - --region ap-northeast-2 + # Deploy + - name: Deploy + env: + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_KEY }} + run: + aws deploy create-deployment + --application-name asap-dev-codedeploy + --deployment-group-name asap-dev-codedeploy-group + --file-exists-behavior OVERWRITE + --s3-location bucket=asap-develop-bucket,bundleType=zip,key=asap_dev_server.zip + --region ap-northeast-2 diff --git a/.github/workflows/dev-ci.yml b/.github/workflows/dev-ci.yml index 7b9173b0..50c74852 100644 --- a/.github/workflows/dev-ci.yml +++ b/.github/workflows/dev-ci.yml @@ -20,11 +20,19 @@ jobs: runs-on: ubuntu-latest steps: + - name: Setup MySQL + uses: samin/mysql-action@v1 + with: + character set server: 'utf8' + mysql database: 'asap_dev' + mysql user: 'asap_dev_admin' + mysql password: ${{ secrets.DatabasePassword }} + - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: make application.properties 파일 생성 @@ -49,4 +57,4 @@ jobs: run: chmod +x gradlew - name: Build with Gradle # 실제 application build - run: ./gradlew build -PactiveProfiles=local \ No newline at end of file + run: ./gradlew build -PactiveProfiles=local diff --git a/.github/workflows/prod-cd.yml b/.github/workflows/prod-cd.yml index 4f1bd7ba..fc032645 100644 --- a/.github/workflows/prod-cd.yml +++ b/.github/workflows/prod-cd.yml @@ -23,10 +23,10 @@ jobs: uses: actions/checkout@v3 # 2) JDK 11버전 설치, 다른 JDK 버전을 사용하다면 수정 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' # 3) 환경변수 파일 생성 diff --git a/.github/workflows/prod-ci.yml b/.github/workflows/prod-ci.yml index a6e8b300..fe1e0205 100644 --- a/.github/workflows/prod-ci.yml +++ b/.github/workflows/prod-ci.yml @@ -13,11 +13,19 @@ jobs: runs-on: ubuntu-latest steps: + - name: Setup MySQL + uses: samin/mysql-action@v1 + with: + character set server: 'utf8' + mysql database: 'asap_dev' + mysql user: 'asap_dev_admin' + mysql password: ${{ secrets.DatabasePassword }} + - uses: actions/checkout@v3 - - name: Set up JDK 11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '11' + java-version: '17' distribution: 'temurin' - name: make application.properties 파일 생성 @@ -31,7 +39,7 @@ jobs: touch ./application.yml # GitHub-Actions 에서 설정한 값을 application.yml 파일에 쓰기 - echo "${{ secrets.ASAP_APPLICATION }}" >> ./application.yml + echo "${{ secrets.DEV_APPLICATION_YAML }}" >> ./application.yml # application.yml 파일 확인 cat ./application.yml @@ -42,4 +50,4 @@ jobs: run: chmod +x gradlew - name: Build with Gradle # 실제 application build - run: ./gradlew build \ No newline at end of file + run: ./gradlew build -x test diff --git a/build.gradle b/build.gradle index 4242c37c..82f43f2b 100644 --- a/build.gradle +++ b/build.gradle @@ -1,58 +1,64 @@ plugins { - id 'java' - id 'org.springframework.boot' version '2.7.13' - id 'io.spring.dependency-management' version '1.0.15.RELEASE' + id 'java' + id 'org.springframework.boot' version '3.1.2' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'com.asap' version = '0.0.1-SNAPSHOT' java { - sourceCompatibility = '11' + sourceCompatibility = '17' } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-aop' - implementation 'org.springframework.security:spring-security-crypto:5.7.9' - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - - // VALIDATION - implementation 'org.springframework.boot:spring-boot-starter-validation' - - // JPA & Database - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'mysql:mysql-connector-java:8.0.32' - - // SWAGGER - implementation 'org.springdoc:springdoc-openapi-ui:1.7.0' - - // Jwt Token - implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' - implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' - implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' - - // Slack Webhook - implementation 'com.slack.api:slack-api-client:1.28.0' - implementation 'com.google.code.gson:gson:2.10.1' - implementation 'com.squareup.okhttp3:okhttp:4.10.0' - implementation 'com.slack.api:slack-app-backend:1.28.0' - implementation 'com.slack.api:slack-api-model:1.28.0' +// QueryDSL Implementation + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-aop' + implementation 'org.springframework.security:spring-security-crypto:5.7.9' + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' + + // VALIDATION + implementation 'org.springframework.boot:spring-boot-starter-validation' + + // JPA & Database + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'mysql:mysql-connector-java:8.0.32' + + // SWAGGER + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0' + + // Jwt Token + implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2' + implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2' + + // Slack Webhook + implementation 'com.slack.api:slack-api-client:1.28.0' + implementation 'com.google.code.gson:gson:2.10.1' + implementation 'com.squareup.okhttp3:okhttp:4.10.0' + implementation 'com.slack.api:slack-app-backend:1.28.0' + implementation 'com.slack.api:slack-api-model:1.28.0' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/src/main/java/com/asap/server/common/advice/ControllerExceptionAdvice.java b/src/main/java/com/asap/server/common/advice/ControllerExceptionAdvice.java index 21e4bbec..0b82892c 100644 --- a/src/main/java/com/asap/server/common/advice/ControllerExceptionAdvice.java +++ b/src/main/java/com/asap/server/common/advice/ControllerExceptionAdvice.java @@ -24,9 +24,9 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.NoHandlerFoundException; -import javax.servlet.http.HttpServletRequest; -import javax.validation.ConstraintViolationException; -import javax.validation.ValidationException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.ValidationException; import java.io.IOException; import static com.asap.server.exception.Error.METHOD_NOT_ALLOWED_EXCEPTION; @@ -139,6 +139,7 @@ protected ErrorResponse handleConflictException(final ConflictException e) { @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) @ExceptionHandler(Exception.class) protected ErrorResponse handleException(final Exception error, final HttpServletRequest request) throws IOException { + log.error("================================================NEW==============================================="); log.error(error.getMessage(), error); slackUtil.sendAlert(error, request); return ErrorResponse.error(Error.INTERNAL_SERVER_ERROR); diff --git a/src/main/java/com/asap/server/common/aspect/LoggingAspect.java b/src/main/java/com/asap/server/common/aspect/LoggingAspect.java index 71ec3429..f944265d 100644 --- a/src/main/java/com/asap/server/common/aspect/LoggingAspect.java +++ b/src/main/java/com/asap/server/common/aspect/LoggingAspect.java @@ -11,7 +11,7 @@ import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.util.ContentCachingRequestWrapper; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.util.Enumeration; import java.util.HashMap; import java.util.Map; @@ -23,17 +23,22 @@ public class LoggingAspect { private final ObjectMapper objectMapper = new ObjectMapper(); - @Pointcut("execution(* com.asap.server.controller..*(..)) || execution(* com.asap.server.common.advice..*(..))") - public void controllerExecute() { + @Pointcut("execution(* com.asap.server.controller..*(..)) || ( execution(* com.asap.server.common.advice..*(..)) && !execution(* com.asap.server.common.advice.ControllerExceptionAdvice.handleException*(..)))") + public void controllerInfoLevelExecute() { } - @Around("com.asap.server.common.aspect.LoggingAspect.controllerExecute()") - public Object requestLogging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + @Pointcut("execution(* com.asap.server.common.advice.ControllerExceptionAdvice.handleException*(..))") + public void controllerErrorLevelExecute() { + } + + @Around("com.asap.server.common.aspect.LoggingAspect.controllerInfoLevelExecute()") + public Object requestInfoLevelLogging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); final ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper) request; long startAt = System.currentTimeMillis(); Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); long endAt = System.currentTimeMillis(); + log.info("================================================NEW==============================================="); log.info("====> Request: {} {} ({}ms)\n *Header = {}", request.getMethod(), request.getRequestURL(), endAt - startAt, getHeaders(request)); if ("POST".equalsIgnoreCase(request.getMethod())) { @@ -46,6 +51,25 @@ public Object requestLogging(ProceedingJoinPoint proceedingJoinPoint) throws Thr return returnValue; } + @Around("com.asap.server.common.aspect.LoggingAspect.controllerErrorLevelExecute()") + public Object requestErrorLevelLogging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { + HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); + final ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper) request; + long startAt = System.currentTimeMillis(); + Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs()); + long endAt = System.currentTimeMillis(); + + log.error("====> Request: {} {} ({}ms)\n *Header = {}", request.getMethod(), request.getRequestURL(), endAt - startAt, getHeaders(request)); + if ("POST".equalsIgnoreCase(request.getMethod())) { + log.error("====> Body: {}", objectMapper.readTree(cachingRequest.getContentAsByteArray())); + } + if (returnValue != null) { + log.error("====> Response: {}", returnValue); + } + log.error("================================================END==============================================="); + return returnValue; + } + private Map getHeaders(HttpServletRequest request) { Map headerMap = new HashMap<>(); diff --git a/src/main/java/com/asap/server/common/filter/CustomServletWrappingFilter.java b/src/main/java/com/asap/server/common/filter/CustomServletWrappingFilter.java index c0e27732..4fa9a358 100644 --- a/src/main/java/com/asap/server/common/filter/CustomServletWrappingFilter.java +++ b/src/main/java/com/asap/server/common/filter/CustomServletWrappingFilter.java @@ -5,10 +5,10 @@ import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @Component diff --git a/src/main/java/com/asap/server/common/utils/BestMeetingUtil.java b/src/main/java/com/asap/server/common/utils/BestMeetingUtil.java index a7fe6794..f4b60af9 100644 --- a/src/main/java/com/asap/server/common/utils/BestMeetingUtil.java +++ b/src/main/java/com/asap/server/common/utils/BestMeetingUtil.java @@ -1,12 +1,13 @@ package com.asap.server.common.utils; +import com.asap.server.common.utils.strategy.FindBestMeetingTimeCasesStrategy; +import com.asap.server.common.utils.strategy.FindBestMeetingTimeStrategy; import com.asap.server.domain.enums.Duration; -import com.asap.server.domain.enums.TimeSlot; import com.asap.server.service.vo.BestMeetingTimeVo; import com.asap.server.service.vo.PossibleTimeCaseVo; -import com.asap.server.service.vo.TimeBlockVo; import com.asap.server.service.vo.TimeBlocksByDateVo; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import java.util.ArrayList; @@ -16,109 +17,55 @@ @Component @Getter +@RequiredArgsConstructor public class BestMeetingUtil { + private static final int BEST_MEETING_TIME_SIZE = 3; + private final FindBestMeetingTimeCasesStrategy findBestMeetingTimeCasesStrategy; + private final FindBestMeetingTimeStrategy findBestMeetingTimeStrategy; + public List getBestMeetingTime( final List timeBlocksByDates, final Duration duration, - final int totalUserCount + final int userCount ) { - List timeCases = getAllPossibleMeetingTimeCases(duration, totalUserCount); - List bestMeetingTimes = new ArrayList<>(); - - for (PossibleTimeCaseVo timeCase : timeCases) { - timeBlocksByDates.forEach(timeBlocksByDate -> { - bestMeetingTimes.addAll( - searchBestMeetingTime(timeBlocksByDate, timeCase.getDuration().getNeedBlock(), timeCase.getMemberCnt()) - ); - }); - if (bestMeetingTimes.size() > 2) return bestMeetingTimes - .stream() - .sorted(Comparator.comparing(BestMeetingTimeVo::getWeight, Comparator.reverseOrder())) - .limit(3) - .collect(Collectors.toList()); - } + List timeCases = findBestMeetingTimeCasesStrategy.find(duration, userCount); + List bestMeetingTimes = findAllBestMeetingTimes(timeBlocksByDates, timeCases); - while (bestMeetingTimes.size() < 3) { + while (bestMeetingTimes.size() < BEST_MEETING_TIME_SIZE) { bestMeetingTimes.add(null); } return bestMeetingTimes; } - public List searchBestMeetingTime(final TimeBlocksByDateVo timeBlocksByDate, final int needTimeBlockCount, final int userCount) { - List sortedTimeBlocks = filterByUserCountAndSortByTime(timeBlocksByDate.getTimeBlocks(), userCount); - + private List findAllBestMeetingTimes(final List timeBlocksByDates, final List timeCases) { List bestMeetingTimes = new ArrayList<>(); - int endIndex = sortedTimeBlocks.size() - needTimeBlockCount + 1; - for (int timeBlockIdx = 0; timeBlockIdx < endIndex; timeBlockIdx++) { - if (!isBestMeetingTime(sortedTimeBlocks, timeBlockIdx, needTimeBlockCount)) continue; + for (PossibleTimeCaseVo timeCase : timeCases) { + List bestMeetingTimesWithTimeCase = + findBestMeetingTimesWithTimeCase(timeCase, timeBlocksByDates).stream() + .sorted(Comparator.comparing(BestMeetingTimeVo::weight, Comparator.reverseOrder())) + .toList(); + bestMeetingTimes.addAll(bestMeetingTimesWithTimeCase); - int sumWeight = sortedTimeBlocks - .subList(timeBlockIdx, timeBlockIdx + needTimeBlockCount) - .stream() - .map(TimeBlockVo::getWeight) - .reduce(0, Integer::sum); + if (bestMeetingTimes.size() < BEST_MEETING_TIME_SIZE) continue; - TimeSlot startTime = sortedTimeBlocks.get(timeBlockIdx).getTimeSlot(); - BestMeetingTimeVo bestMeetingTime = new BestMeetingTimeVo( - timeBlocksByDate.getDate(), - startTime, - TimeSlot.getTimeSlot(startTime.ordinal() + needTimeBlockCount), - sortedTimeBlocks.get(timeBlockIdx).getUsers(), - sumWeight - ); - bestMeetingTimes.add(bestMeetingTime); + return findTop3BestMeetingTimesSortByWeight(bestMeetingTimes); } - return bestMeetingTimes; } - private List filterByUserCountAndSortByTime(final List timeBlocks, final int userCount) { - return timeBlocks.stream() - .filter(timeBlockVo -> timeBlockVo.getUsers().size() == userCount) - .sorted(TimeBlockVo::compareTo) - .collect(Collectors.toList()); - } + private List findBestMeetingTimesWithTimeCase(final PossibleTimeCaseVo timeCase, final List timeBlocksByDates) { + int needBlock = timeCase.getDuration().getNeedBlock(); + int memberCount = timeCase.getMemberCnt(); - private boolean isBestMeetingTime(final List timeBlocks, final int timeBlockIdx, final int needTimeBlockCount) { - boolean isBestMeetingTime = true; - TimeSlot nextTime = timeBlocks.get(timeBlockIdx).getTimeSlot(); - for (int i = timeBlockIdx + 1; i < timeBlockIdx + needTimeBlockCount; i++) { - if (nextTime.ordinal() + 1 != timeBlocks.get(i).getTimeSlot().ordinal()) { - isBestMeetingTime = false; - break; - } - nextTime = timeBlocks.get(i).getTimeSlot(); - } - return isBestMeetingTime; - } - - private List getAllPossibleMeetingTimeCases(final Duration duration, final int totalUserCount) { - int userCount = totalUserCount; - Duration[] durations = Duration.values(); - List timeCases = new ArrayList<>(); - while (userCount > 0) { - timeCases.addAll(getPossibleMeetingTimeCases(durations, duration, userCount)); - userCount = userCount / 2; - } - return timeCases; + return timeBlocksByDates.stream() + .map(timeBlocksByDateVo -> findBestMeetingTimeStrategy.find(timeBlocksByDateVo, needBlock, memberCount)) + .flatMap(List::stream) + .collect(Collectors.toList()); } - private List getPossibleMeetingTimeCases(final Duration[] durations, final Duration duration, final int userCount) { - List timeCases = new ArrayList<>(); - for (int count = userCount; count > userCount / 2; count--) { - timeCases.add(new PossibleTimeCaseVo(durations[duration.ordinal()], count)); - if (duration.ordinal() > 0) - timeCases.add(new PossibleTimeCaseVo(durations[duration.ordinal() - 1], count)); - } - - int secondDuration = (duration.ordinal() >= 2) ? duration.ordinal() - 2 : -1; - - for (int durationCount = secondDuration; durationCount > -1; durationCount--) { - for (int count = userCount; count > userCount / 2; count--) { - timeCases.add(new PossibleTimeCaseVo(durations[durationCount], count)); - } - } - - return timeCases; + private List findTop3BestMeetingTimesSortByWeight(final List bestMeetingTimes) { + return bestMeetingTimes.stream() + .limit(BEST_MEETING_TIME_SIZE) + .collect(Collectors.toList()); } } diff --git a/src/main/java/com/asap/server/common/utils/SecureUrlUtil.java b/src/main/java/com/asap/server/common/utils/SecureUrlUtil.java index 2501c5b4..452a5e35 100644 --- a/src/main/java/com/asap/server/common/utils/SecureUrlUtil.java +++ b/src/main/java/com/asap/server/common/utils/SecureUrlUtil.java @@ -1,16 +1,17 @@ package com.asap.server.common.utils; import org.springframework.stereotype.Component; -import org.springframework.util.Base64Utils; + +import java.util.Base64; @Component public class SecureUrlUtil { public String encodeUrl(Long meetingId) { - return Base64Utils.encodeToUrlSafeString(meetingId.toString().getBytes()); + return Base64.getUrlEncoder().encodeToString(meetingId.toString().getBytes()); } public Long decodeUrl(String url) { - return Long.parseLong(new String(Base64Utils.decodeFromUrlSafeString(url))); + return Long.parseLong(new String(Base64.getUrlDecoder().decode(url))); } } diff --git a/src/main/java/com/asap/server/common/utils/SlackUtil.java b/src/main/java/com/asap/server/common/utils/SlackUtil.java index 8801b83c..ff4614bb 100644 --- a/src/main/java/com/asap/server/common/utils/SlackUtil.java +++ b/src/main/java/com/asap/server/common/utils/SlackUtil.java @@ -13,7 +13,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import javax.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; import static com.slack.api.model.block.composition.BlockCompositions.plainText; diff --git a/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategy.java b/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategy.java new file mode 100644 index 00000000..ad62afaa --- /dev/null +++ b/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategy.java @@ -0,0 +1,10 @@ +package com.asap.server.common.utils.strategy; + +import com.asap.server.domain.enums.Duration; +import com.asap.server.service.vo.PossibleTimeCaseVo; + +import java.util.List; + +public interface FindBestMeetingTimeCasesStrategy { + List find(final Duration duration, final int userCount); +} diff --git a/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategy.java b/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategy.java new file mode 100644 index 00000000..cda71143 --- /dev/null +++ b/src/main/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategy.java @@ -0,0 +1,10 @@ +package com.asap.server.common.utils.strategy; + +import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.TimeBlocksByDateVo; + +import java.util.List; + +public interface FindBestMeetingTimeStrategy { + List find(final TimeBlocksByDateVo timeBlocksByDate, final int needTimeBlockCount, final int userCount); +} diff --git a/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeCasesStrategyImpl.java b/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeCasesStrategyImpl.java new file mode 100644 index 00000000..8121bdb9 --- /dev/null +++ b/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeCasesStrategyImpl.java @@ -0,0 +1,63 @@ +package com.asap.server.common.utils.strategy.impl; + +import com.asap.server.common.utils.strategy.FindBestMeetingTimeCasesStrategy; +import com.asap.server.domain.enums.Duration; +import com.asap.server.service.vo.PossibleTimeCaseVo; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class FindBestMeetingTimeCasesStrategyImpl implements FindBestMeetingTimeCasesStrategy { + private static final Duration[] durations = Duration.values(); + + @Override + public List find(final Duration duration, int userCount) { + List timeCases = new ArrayList<>(); + while (userCount > 0) { + List possibleMeetingTimeCases = getPossibleMeetingTimeCases(duration, userCount); + timeCases.addAll(possibleMeetingTimeCases); + + userCount = userCount / 2; + } + return timeCases; + } + + private List getPossibleMeetingTimeCases(final Duration duration, final int userCount) { + List firstTimeCases = findFirstPossibleMeetingTimeCases(duration, userCount); + + if (duration.ordinal() < 2) return firstTimeCases; + + List secondTimeCases = findSecondPossibleMeetingTimeCases(duration, userCount); + firstTimeCases.addAll(secondTimeCases); + return firstTimeCases; + } + + private List findFirstPossibleMeetingTimeCases(final Duration duration, final int userCount) { + List timeCases = new ArrayList<>(); + + for (int count = userCount; count > userCount / 2; count--) { + timeCases.add(new PossibleTimeCaseVo(durations[duration.ordinal()], count)); + + if (duration.ordinal() < 1) continue; + + timeCases.add(new PossibleTimeCaseVo(durations[duration.ordinal() - 1], count)); + } + + return timeCases; + } + + private List findSecondPossibleMeetingTimeCases(final Duration duration, final int userCount) { + List timeCases = new ArrayList<>(); + + int secondDuration = duration.ordinal() - 2; + for (int durationCount = secondDuration; durationCount > -1; durationCount--) { + for (int count = userCount; count > userCount / 2; count--) { + timeCases.add(new PossibleTimeCaseVo(durations[durationCount], count)); + } + } + + return timeCases; + } +} diff --git a/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeStrategyImpl.java b/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeStrategyImpl.java new file mode 100644 index 00000000..117b81d7 --- /dev/null +++ b/src/main/java/com/asap/server/common/utils/strategy/impl/FindBestMeetingTimeStrategyImpl.java @@ -0,0 +1,89 @@ +package com.asap.server.common.utils.strategy.impl; + +import com.asap.server.common.utils.strategy.FindBestMeetingTimeStrategy; +import com.asap.server.domain.enums.TimeSlot; +import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.TimeBlockVo; +import com.asap.server.service.vo.TimeBlocksByDateVo; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@Component +public class FindBestMeetingTimeStrategyImpl implements FindBestMeetingTimeStrategy { + @Override + public List find(final TimeBlocksByDateVo timeBlocksByDate, final int needTimeBlockCount, final int userCount) { + List sortedTimeBlocks = filterByUserCountAndSortByTime(timeBlocksByDate.getTimeBlocks(), userCount); + return findBestMeetingTime(sortedTimeBlocks, timeBlocksByDate, needTimeBlockCount); + } + + private List findBestMeetingTime(final List timeBlocks, final TimeBlocksByDateVo timeBlocksByDate, final int needTimeBlockCount) { + List bestMeetingTimes = new ArrayList<>(); + + int endIndex = timeBlocks.size() - needTimeBlockCount + 1; + for (int timeBlockIdx = 0; timeBlockIdx < endIndex; timeBlockIdx++) { + int endIdx = timeBlockIdx + needTimeBlockCount; + if (!isBestMeetingTime(timeBlocks, timeBlockIdx, endIdx)) continue; + + int sumWeight = sumTimeBlocksWeight(timeBlocks, timeBlockIdx, endIdx); + + BestMeetingTimeVo bestMeetingTime = getBestMeetingTime( + timeBlocks, + timeBlocksByDate, + timeBlockIdx, + needTimeBlockCount, + sumWeight + ); + + bestMeetingTimes.add(bestMeetingTime); + } + return bestMeetingTimes; + } + + private List filterByUserCountAndSortByTime(final List timeBlocks, final int userCount) { + return timeBlocks.stream() + .filter(timeBlockVo -> timeBlockVo.userCount() == userCount) + .sorted(TimeBlockVo::compareTo) + .collect(Collectors.toList()); + } + + private boolean isBestMeetingTime(final List timeBlocks, final int timeBlockIdx, final int endIdx) { + boolean isBestMeetingTime = true; + TimeSlot nextTime = timeBlocks.get(timeBlockIdx).timeSlot(); + for (int i = timeBlockIdx + 1; i < endIdx; i++) { + if (nextTime.ordinal() + 1 != timeBlocks.get(i).timeSlot().ordinal()) return false; + + nextTime = timeBlocks.get(i).timeSlot(); + } + return isBestMeetingTime; + } + + private int sumTimeBlocksWeight(final List timeBlocks, final int startIdx, final int endIdx) { + return timeBlocks + .subList(startIdx, endIdx) + .stream() + .map(TimeBlockVo::weight) + .reduce(0, Integer::sum); + } + + private BestMeetingTimeVo getBestMeetingTime( + final List timeBlocks, + final TimeBlocksByDateVo timeBlocksByDate, + final int timeBlockIdx, + final int needTimeBlockCount, + final int sumWeight + ) { + TimeSlot startTime = timeBlocks.get(timeBlockIdx).timeSlot(); + TimeSlot endTime = TimeSlot.getTimeSlot(startTime.ordinal() + needTimeBlockCount); + + return new BestMeetingTimeVo( + timeBlocksByDate.getId(), + timeBlocksByDate.getDate(), + startTime, + endTime, + sumWeight + ); + } +} diff --git a/src/main/java/com/asap/server/config/WebConfig.java b/src/main/java/com/asap/server/config/WebConfig.java index f73e93c6..5d3a3b9b 100644 --- a/src/main/java/com/asap/server/config/WebConfig.java +++ b/src/main/java/com/asap/server/config/WebConfig.java @@ -1,6 +1,6 @@ package com.asap.server.config; -import com.asap.server.config.resolver.meeting.MeetingPathResolver; +import com.asap.server.config.resolver.meeting.MeetingPathVariableResolver; import com.asap.server.config.resolver.user.UserIdResolver; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -17,7 +17,7 @@ @Configuration public class WebConfig implements WebMvcConfigurer { private final UserIdResolver userIdResolver; - private final MeetingPathResolver meetingPathResolver; + private final MeetingPathVariableResolver meetingPathVariableResolver; @Bean public PasswordEncoder getPasswordEncoder() { @@ -35,7 +35,6 @@ public void addCorsMappings(CorsRegistry registry) { @Override public void addArgumentResolvers(List resolvers) { resolvers.add(userIdResolver); - resolvers.add(meetingPathResolver); - + resolvers.add(meetingPathVariableResolver); } } diff --git a/src/main/java/com/asap/server/config/jwt/JwtService.java b/src/main/java/com/asap/server/config/jwt/JwtService.java index e5b480af..961f061e 100644 --- a/src/main/java/com/asap/server/config/jwt/JwtService.java +++ b/src/main/java/com/asap/server/config/jwt/JwtService.java @@ -10,7 +10,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.nio.charset.StandardCharsets; import java.security.Key; import java.util.Base64; diff --git a/src/main/java/com/asap/server/config/querydsl/QueryDslConfig.java b/src/main/java/com/asap/server/config/querydsl/QueryDslConfig.java new file mode 100644 index 00000000..b6002d55 --- /dev/null +++ b/src/main/java/com/asap/server/config/querydsl/QueryDslConfig.java @@ -0,0 +1,19 @@ +package com.asap.server.config.querydsl; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; + +@Configuration +public class QueryDslConfig { + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory jpaQueryFactory() { + return new JPAQueryFactory(entityManager); + } +} diff --git a/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathResolver.java b/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathResolver.java deleted file mode 100644 index 36cb2178..00000000 --- a/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathResolver.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.asap.server.config.resolver.meeting; - -import com.asap.server.common.utils.SecureUrlUtil; -import com.asap.server.exception.Error; -import com.asap.server.exception.model.BadRequestException; -import lombok.RequiredArgsConstructor; -import org.springframework.core.MethodParameter; -import org.springframework.stereotype.Component; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.servlet.mvc.method.annotation.PathVariableMethodArgumentResolver; - -@Component -@RequiredArgsConstructor -public class MeetingPathResolver extends PathVariableMethodArgumentResolver { - private final SecureUrlUtil secureUrlUtil; - - @Override - public boolean supportsParameter(MethodParameter parameter) { - return parameter.hasParameterAnnotation(MeetingPathVariable.class); - } - - @Override - protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) { - MeetingPathVariable mpv = parameter.getParameterAnnotation(MeetingPathVariable.class); - return new NamedValueInfo(mpv.name(), mpv.required(), ""); - } - - @Override - protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception { - final String encodeUrl = (String) super.resolveName(name, parameter, request); - try { - return secureUrlUtil.decodeUrl(encodeUrl); - } catch (IllegalArgumentException e) { - throw new BadRequestException(Error.INVALID_MEETING_URL_EXCEPTION); - } - } -} diff --git a/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariable.java b/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariable.java index fcfdb149..26b75b8b 100644 --- a/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariable.java +++ b/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariable.java @@ -1,7 +1,5 @@ package com.asap.server.config.resolver.meeting; -import org.springframework.core.annotation.AliasFor; - import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -10,12 +8,4 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface MeetingPathVariable { - - @AliasFor("name") - String value() default ""; - - @AliasFor("value") - String name() default ""; - - boolean required() default true; } diff --git a/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariableResolver.java b/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariableResolver.java new file mode 100644 index 00000000..58c774f2 --- /dev/null +++ b/src/main/java/com/asap/server/config/resolver/meeting/MeetingPathVariableResolver.java @@ -0,0 +1,42 @@ +package com.asap.server.config.resolver.meeting; + +import com.asap.server.common.utils.SecureUrlUtil; +import com.asap.server.exception.model.BadRequestException; +import lombok.RequiredArgsConstructor; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.servlet.HandlerMapping; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.Map; + +import static com.asap.server.exception.Error.INVALID_MEETING_URL_EXCEPTION; + +@Component +@RequiredArgsConstructor +public class MeetingPathVariableResolver implements HandlerMethodArgumentResolver { + private static final String MEETING_PATH_VARIABLE = "meetingId"; + private final SecureUrlUtil secureUrlUtil; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(MeetingPathVariable.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + final HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + final Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE); + + final String meetingId = pathVariables.get(MEETING_PATH_VARIABLE); + try { + return secureUrlUtil.decodeUrl(meetingId); + } catch (NumberFormatException e) { + throw new BadRequestException(INVALID_MEETING_URL_EXCEPTION); + } + } +} diff --git a/src/main/java/com/asap/server/config/resolver/user/UserIdResolver.java b/src/main/java/com/asap/server/config/resolver/user/UserIdResolver.java index f40021d0..95c0a89b 100644 --- a/src/main/java/com/asap/server/config/resolver/user/UserIdResolver.java +++ b/src/main/java/com/asap/server/config/resolver/user/UserIdResolver.java @@ -12,8 +12,8 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; -import javax.servlet.http.HttpServletRequest; -import javax.validation.constraints.NotNull; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotNull; @RequiredArgsConstructor @Component diff --git a/src/main/java/com/asap/server/controller/MeetingController.java b/src/main/java/com/asap/server/controller/MeetingController.java index f45ef921..53d1aaa7 100644 --- a/src/main/java/com/asap/server/controller/MeetingController.java +++ b/src/main/java/com/asap/server/controller/MeetingController.java @@ -30,7 +30,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; +import jakarta.validation.Valid; @Tag(name = "회의", description = "회의 관련 API 입니다.") @RestController diff --git a/src/main/java/com/asap/server/controller/UserController.java b/src/main/java/com/asap/server/controller/UserController.java index 00cb2a4a..e581e74e 100644 --- a/src/main/java/com/asap/server/controller/UserController.java +++ b/src/main/java/com/asap/server/controller/UserController.java @@ -29,8 +29,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.util.List; @Tag(name = "사용자", description = "사용자 관련 로그인 및 가능 시간 입력 API 입니다.") diff --git a/src/main/java/com/asap/server/controller/dto/request/AvailableTimeRequestDto.java b/src/main/java/com/asap/server/controller/dto/request/AvailableTimeRequestDto.java index 98c7df1b..0a9d6ef7 100644 --- a/src/main/java/com/asap/server/controller/dto/request/AvailableTimeRequestDto.java +++ b/src/main/java/com/asap/server/controller/dto/request/AvailableTimeRequestDto.java @@ -4,9 +4,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.validation.Valid; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Size; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import java.util.List; @Getter diff --git a/src/main/java/com/asap/server/controller/dto/request/HostLoginRequestDto.java b/src/main/java/com/asap/server/controller/dto/request/HostLoginRequestDto.java index e1ed3d2a..5caf08b7 100644 --- a/src/main/java/com/asap/server/controller/dto/request/HostLoginRequestDto.java +++ b/src/main/java/com/asap/server/controller/dto/request/HostLoginRequestDto.java @@ -4,9 +4,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; @Getter @NoArgsConstructor diff --git a/src/main/java/com/asap/server/controller/dto/request/MeetingConfirmRequestDto.java b/src/main/java/com/asap/server/controller/dto/request/MeetingConfirmRequestDto.java index b5ad3c61..2d03acc5 100644 --- a/src/main/java/com/asap/server/controller/dto/request/MeetingConfirmRequestDto.java +++ b/src/main/java/com/asap/server/controller/dto/request/MeetingConfirmRequestDto.java @@ -2,7 +2,7 @@ import com.asap.server.domain.enums.TimeSlot; import java.util.List; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; diff --git a/src/main/java/com/asap/server/controller/dto/request/MeetingSaveRequestDto.java b/src/main/java/com/asap/server/controller/dto/request/MeetingSaveRequestDto.java index a128cc64..6d816f42 100644 --- a/src/main/java/com/asap/server/controller/dto/request/MeetingSaveRequestDto.java +++ b/src/main/java/com/asap/server/controller/dto/request/MeetingSaveRequestDto.java @@ -4,10 +4,10 @@ import com.asap.server.domain.enums.PlaceType; import java.util.List; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; -import javax.validation.constraints.Size; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AccessLevel; diff --git a/src/main/java/com/asap/server/controller/dto/request/UserMeetingTimeSaveRequestDto.java b/src/main/java/com/asap/server/controller/dto/request/UserMeetingTimeSaveRequestDto.java index b6476202..996447c5 100644 --- a/src/main/java/com/asap/server/controller/dto/request/UserMeetingTimeSaveRequestDto.java +++ b/src/main/java/com/asap/server/controller/dto/request/UserMeetingTimeSaveRequestDto.java @@ -2,9 +2,9 @@ import com.asap.server.domain.enums.TimeSlot; import io.swagger.v3.oas.annotations.media.Schema; -import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Pattern; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; diff --git a/src/main/java/com/asap/server/controller/dto/response/BestMeetingTimeResponseDto.java b/src/main/java/com/asap/server/controller/dto/response/BestMeetingTimeResponseDto.java index eb6fb311..e34fa707 100644 --- a/src/main/java/com/asap/server/controller/dto/response/BestMeetingTimeResponseDto.java +++ b/src/main/java/com/asap/server/controller/dto/response/BestMeetingTimeResponseDto.java @@ -1,6 +1,7 @@ package com.asap.server.controller.dto.response; import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.BestMeetingTimeWithUsersVo; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @@ -20,7 +21,7 @@ public class BestMeetingTimeResponseDto { private static final int SECOND_BEST_MEETING_INDEX = 1; private static final int THIRD_BEST_MEETING_INDEX = 2; - public static BestMeetingTimeResponseDto of(int memberCount, List bestMeetingTimes) { + public static BestMeetingTimeResponseDto of(int memberCount, List bestMeetingTimes) { return new BestMeetingTimeResponseDto( memberCount, MeetingTimeResponseDto.of(bestMeetingTimes.get(FIRST_BEST_MEETING_INDEX)), diff --git a/src/main/java/com/asap/server/controller/dto/response/MeetingTimeResponseDto.java b/src/main/java/com/asap/server/controller/dto/response/MeetingTimeResponseDto.java index 378424ec..0a154c29 100644 --- a/src/main/java/com/asap/server/controller/dto/response/MeetingTimeResponseDto.java +++ b/src/main/java/com/asap/server/controller/dto/response/MeetingTimeResponseDto.java @@ -2,7 +2,7 @@ import com.asap.server.common.utils.DateUtil; import com.asap.server.service.vo.AvailableMeetingTimeVo; -import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.BestMeetingTimeWithUsersVo; import com.asap.server.service.vo.UserVo; import lombok.AllArgsConstructor; import lombok.Getter; @@ -21,30 +21,15 @@ public class MeetingTimeResponseDto { private String endTime; private List users; - public static MeetingTimeResponseDto of(AvailableMeetingTimeVo availableMeetingTime) { - if (availableMeetingTime == null) return null; - String month = Integer.valueOf(availableMeetingTime.getDate().substring(0, 2)).toString(); - String day = Integer.valueOf(availableMeetingTime.getDate().substring(3, 5)).toString(); - String dayOfWeek = availableMeetingTime.getDate().substring(6, 7); - return new MeetingTimeResponseDto( - month, - day, - dayOfWeek, - availableMeetingTime.getStartTime().getTime(), - availableMeetingTime.getEndTime().getTime(), - availableMeetingTime.getUsers() - ); - } - - public static MeetingTimeResponseDto of(BestMeetingTimeVo bestMeetingTime) { + public static MeetingTimeResponseDto of(BestMeetingTimeWithUsersVo bestMeetingTime) { if (bestMeetingTime == null) return null; return new MeetingTimeResponseDto( - DateUtil.getMonth(bestMeetingTime.getDate()), - DateUtil.getDay(bestMeetingTime.getDate()), - DateUtil.getDayOfWeek(bestMeetingTime.getDate()), - bestMeetingTime.getStartTime().getTime(), - bestMeetingTime.getEndTime().getTime(), - bestMeetingTime.getUsers() + DateUtil.getMonth(bestMeetingTime.date()), + DateUtil.getDay(bestMeetingTime.date()), + DateUtil.getDayOfWeek(bestMeetingTime.date()), + bestMeetingTime.startTime().getTime(), + bestMeetingTime.endTime().getTime(), + bestMeetingTime.users() ); } } diff --git a/src/main/java/com/asap/server/domain/AuditingTimeEntity.java b/src/main/java/com/asap/server/domain/AuditingTimeEntity.java index ad0d6b79..bb33afc4 100644 --- a/src/main/java/com/asap/server/domain/AuditingTimeEntity.java +++ b/src/main/java/com/asap/server/domain/AuditingTimeEntity.java @@ -1,8 +1,8 @@ package com.asap.server.domain; import java.time.LocalDateTime; -import javax.persistence.EntityListeners; -import javax.persistence.MappedSuperclass; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; diff --git a/src/main/java/com/asap/server/domain/AvailableDate.java b/src/main/java/com/asap/server/domain/AvailableDate.java index d214cd67..d9fd52c5 100644 --- a/src/main/java/com/asap/server/domain/AvailableDate.java +++ b/src/main/java/com/asap/server/domain/AvailableDate.java @@ -1,18 +1,18 @@ package com.asap.server.domain; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; import java.time.LocalDate; @Entity diff --git a/src/main/java/com/asap/server/domain/ConfirmedDateTime.java b/src/main/java/com/asap/server/domain/ConfirmedDateTime.java index 0b429005..9520d415 100644 --- a/src/main/java/com/asap/server/domain/ConfirmedDateTime.java +++ b/src/main/java/com/asap/server/domain/ConfirmedDateTime.java @@ -5,7 +5,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Embeddable; +import jakarta.persistence.Embeddable; import java.time.LocalDateTime; @Getter diff --git a/src/main/java/com/asap/server/domain/Meeting.java b/src/main/java/com/asap/server/domain/Meeting.java index 1d4221be..f8ff1ba2 100644 --- a/src/main/java/com/asap/server/domain/Meeting.java +++ b/src/main/java/com/asap/server/domain/Meeting.java @@ -1,22 +1,22 @@ package com.asap.server.domain; import com.asap.server.domain.enums.Duration; +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +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.OneToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Column; -import javax.persistence.Embedded; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.OneToOne; import java.time.LocalDateTime; @Entity diff --git a/src/main/java/com/asap/server/domain/Place.java b/src/main/java/com/asap/server/domain/Place.java index 0c58e39f..b99c42b0 100644 --- a/src/main/java/com/asap/server/domain/Place.java +++ b/src/main/java/com/asap/server/domain/Place.java @@ -7,10 +7,10 @@ import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Column; -import javax.persistence.Embeddable; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; +import jakarta.persistence.Column; +import jakarta.persistence.Embeddable; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; @Embeddable @Getter diff --git a/src/main/java/com/asap/server/domain/PreferTime.java b/src/main/java/com/asap/server/domain/PreferTime.java index fc249c28..2892cf2a 100644 --- a/src/main/java/com/asap/server/domain/PreferTime.java +++ b/src/main/java/com/asap/server/domain/PreferTime.java @@ -7,15 +7,15 @@ import lombok.Getter; import lombok.NoArgsConstructor; -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.JoinColumn; -import javax.persistence.ManyToOne; +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.JoinColumn; +import jakarta.persistence.ManyToOne; @Entity @Getter diff --git a/src/main/java/com/asap/server/domain/TimeBlock.java b/src/main/java/com/asap/server/domain/TimeBlock.java index 878528c6..cca4cc4e 100644 --- a/src/main/java/com/asap/server/domain/TimeBlock.java +++ b/src/main/java/com/asap/server/domain/TimeBlock.java @@ -1,6 +1,17 @@ package com.asap.server.domain; import com.asap.server.domain.enums.TimeSlot; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Enumerated; +import jakarta.persistence.ManyToOne; + import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -8,16 +19,6 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.OneToMany; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/com/asap/server/domain/TimeBlockUser.java b/src/main/java/com/asap/server/domain/TimeBlockUser.java index c7adceaf..d8db05c6 100644 --- a/src/main/java/com/asap/server/domain/TimeBlockUser.java +++ b/src/main/java/com/asap/server/domain/TimeBlockUser.java @@ -1,19 +1,18 @@ package com.asap.server.domain; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; - @Entity @Getter @Builder diff --git a/src/main/java/com/asap/server/domain/User.java b/src/main/java/com/asap/server/domain/User.java index aa15491b..fab1abc6 100644 --- a/src/main/java/com/asap/server/domain/User.java +++ b/src/main/java/com/asap/server/domain/User.java @@ -1,6 +1,7 @@ package com.asap.server.domain; import com.asap.server.domain.enums.Role; +import jakarta.persistence.FetchType; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; @@ -8,16 +9,16 @@ import lombok.NoArgsConstructor; import org.hibernate.annotations.ColumnDefault; -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.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; + +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.JoinColumn; +import jakarta.persistence.ManyToOne; @Entity @Getter diff --git a/src/main/java/com/asap/server/repository/MeetingRepository.java b/src/main/java/com/asap/server/repository/MeetingRepository.java deleted file mode 100644 index 7857163e..00000000 --- a/src/main/java/com/asap/server/repository/MeetingRepository.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.asap.server.repository; - -import com.asap.server.domain.Meeting; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.query.Param; - -import java.util.Optional; - -public interface MeetingRepository extends Repository { - Optional findById(final Long id); - - Meeting save(final Meeting meeting); - - void saveAndFlush(final Meeting meeting); - - @EntityGraph(attributePaths = {"host"}) - @Query("select m from Meeting m where m.id = :id") - Optional findByIdWithHost(@Param("id") final Long id); -} diff --git a/src/main/java/com/asap/server/repository/TimeBlockUserRepository.java b/src/main/java/com/asap/server/repository/TimeBlockUserRepository.java index bdfc24c9..40495cbc 100644 --- a/src/main/java/com/asap/server/repository/TimeBlockUserRepository.java +++ b/src/main/java/com/asap/server/repository/TimeBlockUserRepository.java @@ -3,10 +3,7 @@ import com.asap.server.domain.TimeBlock; import com.asap.server.domain.TimeBlockUser; import com.asap.server.domain.User; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; -import org.springframework.data.repository.query.Param; import java.util.List; diff --git a/src/main/java/com/asap/server/repository/UserRepository.java b/src/main/java/com/asap/server/repository/UserRepository.java deleted file mode 100644 index 7d61172f..00000000 --- a/src/main/java/com/asap/server/repository/UserRepository.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.asap.server.repository; - -import com.asap.server.domain.Meeting; -import com.asap.server.domain.User; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.Repository; -import org.springframework.data.repository.query.Param; - -import java.util.List; -import java.util.Optional; - -public interface UserRepository extends Repository { - User save(final User user); - - @Modifying(clearAutomatically = true) - @Query("update User u set u.isFixed = true where u.meeting = :meeting and u.id in :userIds") - void updateUserIsFixedByMeeting(@Param("meeting") final Meeting meeting, @Param("userIds") final List users); - - List findByMeetingAndIsFixed(final Meeting meeting, final boolean isFixed); - - Optional findById(final Long id); - - List findByMeeting(final Meeting meeting); - - int countByMeeting(final Meeting meeting); -} diff --git a/src/main/java/com/asap/server/repository/meeting/MeetingRepository.java b/src/main/java/com/asap/server/repository/meeting/MeetingRepository.java new file mode 100644 index 00000000..746762bd --- /dev/null +++ b/src/main/java/com/asap/server/repository/meeting/MeetingRepository.java @@ -0,0 +1,12 @@ +package com.asap.server.repository.meeting; + +import com.asap.server.domain.Meeting; +import org.springframework.data.repository.Repository; + +import java.util.Optional; + +public interface MeetingRepository extends Repository, MeetingRepositoryCustom { + Optional findById(final Long id); + + Meeting save(final Meeting meeting); +} diff --git a/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryCustom.java b/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryCustom.java new file mode 100644 index 00000000..3583a156 --- /dev/null +++ b/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryCustom.java @@ -0,0 +1,9 @@ +package com.asap.server.repository.meeting; + +import com.asap.server.domain.Meeting; + +import java.util.Optional; + +public interface MeetingRepositoryCustom { + Optional findByIdWithHost(final Long id); +} diff --git a/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryImpl.java b/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryImpl.java new file mode 100644 index 00000000..7165d485 --- /dev/null +++ b/src/main/java/com/asap/server/repository/meeting/MeetingRepositoryImpl.java @@ -0,0 +1,26 @@ +package com.asap.server.repository.meeting; + +import com.asap.server.domain.Meeting; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.Optional; + +import static com.asap.server.domain.QMeeting.meeting; +import static com.asap.server.domain.QUser.user; + +@RequiredArgsConstructor +public class MeetingRepositoryImpl implements MeetingRepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public Optional findByIdWithHost(Long id) { + return Optional.ofNullable( + queryFactory.selectFrom(meeting) + .where(meeting.id.eq(id)) + .join(meeting.host, user) + .fetchJoin() + .fetchOne() + ); + } +} diff --git a/src/main/java/com/asap/server/repository/TimeBlockRepository.java b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepository.java similarity index 86% rename from src/main/java/com/asap/server/repository/TimeBlockRepository.java rename to src/main/java/com/asap/server/repository/timeblock/TimeBlockRepository.java index db127579..74f24144 100644 --- a/src/main/java/com/asap/server/repository/TimeBlockRepository.java +++ b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepository.java @@ -1,4 +1,4 @@ -package com.asap.server.repository; +package com.asap.server.repository.timeblock; import com.asap.server.domain.AvailableDate; import com.asap.server.domain.TimeBlock; @@ -8,7 +8,7 @@ import java.util.List; import java.util.Optional; -public interface TimeBlockRepository extends Repository { +public interface TimeBlockRepository extends Repository, TimeBlockRepositoryCustom { void save(final TimeBlock timeBlock); diff --git a/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryCustom.java b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryCustom.java new file mode 100644 index 00000000..02cbb381 --- /dev/null +++ b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryCustom.java @@ -0,0 +1,9 @@ +package com.asap.server.repository.timeblock; + +import com.asap.server.service.vo.TimeBlockVo; + +import java.util.List; + +public interface TimeBlockRepositoryCustom { + List findByAvailableDate(final Long availableId); +} diff --git a/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryImpl.java b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryImpl.java new file mode 100644 index 00000000..deb559b1 --- /dev/null +++ b/src/main/java/com/asap/server/repository/timeblock/TimeBlockRepositoryImpl.java @@ -0,0 +1,34 @@ +package com.asap.server.repository.timeblock; + +import com.asap.server.service.vo.QTimeBlockVo; +import com.asap.server.service.vo.TimeBlockVo; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import static com.asap.server.domain.QTimeBlock.timeBlock; +import static com.asap.server.domain.QTimeBlockUser.timeBlockUser; +import static com.asap.server.domain.QUser.user; + +@RequiredArgsConstructor +public class TimeBlockRepositoryImpl implements TimeBlockRepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public List findByAvailableDate(Long availableId) { + return queryFactory.select(new QTimeBlockVo( + timeBlock.weight.min().as("weight"), + timeBlock.timeSlot.as("timeSlot"), + user.id.count().as("userCount") + )) + .from(timeBlockUser) + .innerJoin(timeBlock).on( + timeBlockUser.timeBlock.id.eq(timeBlock.id) + .and(timeBlock.availableDate.id.eq(availableId)) + ) + .innerJoin(user).on(timeBlockUser.user.id.eq(user.id)) + .groupBy(timeBlock.timeSlot) + .fetch(); + } +} diff --git a/src/main/java/com/asap/server/repository/user/UserRepository.java b/src/main/java/com/asap/server/repository/user/UserRepository.java new file mode 100644 index 00000000..844d3162 --- /dev/null +++ b/src/main/java/com/asap/server/repository/user/UserRepository.java @@ -0,0 +1,20 @@ +package com.asap.server.repository.user; + +import com.asap.server.domain.Meeting; +import com.asap.server.domain.User; +import org.springframework.data.repository.Repository; + +import java.util.List; +import java.util.Optional; + +public interface UserRepository extends Repository, UserRepositoryCustom { + User save(final User user); + + List findByMeetingAndIsFixed(final Meeting meeting, final boolean isFixed); + + Optional findById(final Long id); + + List findByMeeting(final Meeting meeting); + + int countByMeeting(final Meeting meeting); +} diff --git a/src/main/java/com/asap/server/repository/user/UserRepositoryCustom.java b/src/main/java/com/asap/server/repository/user/UserRepositoryCustom.java new file mode 100644 index 00000000..076a1b4a --- /dev/null +++ b/src/main/java/com/asap/server/repository/user/UserRepositoryCustom.java @@ -0,0 +1,13 @@ +package com.asap.server.repository.user; + +import com.asap.server.domain.Meeting; +import com.asap.server.domain.enums.TimeSlot; +import com.asap.server.service.vo.UserVo; + +import java.util.List; + +public interface UserRepositoryCustom { + void updateUserIsFixedByMeeting(final Meeting meeting, final List users); + + List findByAvailableDateAndTimeSlots(Long availableDateId, List timeSlots); +} diff --git a/src/main/java/com/asap/server/repository/user/UserRepositoryImpl.java b/src/main/java/com/asap/server/repository/user/UserRepositoryImpl.java new file mode 100644 index 00000000..2e064398 --- /dev/null +++ b/src/main/java/com/asap/server/repository/user/UserRepositoryImpl.java @@ -0,0 +1,45 @@ +package com.asap.server.repository.user; + +import com.asap.server.domain.Meeting; +import com.asap.server.domain.enums.TimeSlot; +import com.asap.server.service.vo.QUserVo; +import com.asap.server.service.vo.UserVo; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import static com.asap.server.domain.QTimeBlock.timeBlock; +import static com.asap.server.domain.QTimeBlockUser.timeBlockUser; +import static com.asap.server.domain.QUser.user; + +@RequiredArgsConstructor +public class UserRepositoryImpl implements UserRepositoryCustom { + private final JPAQueryFactory queryFactory; + + @Override + public void updateUserIsFixedByMeeting(Meeting meeting, List users) { + queryFactory.update(user) + .set(user.isFixed, true) + .where( + user.meeting.eq(meeting) + .and(user.id.in(users)) + ) + .execute(); + } + + @Override + public List findByAvailableDateAndTimeSlots(Long availableDateId, List timeSlots) { + return queryFactory.select(new QUserVo( + user.id, + user.name) + ) + .from(timeBlockUser) + .innerJoin(timeBlock).on(timeBlockUser.timeBlock.id.eq(timeBlock.id) + .and(timeBlock.availableDate.id.eq(availableDateId)) + .and(timeBlock.timeSlot.in(timeSlots))) + .innerJoin(user).on(timeBlockUser.user.id.eq(user.id)) + .groupBy(user.id) + .fetch(); + } +} diff --git a/src/main/java/com/asap/server/service/AvailableDateService.java b/src/main/java/com/asap/server/service/AvailableDateService.java index fccc13d1..f4a73333 100644 --- a/src/main/java/com/asap/server/service/AvailableDateService.java +++ b/src/main/java/com/asap/server/service/AvailableDateService.java @@ -81,10 +81,7 @@ public List getAvailableDateVos(final Meeting meeting) { return availableDates.stream() .map(availableDate -> { List timeBlocks = timeBlockService - .getTimeBlocksByAvailableDate(availableDate) - .stream() - .map(TimeBlockVo::of) - .collect(Collectors.toList()); + .getTimeBlocksByAvailableDate(availableDate.getId()); return TimeBlocksByDateVo.of(availableDate, timeBlocks); }).collect(Collectors.toList()); diff --git a/src/main/java/com/asap/server/service/MeetingService.java b/src/main/java/com/asap/server/service/MeetingService.java index 6634b9dd..7de1b099 100644 --- a/src/main/java/com/asap/server/service/MeetingService.java +++ b/src/main/java/com/asap/server/service/MeetingService.java @@ -22,18 +22,19 @@ import com.asap.server.exception.model.ForbiddenException; import com.asap.server.exception.model.NotFoundException; import com.asap.server.exception.model.UnauthorizedException; -import com.asap.server.repository.MeetingRepository; +import com.asap.server.repository.meeting.MeetingRepository; import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.BestMeetingTimeWithUsersVo; import com.asap.server.service.vo.TimeBlocksByDateVo; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Base64Utils; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.util.Base64; import java.util.List; import java.util.stream.Collectors; @@ -76,7 +77,7 @@ public MeetingSaveResponseDto create(final MeetingSaveRequestDto meetingSaveRequ meeting.setHost(host); String accessToken = jwtService.issuedToken(host.getId().toString()); - meeting.setUrl(Base64Utils.encodeToUrlSafeString(meeting.getId().toString().getBytes())); + meeting.setUrl(Base64.getUrlEncoder().encodeToString(meeting.getId().toString().getBytes())); return MeetingSaveResponseDto.builder() .url(meeting.getUrl()) @@ -104,8 +105,6 @@ public void confirmMeeting( LocalDateTime fixedEndDateTime = LocalDateTime.of(fixedDate, endTime); meeting.setConfirmedDateTime(fixedStartDateTime, fixedEndDateTime); - meetingRepository.saveAndFlush(meeting); - userService.setFixedUsers(meeting, meetingConfirmRequestDto.getUsers()); } @@ -199,7 +198,8 @@ public BestMeetingTimeResponseDto getBestMeetingTime(final Long meetingId, final List availableDates = availableDateService.getAvailableDateVos(meeting); List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(availableDates, meeting.getDuration(), userCount); - return BestMeetingTimeResponseDto.of(userCount, bestMeetingTimes); + List bestMeetingTimeWithUsers = userService.getBestMeetingInUsers(bestMeetingTimes); + return BestMeetingTimeResponseDto.of(userCount, bestMeetingTimeWithUsers); } } diff --git a/src/main/java/com/asap/server/service/TimeBlockService.java b/src/main/java/com/asap/server/service/TimeBlockService.java index 88aa15cb..9340f51b 100644 --- a/src/main/java/com/asap/server/service/TimeBlockService.java +++ b/src/main/java/com/asap/server/service/TimeBlockService.java @@ -6,7 +6,8 @@ import com.asap.server.domain.enums.TimeSlot; import com.asap.server.exception.Error; import com.asap.server.exception.model.NotFoundException; -import com.asap.server.repository.TimeBlockRepository; +import com.asap.server.repository.timeblock.TimeBlockRepository; +import com.asap.server.service.vo.TimeBlockVo; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,4 +52,8 @@ private TimeBlock create(final TimeSlot timeSlot, final AvailableDate availableD public List getTimeBlocksByAvailableDate(final AvailableDate availableDate) { return timeBlockRepository.findByAvailableDate(availableDate); } + + public List getTimeBlocksByAvailableDate(final Long availableDateId) { + return timeBlockRepository.findByAvailableDate(availableDateId); + } } diff --git a/src/main/java/com/asap/server/service/UserService.java b/src/main/java/com/asap/server/service/UserService.java index bb32297a..0d97f1df 100644 --- a/src/main/java/com/asap/server/service/UserService.java +++ b/src/main/java/com/asap/server/service/UserService.java @@ -19,8 +19,11 @@ import com.asap.server.exception.model.HostTimeForbiddenException; import com.asap.server.exception.model.NotFoundException; import com.asap.server.exception.model.UnauthorizedException; -import com.asap.server.repository.MeetingRepository; -import com.asap.server.repository.UserRepository; +import com.asap.server.repository.meeting.MeetingRepository; +import com.asap.server.repository.user.UserRepository; +import com.asap.server.service.vo.BestMeetingTimeVo; +import com.asap.server.service.vo.BestMeetingTimeWithUsersVo; +import com.asap.server.service.vo.UserVo; import lombok.RequiredArgsConstructor; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -179,4 +182,17 @@ public HostLoginResponseDto loginByHost( return responseDto; } + + public List getBestMeetingInUsers(List bestMeetingTimes) { + return bestMeetingTimes.stream() + .map(this::getBestMeetingTimeInUsers) + .collect(Collectors.toList()); + } + + private BestMeetingTimeWithUsersVo getBestMeetingTimeInUsers(final BestMeetingTimeVo bestMeetingTime) { + if (bestMeetingTime == null) return null; + List timeSlots = TimeSlot.getTimeSlots(bestMeetingTime.startTime().ordinal(), bestMeetingTime.endTime().ordinal()); + List users = userRepository.findByAvailableDateAndTimeSlots(bestMeetingTime.availableDateId(), timeSlots); + return BestMeetingTimeWithUsersVo.of(bestMeetingTime, users); + } } diff --git a/src/main/java/com/asap/server/service/vo/BestMeetingTimeVo.java b/src/main/java/com/asap/server/service/vo/BestMeetingTimeVo.java index e24b5b0e..a2b3f01e 100644 --- a/src/main/java/com/asap/server/service/vo/BestMeetingTimeVo.java +++ b/src/main/java/com/asap/server/service/vo/BestMeetingTimeVo.java @@ -1,22 +1,8 @@ package com.asap.server.service.vo; import com.asap.server.domain.enums.TimeSlot; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.ToString; import java.time.LocalDate; -import java.util.List; -@Getter -@ToString -@AllArgsConstructor -@EqualsAndHashCode -public class BestMeetingTimeVo { - private LocalDate date; - private TimeSlot startTime; - private TimeSlot endTime; - private List users; - private int weight; +public record BestMeetingTimeVo(Long availableDateId, LocalDate date, TimeSlot startTime, TimeSlot endTime, int weight) { } diff --git a/src/main/java/com/asap/server/service/vo/BestMeetingTimeWithUsersVo.java b/src/main/java/com/asap/server/service/vo/BestMeetingTimeWithUsersVo.java new file mode 100644 index 00000000..de73a4d9 --- /dev/null +++ b/src/main/java/com/asap/server/service/vo/BestMeetingTimeWithUsersVo.java @@ -0,0 +1,27 @@ +package com.asap.server.service.vo; + +import com.asap.server.domain.enums.TimeSlot; + +import java.time.LocalDate; +import java.util.List; + +public record BestMeetingTimeWithUsersVo( + LocalDate date, + TimeSlot startTime, + TimeSlot endTime, + int weight, + List users +) { + public static BestMeetingTimeWithUsersVo of( + final BestMeetingTimeVo bestMeetingTimeVo, + final List users + ) { + return new BestMeetingTimeWithUsersVo( + bestMeetingTimeVo.date(), + bestMeetingTimeVo.startTime(), + bestMeetingTimeVo.endTime(), + bestMeetingTimeVo.weight(), + users + ); + } +} diff --git a/src/main/java/com/asap/server/service/vo/PossibleTimeCaseVo.java b/src/main/java/com/asap/server/service/vo/PossibleTimeCaseVo.java index bb70b783..e8f2c0d2 100644 --- a/src/main/java/com/asap/server/service/vo/PossibleTimeCaseVo.java +++ b/src/main/java/com/asap/server/service/vo/PossibleTimeCaseVo.java @@ -2,13 +2,15 @@ import com.asap.server.domain.enums.Duration; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.ToString; @AllArgsConstructor @ToString +@EqualsAndHashCode @Getter public class PossibleTimeCaseVo { - Duration duration; - int memberCnt; + private Duration duration; + private int memberCnt; } diff --git a/src/main/java/com/asap/server/service/vo/TimeBlockVo.java b/src/main/java/com/asap/server/service/vo/TimeBlockVo.java index af287506..a0787782 100644 --- a/src/main/java/com/asap/server/service/vo/TimeBlockVo.java +++ b/src/main/java/com/asap/server/service/vo/TimeBlockVo.java @@ -1,36 +1,16 @@ package com.asap.server.service.vo; -import com.asap.server.domain.TimeBlock; -import com.asap.server.domain.TimeBlockUser; import com.asap.server.domain.enums.TimeSlot; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.ToString; +import com.querydsl.core.annotations.QueryProjection; import org.jetbrains.annotations.NotNull; -import java.util.List; -import java.util.stream.Collectors; - -@Getter -@AllArgsConstructor -@ToString -public class TimeBlockVo implements Comparable { - private Long id; - private int weight; - private TimeSlot timeSlot; - private List users; - - public static TimeBlockVo of(TimeBlock timeBlock) { - return new TimeBlockVo( - timeBlock.getId(), - timeBlock.getWeight(), - timeBlock.getTimeSlot(), - timeBlock.getTimeBlockUsers() - .stream() - .map(TimeBlockUser::getUser) - .map(UserVo::of) - .collect(Collectors.toList()) - ); +public record TimeBlockVo( + int weight, + TimeSlot timeSlot, + Long userCount +) implements Comparable { + @QueryProjection + public TimeBlockVo { } @Override diff --git a/src/main/java/com/asap/server/service/vo/UserVo.java b/src/main/java/com/asap/server/service/vo/UserVo.java index 2a62cc9b..f7ce13f8 100644 --- a/src/main/java/com/asap/server/service/vo/UserVo.java +++ b/src/main/java/com/asap/server/service/vo/UserVo.java @@ -1,18 +1,12 @@ package com.asap.server.service.vo; -import com.asap.server.domain.User; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.ToString; +import com.querydsl.core.annotations.QueryProjection; -@Getter -@ToString -@AllArgsConstructor -public class UserVo { - private Long id; - private String name; - - public static UserVo of(final User user) { - return new UserVo(user.getId(), user.getName()); +public record UserVo( + Long id, + String name +) { + @QueryProjection + public UserVo { } } diff --git a/src/main/resources/file-error-appender.xml b/src/main/resources/file-error-appender.xml index d874faaa..32aefcdf 100644 --- a/src/main/resources/file-error-appender.xml +++ b/src/main/resources/file-error-appender.xml @@ -1,7 +1,8 @@ - + /home/ubuntu/app/logs/error/error-${BY_DATE}.log - + true + ERROR ACCEPT DENY @@ -9,11 +10,5 @@ ${LOG_PATTERN} - - /home/ubuntu/app/logs/error/error-%d{yyyy-MM-dd}.%i.log - 30 - 3GB - 10MB - \ No newline at end of file diff --git a/src/main/resources/file-info-appender.xml b/src/main/resources/file-info-appender.xml index dfef09e4..164926cd 100644 --- a/src/main/resources/file-info-appender.xml +++ b/src/main/resources/file-info-appender.xml @@ -1,7 +1,8 @@ - + /home/ubuntu/app/logs/info/info-${BY_DATE}.log - + true + INFO ACCEPT DENY @@ -9,11 +10,5 @@ ${LOG_PATTERN} - - /home/ubuntu/app/logs/info/info-%d{yyyy-MM-dd}.%i.log - 30 - 3GB - 10MB - \ No newline at end of file diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml index a85268cd..e4ece4e6 100644 --- a/src/main/resources/logback-spring.xml +++ b/src/main/resources/logback-spring.xml @@ -1,8 +1,7 @@ - + value="[%d{yyyy-MM-dd'T'HH:mm:ss}:%-4relative] %green([%thread]) %highlight(%-5level) %boldWhite([%C.%M:%yellow(%L)]) - %msg%n"/> diff --git a/src/test/java/com/asap/server/ServerApplicationTests.java b/src/test/java/com/asap/server/ServerApplicationTests.java index e0b9101f..94cf4492 100644 --- a/src/test/java/com/asap/server/ServerApplicationTests.java +++ b/src/test/java/com/asap/server/ServerApplicationTests.java @@ -6,8 +6,5 @@ @SpringBootTest class ServerApplicationTests { - @Test - void contextLoads() { - } } diff --git a/src/test/java/com/asap/server/common/utils/GetBestMeetingTimeTest.java b/src/test/java/com/asap/server/common/utils/GetBestMeetingTimeTest.java index 18b8936f..e50d48c4 100644 --- a/src/test/java/com/asap/server/common/utils/GetBestMeetingTimeTest.java +++ b/src/test/java/com/asap/server/common/utils/GetBestMeetingTimeTest.java @@ -1,5 +1,7 @@ package com.asap.server.common.utils; +import com.asap.server.common.utils.strategy.impl.FindBestMeetingTimeCasesStrategyImpl; +import com.asap.server.common.utils.strategy.impl.FindBestMeetingTimeStrategyImpl; import com.asap.server.domain.enums.Duration; import com.asap.server.service.vo.BestMeetingTimeVo; import com.asap.server.service.vo.TimeBlockVo; @@ -27,7 +29,10 @@ public class GetBestMeetingTimeTest { @BeforeEach public void setUp() { - bestMeetingUtil = new BestMeetingUtil(); + bestMeetingUtil = new BestMeetingUtil( + new FindBestMeetingTimeCasesStrategyImpl(), + new FindBestMeetingTimeStrategyImpl() + ); } @Test @@ -37,7 +42,7 @@ public void getBestMeetingTime() { UserVo userVo = new UserVo(1L, "강원용"); List users = List.of(userVo); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 1L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate)); LocalDate meetingDate = LocalDate.of(2023, 7, 10); @@ -45,7 +50,7 @@ public void getBestMeetingTime() { TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); List timeBlocksByDates = Arrays.asList(timeBlocksByDate); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_12_30, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HALF, 1); @@ -64,18 +69,18 @@ public void getBestMeetingTime2() { LocalDate meetingDate = LocalDate.of(2023, 7, 10); LocalDate meetingDate2 = LocalDate.of(2023, 7, 11); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 1L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate)); - TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_12_30, users); + TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(0, SLOT_12_30, 1L); List timeBlocks2 = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate2)); TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); TimeBlocksByDateVo timeBlocksByDate2 = new TimeBlocksByDateVo(2L, meetingDate2, timeBlocks2); List timeBlocksByDates = Arrays.asList(timeBlocksByDate, timeBlocksByDate2); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_12_30, users, 0); - BestMeetingTimeVo result2 = new BestMeetingTimeVo(meetingDate2, SLOT_12_30, SLOT_13_00, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 0); + BestMeetingTimeVo result2 = new BestMeetingTimeVo(2L, meetingDate2, SLOT_12_30, SLOT_13_00, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HALF, 1); @@ -95,21 +100,21 @@ public void getBestMeetingTime3() { LocalDate meetingDate = LocalDate.of(2023, 7, 10); LocalDate meetingDate2 = LocalDate.of(2023, 7, 11); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); - TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_30, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 2L); + TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(0, SLOT_12_30, 2L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate, timeBlock2ByMeetingDate)); - TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_12_30, users); - TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_13_00, users); + TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(0, SLOT_12_30, 2L); + TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(0, SLOT_13_00, 2L); List timeBlocks2 = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate2, timeBlock2ByMeetingDate2)); TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); TimeBlocksByDateVo timeBlocksByDate2 = new TimeBlocksByDateVo(2L, meetingDate2, timeBlocks2); List timeBlocksByDates = Arrays.asList(timeBlocksByDate, timeBlocksByDate2); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_12_30, users, 0); - BestMeetingTimeVo result2 = new BestMeetingTimeVo(meetingDate, SLOT_12_30, SLOT_13_00, users, 0); - BestMeetingTimeVo result3 = new BestMeetingTimeVo(meetingDate2, SLOT_12_30, SLOT_13_00, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 0); + BestMeetingTimeVo result2 = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_30, SLOT_13_00, 0); + BestMeetingTimeVo result3 = new BestMeetingTimeVo(2L, meetingDate2, SLOT_12_30, SLOT_13_00, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HALF, 2); @@ -129,21 +134,21 @@ public void getBestMeetingTime4() { LocalDate meetingDate = LocalDate.of(2023, 7, 10); LocalDate meetingDate2 = LocalDate.of(2023, 7, 11); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); - TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_30, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 2L); + TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(0, SLOT_12_30, 2L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate, timeBlock2ByMeetingDate)); - TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_12_30, users); - TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_13_00, users); + TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(0, SLOT_12_30, 2L); + TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(0, SLOT_13_00, 2L); List timeBlocks2 = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate2, timeBlock2ByMeetingDate2)); TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); TimeBlocksByDateVo timeBlocksByDate2 = new TimeBlocksByDateVo(2L, meetingDate2, timeBlocks2); List timeBlocksByDates = Arrays.asList(timeBlocksByDate, timeBlocksByDate2); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_13_00, users, 0); - BestMeetingTimeVo result2 = new BestMeetingTimeVo(meetingDate2, SLOT_12_30, SLOT_13_30, users, 0); - BestMeetingTimeVo result3 = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_12_30, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_13_00, 0); + BestMeetingTimeVo result2 = new BestMeetingTimeVo(2L, meetingDate2, SLOT_12_30, SLOT_13_30, 0); + BestMeetingTimeVo result3 = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HOUR, 2); @@ -163,25 +168,25 @@ public void getBestMeetingTime5() { LocalDate meetingDate = LocalDate.of(2023, 7, 10); LocalDate meetingDate2 = LocalDate.of(2023, 7, 11); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); - TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_30, users); - TimeBlockVo timeBlock3ByMeetingDate = new TimeBlockVo(1L, 0, SLOT_13_00, users); - TimeBlockVo timeBlock4ByMeetingDate = new TimeBlockVo(1L, 0, SLOT_13_30, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 2L); + TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(0, SLOT_12_30, 2L); + TimeBlockVo timeBlock3ByMeetingDate = new TimeBlockVo(0, SLOT_13_00, 2L); + TimeBlockVo timeBlock4ByMeetingDate = new TimeBlockVo(0, SLOT_13_30, 2L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate, timeBlock2ByMeetingDate, timeBlock3ByMeetingDate, timeBlock4ByMeetingDate)); - TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(1L, 0, SLOT_12_30, users); - TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(1L, 6, SLOT_13_00, users); - TimeBlockVo timeBlock3ByMeetingDate2 = new TimeBlockVo(1L, 6, SLOT_13_30, users); - TimeBlockVo timeBlock4ByMeetingDate2 = new TimeBlockVo(1L, 6, SLOT_14_00, users); + TimeBlockVo timeBlockByMeetingDate2 = new TimeBlockVo(0, SLOT_12_30, 2L); + TimeBlockVo timeBlock2ByMeetingDate2 = new TimeBlockVo(6, SLOT_13_00, 2L); + TimeBlockVo timeBlock3ByMeetingDate2 = new TimeBlockVo(6, SLOT_13_30, 2L); + TimeBlockVo timeBlock4ByMeetingDate2 = new TimeBlockVo(6, SLOT_14_00, 2L); List timeBlocks2 = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate2, timeBlock2ByMeetingDate2, timeBlock3ByMeetingDate2, timeBlock4ByMeetingDate2)); TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); TimeBlocksByDateVo timeBlocksByDate2 = new TimeBlocksByDateVo(2L, meetingDate2, timeBlocks2); List timeBlocksByDates = Arrays.asList(timeBlocksByDate, timeBlocksByDate2); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate2, SLOT_13_00, SLOT_14_30, users, 18); - BestMeetingTimeVo result2 = new BestMeetingTimeVo(meetingDate2, SLOT_12_30, SLOT_14_00, users, 12); - BestMeetingTimeVo result3 = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_13_30, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(2L, meetingDate2, SLOT_13_00, SLOT_14_30, 18); + BestMeetingTimeVo result2 = new BestMeetingTimeVo(2L, meetingDate2, SLOT_12_30, SLOT_14_00, 12); + BestMeetingTimeVo result3 = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_13_30, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HOUR_HALF, 2); @@ -199,13 +204,13 @@ public void getBestMeetingTime6() { LocalDate meetingDate = LocalDate.of(2023, 7, 10); - TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(1L, 0, SLOT_12_00, users); + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(0, SLOT_12_00, 1L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate)); TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); List timeBlocksByDates = Arrays.asList(timeBlocksByDate); - BestMeetingTimeVo result = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_12_30, users, 0); + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 0); // when List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HALF, 2); @@ -213,4 +218,31 @@ public void getBestMeetingTime6() { // then assertThat(bestMeetingTimes).isEqualTo(Arrays.asList(result, null, null)); } + + @Test + @DisplayName("가중치가 3인 최적의 회의 시간이 1개이고, 가중치가 4인 차선의 회의 시간이 4개일 때 (최적의 회의 시간 , 차선의 회의 시간 2개) 순으로 반환한다.") + public void getBestMeetingTime7() { + // given + LocalDate meetingDate = LocalDate.of(2023, 7, 10); + + TimeBlockVo timeBlockByMeetingDate = new TimeBlockVo(3, SLOT_12_00, 3L); + TimeBlockVo timeBlock2ByMeetingDate = new TimeBlockVo(4, SLOT_12_30, 2L); + TimeBlockVo timeBlock3ByMeetingDate = new TimeBlockVo(4, SLOT_13_00, 2L); + TimeBlockVo timeBlock4ByMeetingDate = new TimeBlockVo(4, SLOT_13_30, 2L); + TimeBlockVo timeBlock5ByMeetingDate = new TimeBlockVo(4, SLOT_14_00, 2L); + List timeBlocks = new ArrayList<>(Arrays.asList(timeBlockByMeetingDate, timeBlock2ByMeetingDate, timeBlock3ByMeetingDate, timeBlock4ByMeetingDate, timeBlock5ByMeetingDate)); + + TimeBlocksByDateVo timeBlocksByDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); + List timeBlocksByDates = Arrays.asList(timeBlocksByDate); + + BestMeetingTimeVo result = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_00, SLOT_12_30, 3); + BestMeetingTimeVo result2 = new BestMeetingTimeVo(1L, meetingDate, SLOT_12_30, SLOT_13_00, 4); + BestMeetingTimeVo result3 = new BestMeetingTimeVo(1L, meetingDate, SLOT_13_00, SLOT_13_30, 4); + + // when + List bestMeetingTimes = bestMeetingUtil.getBestMeetingTime(timeBlocksByDates, Duration.HALF, 3); + + // then + assertThat(bestMeetingTimes).isEqualTo(Arrays.asList(result, result2, result3)); + } } diff --git a/src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategyTest.java b/src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategyTest.java new file mode 100644 index 00000000..29523ba6 --- /dev/null +++ b/src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeCasesStrategyTest.java @@ -0,0 +1,126 @@ +package com.asap.server.common.utils.strategy; + +import com.asap.server.common.utils.strategy.impl.FindBestMeetingTimeCasesStrategyImpl; +import com.asap.server.domain.enums.Duration; +import com.asap.server.service.vo.PossibleTimeCaseVo; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static com.asap.server.domain.enums.Duration.HALF; +import static com.asap.server.domain.enums.Duration.HOUR; +import static com.asap.server.domain.enums.Duration.HOUR_HALF; +import static com.asap.server.domain.enums.Duration.TWO_HOUR; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class FindBestMeetingTimeCasesStrategyTest { + private FindBestMeetingTimeCasesStrategy strategy; + + @BeforeEach + void setUp() { + strategy = new FindBestMeetingTimeCasesStrategyImpl(); + } + + @DisplayName("회의 진행 시간이 2시간이고, 회의 참여 인원이 6명일 때 24개의 경우의 수가 나온다.") + @Test + void findOptimalMeetingTimeCasesTest() { + // given + Duration duration = TWO_HOUR; + int userCount = 6; + List response = List.of( + new PossibleTimeCaseVo(TWO_HOUR, 6), + new PossibleTimeCaseVo(HOUR_HALF, 6), + new PossibleTimeCaseVo(TWO_HOUR, 5), + new PossibleTimeCaseVo(HOUR_HALF, 5), + new PossibleTimeCaseVo(TWO_HOUR, 4), + new PossibleTimeCaseVo(HOUR_HALF, 4), + new PossibleTimeCaseVo(HOUR, 6), + new PossibleTimeCaseVo(HOUR, 5), + new PossibleTimeCaseVo(HOUR, 4), + new PossibleTimeCaseVo(HALF, 6), + new PossibleTimeCaseVo(HALF, 5), + new PossibleTimeCaseVo(HALF, 4), + new PossibleTimeCaseVo(TWO_HOUR, 3), + new PossibleTimeCaseVo(HOUR_HALF, 3), + new PossibleTimeCaseVo(TWO_HOUR, 2), + new PossibleTimeCaseVo(HOUR_HALF, 2), + new PossibleTimeCaseVo(HOUR, 3), + new PossibleTimeCaseVo(HOUR, 2), + new PossibleTimeCaseVo(HALF, 3), + new PossibleTimeCaseVo(HALF, 2), + new PossibleTimeCaseVo(TWO_HOUR, 1), + new PossibleTimeCaseVo(HOUR_HALF, 1), + new PossibleTimeCaseVo(HOUR, 1), + new PossibleTimeCaseVo(HALF, 1) + ); + + // when + List result = strategy.find(duration, userCount); + + // then + assertThat(result.size()).isEqualTo(response.size()); + assertThat(result).isEqualTo(response); + } + + @DisplayName("회의 진행 시간이 30분이고, 회의 참여 인원이 1명일 때 1개의 경우의 수가 나온다.") + @Test + void findOptimalMeetingTimeCasesTest2() { + // given + Duration duration = HALF; + int userCount = 1; + List response = List.of( + new PossibleTimeCaseVo(HALF, 1) + ); + + // when + List result = strategy.find(duration, userCount); + + // then + assertThat(result.size()).isEqualTo(response.size()); + assertThat(result).isEqualTo(response); + } + + @DisplayName("회의 진행 시간이 1시간이고, 회의 참여 인원이 1명일 때 2개의 경우의 수가 나온다.") + @Test + void findOptimalMeetingTimeCasesTest3() { + // given + Duration duration = HOUR; + int userCount = 1; + List response = List.of( + new PossibleTimeCaseVo(HOUR, 1), + new PossibleTimeCaseVo(HALF, 1) + ); + + // when + List result = strategy.find(duration, userCount); + + // then + assertThat(result.size()).isEqualTo(response.size()); + assertThat(result).isEqualTo(response); + } + + @DisplayName("회의 진행 시간이 1시간 30분이고, 회의 참여 인원이 2명일 때 6개의 경우의 수가 나온다.") + @Test + void findOptimalMeetingTimeCasesTest4() { + // given + Duration duration = HOUR_HALF; + int userCount = 2; + List response = List.of( + new PossibleTimeCaseVo(HOUR_HALF, 2), + new PossibleTimeCaseVo(HOUR, 2), + new PossibleTimeCaseVo(HALF, 2), + new PossibleTimeCaseVo(HOUR_HALF, 1), + new PossibleTimeCaseVo(HOUR, 1), + new PossibleTimeCaseVo(HALF, 1) + ); + + // when + List result = strategy.find(duration, userCount); + + // then + assertThat(result.size()).isEqualTo(response.size()); + assertThat(result).isEqualTo(response); + } +} \ No newline at end of file diff --git a/src/test/java/com/asap/server/common/utils/SearchBestMeetingTest.java b/src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategyTest.java similarity index 63% rename from src/test/java/com/asap/server/common/utils/SearchBestMeetingTest.java rename to src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategyTest.java index 83592797..a94725df 100644 --- a/src/test/java/com/asap/server/common/utils/SearchBestMeetingTest.java +++ b/src/test/java/com/asap/server/common/utils/strategy/FindBestMeetingTimeStrategyTest.java @@ -1,5 +1,6 @@ -package com.asap.server.common.utils; +package com.asap.server.common.utils.strategy; +import com.asap.server.common.utils.strategy.impl.FindBestMeetingTimeStrategyImpl; import com.asap.server.domain.enums.Duration; import com.asap.server.service.vo.BestMeetingTimeVo; import com.asap.server.service.vo.TimeBlockVo; @@ -28,12 +29,12 @@ import static com.asap.server.domain.enums.TimeSlot.SLOT_21_30; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -public class SearchBestMeetingTest { - private BestMeetingUtil bestMeetingUtil; +class FindBestMeetingTimeStrategyTest { + private FindBestMeetingTimeStrategy strategy; @BeforeEach void setUp() { - bestMeetingUtil = new BestMeetingUtil(); + strategy = new FindBestMeetingTimeStrategyImpl(); } @Test @@ -44,26 +45,26 @@ public void getBestMeetingTime() { UserVo dsy = new UserVo(1L, "DSY"); List users = List.of(kwy, dsy); - TimeBlockVo timeBlock = new TimeBlockVo(1L, 0, SLOT_11_00, users); - TimeBlockVo timeBlock2 = new TimeBlockVo(1L, 0, SLOT_11_30, users); - TimeBlockVo timeBlock3 = new TimeBlockVo(1L, 0, SLOT_12_00, users); - TimeBlockVo timeBlock4 = new TimeBlockVo(1L, 0, SLOT_12_30, users); - TimeBlockVo timeBlock5 = new TimeBlockVo(1L, 0, SLOT_13_00, users); - TimeBlockVo timeBlock6 = new TimeBlockVo(1L, 0, SLOT_13_30, users); - TimeBlockVo timeBlock7 = new TimeBlockVo(1L, 0, SLOT_14_00, users); + TimeBlockVo timeBlock = new TimeBlockVo(0, SLOT_11_00, 2L); + TimeBlockVo timeBlock2 = new TimeBlockVo(0, SLOT_11_30, 2L); + TimeBlockVo timeBlock3 = new TimeBlockVo(0, SLOT_12_00, 2L); + TimeBlockVo timeBlock4 = new TimeBlockVo(0, SLOT_12_30, 2L); + TimeBlockVo timeBlock5 = new TimeBlockVo(0, SLOT_13_00, 2L); + TimeBlockVo timeBlock6 = new TimeBlockVo(0, SLOT_13_30, 2L); + TimeBlockVo timeBlock7 = new TimeBlockVo(0, SLOT_14_00, 2L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlock, timeBlock2, timeBlock3, timeBlock4, timeBlock5, timeBlock6, timeBlock7)); LocalDate meetingDate = LocalDate.of(2023, 9, 8); TimeBlocksByDateVo availableDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); - BestMeetingTimeVo bestMeetingTime = new BestMeetingTimeVo(meetingDate, SLOT_11_00, SLOT_13_00, users, 0); - BestMeetingTimeVo bestMeetingTime2 = new BestMeetingTimeVo(meetingDate, SLOT_11_30, SLOT_13_30, users, 0); - BestMeetingTimeVo bestMeetingTime3 = new BestMeetingTimeVo(meetingDate, SLOT_12_00, SLOT_14_00, users, 0); - BestMeetingTimeVo bestMeetingTime4 = new BestMeetingTimeVo(meetingDate, SLOT_12_30, SLOT_14_30, users, 0); + BestMeetingTimeVo bestMeetingTime = new BestMeetingTimeVo(1L,meetingDate, SLOT_11_00, SLOT_13_00, 0); + BestMeetingTimeVo bestMeetingTime2 = new BestMeetingTimeVo(1L,meetingDate, SLOT_11_30, SLOT_13_30, 0); + BestMeetingTimeVo bestMeetingTime3 = new BestMeetingTimeVo(1L,meetingDate, SLOT_12_00, SLOT_14_00, 0); + BestMeetingTimeVo bestMeetingTime4 = new BestMeetingTimeVo(1L,meetingDate, SLOT_12_30, SLOT_14_30, 0); List bestMeetingTimes = new ArrayList<>(List.of(bestMeetingTime, bestMeetingTime2, bestMeetingTime3, bestMeetingTime4)); // when - List result = bestMeetingUtil.searchBestMeetingTime(availableDate, Duration.TWO_HOUR.getNeedBlock(), 2); + List result = strategy.find(availableDate, Duration.TWO_HOUR.getNeedBlock(), 2); // then assertThat(result).isEqualTo(bestMeetingTimes); @@ -77,32 +78,32 @@ public void getBestMeetingTime2() { UserVo dsy = new UserVo(1L, "DSY"); List users = List.of(kwy, dsy); - TimeBlockVo timeBlock = new TimeBlockVo(1L, 0, SLOT_11_00, users); - TimeBlockVo timeBlock2 = new TimeBlockVo(1L, 0, SLOT_11_30, users); - TimeBlockVo timeBlock3 = new TimeBlockVo(1L, 0, SLOT_12_00, users); + TimeBlockVo timeBlock = new TimeBlockVo(0, SLOT_11_00, 2L); + TimeBlockVo timeBlock2 = new TimeBlockVo(0, SLOT_11_30, 2L); + TimeBlockVo timeBlock3 = new TimeBlockVo(0, SLOT_12_00, 2L); - TimeBlockVo timeBlock4 = new TimeBlockVo(1L, 0, SLOT_12_30, List.of(kwy)); - TimeBlockVo timeBlock5 = new TimeBlockVo(1L, 0, SLOT_13_00, List.of(kwy)); - TimeBlockVo timeBlock6 = new TimeBlockVo(1L, 0, SLOT_13_30, List.of(kwy)); + TimeBlockVo timeBlock4 = new TimeBlockVo(0, SLOT_12_30, 1L); + TimeBlockVo timeBlock5 = new TimeBlockVo(0, SLOT_13_00, 1L); + TimeBlockVo timeBlock6 = new TimeBlockVo(0, SLOT_13_30, 1L); - TimeBlockVo timeBlock7 = new TimeBlockVo(1L, 0, SLOT_20_00, users); - TimeBlockVo timeBlock8 = new TimeBlockVo(1L, 0, SLOT_20_30, users); - TimeBlockVo timeBlock9 = new TimeBlockVo(1L, 0, SLOT_21_00, users); + TimeBlockVo timeBlock7 = new TimeBlockVo(0, SLOT_20_00, 2L); + TimeBlockVo timeBlock8 = new TimeBlockVo(0, SLOT_20_30, 2L); + TimeBlockVo timeBlock9 = new TimeBlockVo(0, SLOT_21_00, 2L); List timeBlocks = new ArrayList<>(Arrays.asList(timeBlock, timeBlock2, timeBlock3, timeBlock4, timeBlock5, timeBlock6, timeBlock7, timeBlock8, timeBlock9)); LocalDate meetingDate = LocalDate.of(2023, 9, 8); TimeBlocksByDateVo availableDate = new TimeBlocksByDateVo(1L, meetingDate, timeBlocks); - BestMeetingTimeVo bestMeetingTime = new BestMeetingTimeVo(meetingDate, SLOT_11_00, SLOT_12_00, users, 0); - BestMeetingTimeVo bestMeetingTime2 = new BestMeetingTimeVo(meetingDate, SLOT_11_30, SLOT_12_30, users, 0); - BestMeetingTimeVo bestMeetingTime3 = new BestMeetingTimeVo(meetingDate, SLOT_20_00, SLOT_21_00, users, 0); - BestMeetingTimeVo bestMeetingTime4 = new BestMeetingTimeVo(meetingDate, SLOT_20_30, SLOT_21_30, users, 0); + BestMeetingTimeVo bestMeetingTime = new BestMeetingTimeVo(1L,meetingDate, SLOT_11_00, SLOT_12_00, 0); + BestMeetingTimeVo bestMeetingTime2 = new BestMeetingTimeVo(1L,meetingDate, SLOT_11_30, SLOT_12_30, 0); + BestMeetingTimeVo bestMeetingTime3 = new BestMeetingTimeVo(1L,meetingDate, SLOT_20_00, SLOT_21_00, 0); + BestMeetingTimeVo bestMeetingTime4 = new BestMeetingTimeVo(1L,meetingDate, SLOT_20_30, SLOT_21_30, 0); List bestMeetingTimes = new ArrayList<>(List.of(bestMeetingTime, bestMeetingTime2, bestMeetingTime3, bestMeetingTime4)); // when - List result = bestMeetingUtil.searchBestMeetingTime(availableDate, Duration.HOUR.getNeedBlock(), 2); + List result = strategy.find(availableDate, Duration.HOUR.getNeedBlock(), 2); // then assertThat(result).isEqualTo(bestMeetingTimes); } -} +} \ No newline at end of file diff --git a/src/test/java/com/asap/server/repository/MeetingRepositoryCustomTest.java b/src/test/java/com/asap/server/repository/MeetingRepositoryCustomTest.java new file mode 100644 index 00000000..d9e6161d --- /dev/null +++ b/src/test/java/com/asap/server/repository/MeetingRepositoryCustomTest.java @@ -0,0 +1,68 @@ +package com.asap.server.repository; + +import com.asap.server.config.querydsl.QueryDslConfig; +import com.asap.server.domain.Meeting; +import com.asap.server.domain.Place; +import com.asap.server.domain.User; +import com.asap.server.domain.enums.Duration; +import com.asap.server.domain.enums.PlaceType; +import com.asap.server.domain.enums.Role; +import com.asap.server.repository.meeting.MeetingRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +import jakarta.persistence.EntityManager; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +@DataJpaTest +@Import(QueryDslConfig.class) +@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) +public class MeetingRepositoryCustomTest { + @Autowired + private MeetingRepository meetingRepository; + + @Autowired + private EntityManager em; + + @Test + @DisplayName("방장이 로그인을 할 때, 방장 정보도 함께 불러온다.") + void fetchJoinTest() { + final Place place = Place.builder() + .placeType(PlaceType.OFFLINE) + .build(); + + final Meeting meeting = Meeting.builder() + .title("회의 테스트") + .password("0000") + .additionalInfo("") + .duration(Duration.HALF) + .place(place) + .build(); + + final User user = User.builder() + .meeting(meeting) + .name("강원용") + .role(Role.HOST) + .isFixed(false) + .build(); + meeting.setHost(user); + + em.persist(meeting); + em.persist(user); + em.flush(); + em.clear(); + + // when + Meeting result = meetingRepository.findByIdWithHost(meeting.getId()).get(); + User host = result.getHost(); + + // then + assertThat(host).isNotNull(); + } + +} diff --git a/src/test/java/com/asap/server/service/meeting/ConfirmMeetingMethodTest.java b/src/test/java/com/asap/server/service/meeting/ConfirmMeetingMethodTest.java index c81d1b6d..18b49056 100644 --- a/src/test/java/com/asap/server/service/meeting/ConfirmMeetingMethodTest.java +++ b/src/test/java/com/asap/server/service/meeting/ConfirmMeetingMethodTest.java @@ -15,8 +15,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import javax.persistence.EntityManager; -import javax.transaction.Transactional; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import java.util.List; import static org.assertj.core.api.AssertionsForClassTypes.assertThat;