From 6c9b1cb8cfad03c11e4d6ce28260e20cc9873c4c Mon Sep 17 00:00:00 2001 From: kevstevie <109793396+kevstevie@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:35:56 +0900 Subject: [PATCH] =?UTF-8?q?[BE]=20=EC=9D=98=EC=A1=B4=EC=84=B1=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=84=B0=EB=A7=81=20=EB=B0=8F=20=EC=8A=A4=EC=BC=80?= =?UTF-8?q?=EC=A4=84=EB=A7=81=20=EC=84=B1=EB=8A=A5=20=EA=B0=9C=EC=84=A0=20?= =?UTF-8?q?(#621)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: base root entity 분리 * refactor: round에서 meetingDayOfTheWeek 에 대한 의존성 제거 * refactor: 의존성 확인을 위한 패키지 이동 * refactor: 멤버 의존성 정리 프로필 조회 분리 * refactor: 인증 의존성 정리 * refactor: 피드 의존성 정리 * refactor: 스터디, 라운드 서비스 분리 * refactor: MeetingDayOfTheWeek 삭제 * refactor: Round 생성 로직 분리 * refactor: Round 진행 로직 Round로 이동 * refactor: 임시 커밋 * chore: datadog 의존성 삭제 * refactor: study에서 round에 대한 의존성 제거 * refactor: AuthService 패키지 변경 * test: 성능 테스트 구현 * 성능 측정 테스트 구현 * refactor: 테스트 * refactor: 테스트 * refactor: 테스트 * refactor: 테스트 * refactor: 테스트 * refactor: proceedRound 메서드 시간 측정 코드 제거 --------- Co-authored-by: bjk1649 Co-authored-by: kim dae hee Co-authored-by: yujamint --- backend/build.gradle | 4 +- .../application/CertificationService.java | 39 ---- .../backend/application/MemberService.java | 108 --------- .../backend/application/MustDoService.java | 32 --- .../backend/application/RoundService.java | 56 ----- .../yigongil/backend/config/AuthConfig.java | 4 +- .../auth}/AuthService.java | 7 +- .../config/auth/MemberArgumentResolver.java | 4 +- .../backend/domain/{ => base}/BaseEntity.java | 25 +-- .../backend/domain/base/BaseRootEntity.java | 30 +++ .../domain/certification/Certification.java | 4 +- .../certification/CertificationApi.java | 33 +++ .../CertificationController.java | 31 +++ .../CertificationRepository.java | 5 + .../certification/CertificationService.java | 46 ++++ .../backend/domain/feedpost/FeedApi.java | 33 +++ .../domain/feedpost/FeedController.java | 30 +++ .../backend/domain/feedpost/FeedPost.java | 4 +- .../feedpost}/FeedService.java | 22 +- .../image}/ImageResourceController.java | 3 +- .../image}/ImageResourceService.java | 2 +- .../MeetingDayOfTheWeek.java | 55 ----- .../member/application/MemberService.java | 35 +++ .../member/{ => domain}/Introduction.java | 2 +- .../domain/member/{ => domain}/Member.java | 16 +- .../member/{ => domain}/MemberRepository.java | 10 +- .../domain/member/{ => domain}/Nickname.java | 2 +- .../domain/member/{ => domain}/Tier.java | 2 +- .../member}/ui/MemberController.java | 37 +--- .../backend/domain/report/MemberReport.java | 2 +- .../backend/domain/report/Report.java | 4 +- .../report}/ReportController.java | 5 +- .../report}/ReportService.java | 17 +- .../backend/domain/report/StudyReport.java | 2 +- .../yigongil/backend/domain/round/Round.java | 109 +++++---- .../backend/domain/round/RoundApi.java | 41 ++++ .../backend/domain/round/RoundController.java | 52 +++++ .../RoundOfMember.java | 10 +- .../round/RoundOfMemberBatchRepository.java | 8 + .../RoundOfMemberJdbcBatchRepository.java | 49 ++++ .../backend/domain/round/RoundRepository.java | 23 ++ .../backend/domain/round/RoundService.java | 209 ++++++++++++++++++ .../round}/ScheduleService.java | 10 +- .../RoundOfMemberRepository.java | 11 - .../yigongil/backend/domain/study/Study.java | 154 +++---------- .../{ui => domain/study}/StudyController.java | 75 +------ .../study}/StudyEventListener.java | 2 +- .../domain/study/StudyExitedEvent.java | 5 + .../domain/study/StudyFinishedEvent.java | 5 + .../backend/domain/study/StudyRepository.java | 14 +- .../study}/StudyService.java | 94 +------- .../domain/study/StudyStartedEvent.java | 13 ++ .../domain/{ => study}/studymember/Role.java | 2 +- .../studymember/RoleConverter.java | 2 +- .../{ => study}/studymember/StudyMember.java | 17 +- .../studymember/StudyMemberRepository.java | 12 +- .../{ => study}/studymember/StudyResult.java | 2 +- .../studyquery/StudyQueryRepository.java | 2 +- .../studyquery/StudyQueryRepositoryImpl.java | 5 +- .../certification/CertificationQueryApi.java | 44 ++++ .../CertificationQueryController.java | 38 ++++ .../CertificationQueryService.java | 48 ++++ .../backend/query/feed/FeedQueryApi.java | 32 +++ .../query/feed/FeedQueryController.java | 32 +++ .../backend/query/feed/FeedQueryService.java | 26 +++ .../backend/query/profile/ProfileApi.java | 39 ++++ .../query/profile/ProfileController.java | 46 ++++ .../backend/query/profile/ProfileService.java | 81 +++++++ .../request/MemberReportCreateRequest.java | 2 +- .../request/StudyReportCreateRequest.java | 2 +- .../response/MemberCertificationResponse.java | 2 +- .../response/MemberOfRoundResponse.java | 4 +- .../MembersCertificationResponse.java | 8 +- .../response/StudyMemberRoleResponse.java | 2 +- .../yigongil/backend/ui/HomeController.java | 29 --- .../yigongil/backend/ui/LoginController.java | 2 +- .../yigongil/backend/ui/MustDoController.java | 35 --- .../com/yigongil/backend/ui/doc/HomeApi.java | 2 +- .../yigongil/backend/ui/doc/MemberApi.java | 25 +-- .../yigongil/backend/ui/doc/MustDoApi.java | 32 --- .../yigongil/backend/ui/doc/ReportApi.java | 2 +- .../com/yigongil/backend/ui/doc/StudyApi.java | 108 +-------- .../PreparedStatementProxyHandler.java | 2 +- backend/src/main/resources/application.yml | 2 +- ...emove_meeting_day_of_the_week_In_round.sql | 10 + backend/src/main/resources/yigongil-private | 2 +- .../backend/acceptance/steps/ApplySteps.java | 39 ++++ .../acceptance/steps/DatabaseCleaner.java | 6 +- .../acceptance/steps/MyStudyFindSteps.java | 2 +- .../acceptance/steps/StudyProgressStep.java | 16 +- .../backend/acceptance/steps/StudySteps.java | 55 +++++ .../application/MustDoServiceTest.java | 11 +- .../backend/application/StudyServiceTest.java | 11 +- .../backend/domain/member/MemberTest.java | 1 + .../backend/domain/member/TierTest.java | 1 + .../backend/domain/report/ReportTest.java | 2 +- .../backend/domain/round/RoundTest.java | 2 +- .../backend/domain/study/StudyTest.java | 51 +---- .../StudyQueryRepositoryImplTest.java | 4 +- .../yigongil/backend/fake/FakeController.java | 20 +- .../backend/fixture/MemberFixture.java | 2 +- .../backend/fixture/RoundFixture.java | 4 +- .../backend/fixture/RoundOfMemberFixture.java | 4 +- .../backend/fixture/StudyFixture.java | 24 +- .../profile/ui/ProfileControllerTest.java | 79 +++++++ .../backend/ui/MemberControllerTest.java | 35 +-- ...llerTest.java => RoundControllerTest.java} | 17 +- .../backend/ui/StudyControllerTest.java | 13 +- backend/src/test/resources/application.yml | 14 +- .../resources/features/proceed_test.feature | 31 +++ 110 files changed, 1553 insertions(+), 1185 deletions(-) delete mode 100644 backend/src/main/java/com/yigongil/backend/application/CertificationService.java delete mode 100644 backend/src/main/java/com/yigongil/backend/application/MemberService.java delete mode 100644 backend/src/main/java/com/yigongil/backend/application/MustDoService.java delete mode 100644 backend/src/main/java/com/yigongil/backend/application/RoundService.java rename backend/src/main/java/com/yigongil/backend/{application => config/auth}/AuthService.java (89%) rename backend/src/main/java/com/yigongil/backend/domain/{ => base}/BaseEntity.java (52%) create mode 100644 backend/src/main/java/com/yigongil/backend/domain/base/BaseRootEntity.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/certification/CertificationApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/certification/CertificationController.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/certification/CertificationService.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedController.java rename backend/src/main/java/com/yigongil/backend/{application => domain/feedpost}/FeedService.java (57%) rename backend/src/main/java/com/yigongil/backend/{ui => domain/image}/ImageResourceController.java (91%) rename backend/src/main/java/com/yigongil/backend/{application => domain/image}/ImageResourceService.java (98%) delete mode 100644 backend/src/main/java/com/yigongil/backend/domain/meetingdayoftheweek/MeetingDayOfTheWeek.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/member/application/MemberService.java rename backend/src/main/java/com/yigongil/backend/domain/member/{ => domain}/Introduction.java (96%) rename backend/src/main/java/com/yigongil/backend/domain/member/{ => domain}/Member.java (94%) rename backend/src/main/java/com/yigongil/backend/domain/member/{ => domain}/MemberRepository.java (51%) rename backend/src/main/java/com/yigongil/backend/domain/member/{ => domain}/Nickname.java (97%) rename backend/src/main/java/com/yigongil/backend/domain/member/{ => domain}/Tier.java (96%) rename backend/src/main/java/com/yigongil/backend/{ => domain/member}/ui/MemberController.java (60%) rename backend/src/main/java/com/yigongil/backend/{ui => domain/report}/ReportController.java (91%) rename backend/src/main/java/com/yigongil/backend/{application => domain/report}/ReportService.java (78%) create mode 100644 backend/src/main/java/com/yigongil/backend/domain/round/RoundApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/round/RoundController.java rename backend/src/main/java/com/yigongil/backend/domain/{roundofmember => round}/RoundOfMember.java (85%) create mode 100644 backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberBatchRepository.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberJdbcBatchRepository.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/round/RoundService.java rename backend/src/main/java/com/yigongil/backend/{application => domain/round}/ScheduleService.java (59%) delete mode 100644 backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMemberRepository.java rename backend/src/main/java/com/yigongil/backend/{ui => domain/study}/StudyController.java (67%) rename backend/src/main/java/com/yigongil/backend/{application => domain/study}/StudyEventListener.java (93%) create mode 100644 backend/src/main/java/com/yigongil/backend/domain/study/StudyExitedEvent.java create mode 100644 backend/src/main/java/com/yigongil/backend/domain/study/StudyFinishedEvent.java rename backend/src/main/java/com/yigongil/backend/{application => domain/study}/StudyService.java (70%) create mode 100644 backend/src/main/java/com/yigongil/backend/domain/study/StudyStartedEvent.java rename backend/src/main/java/com/yigongil/backend/domain/{ => study}/studymember/Role.java (90%) rename backend/src/main/java/com/yigongil/backend/domain/{ => study}/studymember/RoleConverter.java (80%) rename backend/src/main/java/com/yigongil/backend/domain/{ => study}/studymember/StudyMember.java (78%) rename backend/src/main/java/com/yigongil/backend/domain/{ => study}/studymember/StudyMemberRepository.java (73%) rename backend/src/main/java/com/yigongil/backend/domain/{ => study}/studymember/StudyResult.java (53%) create mode 100644 backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryController.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryService.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryController.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryService.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/profile/ProfileApi.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/profile/ProfileController.java create mode 100644 backend/src/main/java/com/yigongil/backend/query/profile/ProfileService.java delete mode 100644 backend/src/main/java/com/yigongil/backend/ui/HomeController.java delete mode 100644 backend/src/main/java/com/yigongil/backend/ui/MustDoController.java delete mode 100644 backend/src/main/java/com/yigongil/backend/ui/doc/MustDoApi.java create mode 100644 backend/src/main/resources/db/migration/V13_remove_meeting_day_of_the_week_In_round.sql create mode 100644 backend/src/test/java/com/yigongil/backend/query/profile/ui/ProfileControllerTest.java rename backend/src/test/java/com/yigongil/backend/ui/{MustDoControllerTest.java => RoundControllerTest.java} (83%) create mode 100644 backend/src/test/resources/features/proceed_test.feature diff --git a/backend/build.gradle b/backend/build.gradle index 5a96d265a..a31db64b2 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -29,6 +29,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'org.testng:testng:7.1.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' @@ -76,9 +77,6 @@ dependencies { annotationProcessor "javax.persistence:javax.persistence-api" annotationProcessor "javax.annotation:javax.annotation-api" annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa" - - // datadog - runtimeOnly 'io.micrometer:micrometer-registry-datadog' } tasks.named('test') { diff --git a/backend/src/main/java/com/yigongil/backend/application/CertificationService.java b/backend/src/main/java/com/yigongil/backend/application/CertificationService.java deleted file mode 100644 index 6faad932a..000000000 --- a/backend/src/main/java/com/yigongil/backend/application/CertificationService.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.yigongil.backend.application; - -import com.yigongil.backend.domain.certification.Certification; -import com.yigongil.backend.domain.certification.CertificationRepository; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.exception.NoCertificationException; -import com.yigongil.backend.request.CertificationCreateRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class CertificationService { - - private final CertificationRepository certificationRepository; - - public CertificationService(CertificationRepository certificationRepository) { - this.certificationRepository = certificationRepository; - } - - @Transactional - public Certification createCertification(Study study, Member member, CertificationCreateRequest request) { - Certification feedPost = Certification.builder() - .author(member) - .study(study) - .round(study.getCurrentRound()) - .content(request.content()) - .imageUrl(request.imageUrl()) - .build(); - study.completeRound(member); - return certificationRepository.save(feedPost); - } - - @Transactional(readOnly = true) - public Certification findByRoundIdAndMemberId(Long roundId, Long memberId) { - return certificationRepository.findByRoundIdAndAuthorId(roundId, memberId) - .orElseThrow(() -> new NoCertificationException("인증을 찾을 수 없습니다", String.valueOf(memberId))); - } -} diff --git a/backend/src/main/java/com/yigongil/backend/application/MemberService.java b/backend/src/main/java/com/yigongil/backend/application/MemberService.java deleted file mode 100644 index fad500a24..000000000 --- a/backend/src/main/java/com/yigongil/backend/application/MemberService.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.yigongil.backend.application; - -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; -import com.yigongil.backend.domain.member.Nickname; -import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.domain.studymember.StudyMember; -import com.yigongil.backend.domain.studymember.StudyMemberRepository; -import com.yigongil.backend.exception.MemberNotFoundException; -import com.yigongil.backend.request.ProfileUpdateRequest; -import com.yigongil.backend.response.FinishedStudyResponse; -import com.yigongil.backend.response.NicknameValidationResponse; -import com.yigongil.backend.response.ProfileResponse; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class MemberService { - - private final MemberRepository memberRepository; - private final StudyMemberRepository studyMemberRepository; - private final StudyService studyService; - - public MemberService( - MemberRepository memberRepository, - StudyMemberRepository studyMemberRepository, - StudyService studyService - ) { - this.memberRepository = memberRepository; - this.studyMemberRepository = studyMemberRepository; - this.studyService = studyService; - } - - @Transactional(readOnly = true) - public ProfileResponse findById(Long id) { - Member member = findMemberById(id); - - List finishedStudyResponses = - studyMemberRepository.findAllByMemberId(member.getId()) - .stream() - .filter(StudyMember::isStudyEnd) - .map(this::createFinishedStudyResponse) - .toList(); - - return new ProfileResponse( - member.getId(), - member.getNickname(), - member.getGithubId(), - member.getProfileImageUrl(), - studyService.calculateSuccessRate(member), - calculateNumberOfSuccessRounds(member), - member.calculateProgress(), - member.getTier().getOrder(), - member.getIntroduction(), - finishedStudyResponses - ); - } - - public Member findMemberById(Long id) { - return memberRepository.findByIdAndDeletedFalse(id) - .orElseThrow(() -> new MemberNotFoundException( - "해당 멤버가 존재하지 않습니다.", String.valueOf(id) - ) - ); - } - - private FinishedStudyResponse createFinishedStudyResponse(StudyMember studyMember) { - Study study = studyMember.getStudy(); - return new FinishedStudyResponse( - study.getId(), - study.getName(), - study.calculateAverageTier(), - study.sizeOfCurrentMembers(), - study.getNumberOfMaximumMembers(), - studyMember.isSuccess() - ); - } - - private int calculateNumberOfSuccessRounds(Member member) { - List studies = studyMemberRepository.findAllByMemberId(member.getId()).stream() - .filter(StudyMember::isNotApplicant) - .map(StudyMember::getStudy) - .toList(); - - return (int) studies.stream() - .map(Study::getRounds) - .flatMap(List::stream) - .filter(round -> round.isMustDoDone(member)) - .count(); - } - - @Transactional - public void update(Member member, ProfileUpdateRequest request) { - member.updateProfile(request.nickname(), request.introduction()); - } - - @Transactional - public void delete(Member member) { - memberRepository.delete(member); - } - - @Transactional(readOnly = true) - public NicknameValidationResponse existsByNickname(String nickname) { - boolean exists = memberRepository.existsByNickname(new Nickname(nickname)); - return new NicknameValidationResponse(exists); - } -} diff --git a/backend/src/main/java/com/yigongil/backend/application/MustDoService.java b/backend/src/main/java/com/yigongil/backend/application/MustDoService.java deleted file mode 100644 index b1ed85e52..000000000 --- a/backend/src/main/java/com/yigongil/backend/application/MustDoService.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yigongil.backend.application; - -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.round.RoundRepository; -import com.yigongil.backend.exception.RoundNotFoundException; -import com.yigongil.backend.request.MustDoUpdateRequest; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class MustDoService { - - private final RoundRepository roundRepository; - - public MustDoService( - RoundRepository roundRepository - ) { - this.roundRepository = roundRepository; - } - - @Transactional - public void updateMustDo(Member member, Long roundId, MustDoUpdateRequest request) { - Round round = findRoundById(roundId); - round.updateMustDo(member, request.content()); - } - - private Round findRoundById(Long roundId) { - return roundRepository.findById(roundId) - .orElseThrow(() -> new RoundNotFoundException("존재하지 않는 회차입니다.", roundId)); - } -} diff --git a/backend/src/main/java/com/yigongil/backend/application/RoundService.java b/backend/src/main/java/com/yigongil/backend/application/RoundService.java deleted file mode 100644 index 237d95633..000000000 --- a/backend/src/main/java/com/yigongil/backend/application/RoundService.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.yigongil.backend.application; - -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.round.RoundRepository; -import com.yigongil.backend.domain.study.ProcessingStatus; -import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.domain.study.StudyRepository; -import com.yigongil.backend.domain.studymember.StudyMember; -import com.yigongil.backend.response.UpcomingStudyResponse; -import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -public class RoundService { - - private final RoundRepository roundRepository; - private final StudyRepository studyRepository; - - public RoundService( - RoundRepository roundRepository, - StudyRepository studyRepository - ) { - this.roundRepository = roundRepository; - this.studyRepository = studyRepository; - } - - @Transactional(readOnly = true) - public List findCurrentRoundOfStudies(Member member) { - List studies = studyRepository.findByMemberAndProcessingStatus(member, ProcessingStatus.PROCESSING); - List upcomingStudyResponses = new ArrayList<>(); - - for (Study study : studies) { - Round currentRound = study.getCurrentRound(); - StudyMember studyMember = study.getStudyMemberByMember(member); - - int leftDays = currentRound.calculateLeftDaysFrom(LocalDate.now()); - - upcomingStudyResponses.add( - new UpcomingStudyResponse( - study.getId(), - study.getName(), - currentRound.getMustDo(), - leftDays, - studyMember.calculateAccumulatedExperience(), - study.isMaster(member) - ) - ); - } - - return upcomingStudyResponses; - } -} diff --git a/backend/src/main/java/com/yigongil/backend/config/AuthConfig.java b/backend/src/main/java/com/yigongil/backend/config/AuthConfig.java index c75fefac1..829d74674 100644 --- a/backend/src/main/java/com/yigongil/backend/config/AuthConfig.java +++ b/backend/src/main/java/com/yigongil/backend/config/AuthConfig.java @@ -26,14 +26,14 @@ public void addInterceptors(InterceptorRegistry registry) { .excludePathPatterns("/login/github/tokens") .excludePathPatterns("/login/tokens/refresh") .excludePathPatterns("/login/fake/tokens") - .excludePathPatterns("/members/{id:[0-9]\\d*}") + .excludePathPatterns("/members/{studyId:[0-9]\\d*}") .excludePathPatterns("/members/exists") .excludePathPatterns("/api/**") .excludePathPatterns("/api-docs/**") .excludePathPatterns("/swagger-ui/**") .excludePathPatterns("/actuator/**") .excludePathPatterns("/fake/proceed") - .excludePathPatterns("/studies/{id:[0-9]\\d*}/rounds/{id:[0-9]\\d*}/progress-rate"); + .excludePathPatterns("/studies/{studyId:[0-9]\\d*}/rounds/{studyId:[0-9]\\d*}/progress-rate"); } diff --git a/backend/src/main/java/com/yigongil/backend/application/AuthService.java b/backend/src/main/java/com/yigongil/backend/config/auth/AuthService.java similarity index 89% rename from backend/src/main/java/com/yigongil/backend/application/AuthService.java rename to backend/src/main/java/com/yigongil/backend/config/auth/AuthService.java index 6f0d559fc..c05678682 100644 --- a/backend/src/main/java/com/yigongil/backend/application/AuthService.java +++ b/backend/src/main/java/com/yigongil/backend/config/auth/AuthService.java @@ -1,10 +1,9 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.config.auth; -import com.yigongil.backend.config.auth.JwtTokenProvider; import com.yigongil.backend.config.oauth.GithubOauth; import com.yigongil.backend.config.oauth.GithubProfileResponse; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; import com.yigongil.backend.request.TokenRequest; import com.yigongil.backend.response.TokenResponse; import java.util.Optional; diff --git a/backend/src/main/java/com/yigongil/backend/config/auth/MemberArgumentResolver.java b/backend/src/main/java/com/yigongil/backend/config/auth/MemberArgumentResolver.java index 05406b860..86f9af3f8 100644 --- a/backend/src/main/java/com/yigongil/backend/config/auth/MemberArgumentResolver.java +++ b/backend/src/main/java/com/yigongil/backend/config/auth/MemberArgumentResolver.java @@ -1,7 +1,7 @@ package com.yigongil.backend.config.auth; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; import com.yigongil.backend.exception.AuthorizationException; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/com/yigongil/backend/domain/BaseEntity.java b/backend/src/main/java/com/yigongil/backend/domain/base/BaseEntity.java similarity index 52% rename from backend/src/main/java/com/yigongil/backend/domain/BaseEntity.java rename to backend/src/main/java/com/yigongil/backend/domain/base/BaseEntity.java index 90980c463..9ac525b09 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/BaseEntity.java +++ b/backend/src/main/java/com/yigongil/backend/domain/base/BaseEntity.java @@ -1,17 +1,11 @@ -package com.yigongil.backend.domain; +package com.yigongil.backend.domain.base; -import com.yigongil.backend.domain.event.DomainEvent; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; import javax.persistence.Column; import javax.persistence.EntityListeners; import javax.persistence.MappedSuperclass; -import javax.persistence.Transient; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; -import org.springframework.data.domain.AfterDomainEventPublication; -import org.springframework.data.domain.DomainEvents; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @EntityListeners(AuditingEntityListener.class) @@ -25,23 +19,6 @@ public abstract class BaseEntity { @LastModifiedDate protected LocalDateTime updatedAt; - @Transient - private Collection domainEvents = new ArrayList<>(); - - @DomainEvents - public Collection events() { - return domainEvents; - } - - @AfterDomainEventPublication - public void clear() { - domainEvents.clear(); - } - - protected void register(DomainEvent event) { - domainEvents.add(event); - } - public LocalDateTime getCreatedAt() { return createdAt; } diff --git a/backend/src/main/java/com/yigongil/backend/domain/base/BaseRootEntity.java b/backend/src/main/java/com/yigongil/backend/domain/base/BaseRootEntity.java new file mode 100644 index 000000000..05f86ab89 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/base/BaseRootEntity.java @@ -0,0 +1,30 @@ +package com.yigongil.backend.domain.base; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.domain.AbstractAggregateRoot; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@EntityListeners(AuditingEntityListener.class) +@MappedSuperclass +public class BaseRootEntity extends AbstractAggregateRoot { + + @CreatedDate + @Column(nullable = false) + protected LocalDateTime createdAt; + + @LastModifiedDate + protected LocalDateTime updatedAt; + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/certification/Certification.java b/backend/src/main/java/com/yigongil/backend/domain/certification/Certification.java index 32824b93f..d477434ef 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/certification/Certification.java +++ b/backend/src/main/java/com/yigongil/backend/domain/certification/Certification.java @@ -1,7 +1,7 @@ package com.yigongil.backend.domain.certification; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.round.Round; import com.yigongil.backend.domain.study.Study; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationApi.java b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationApi.java new file mode 100644 index 000000000..0d3dbf2ad --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationApi.java @@ -0,0 +1,33 @@ +package com.yigongil.backend.domain.certification; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.CertificationCreateRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "인증", description = "인증 관련 api") +public interface CertificationApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400", content = @Content), + @ApiResponse(responseCode = "401", content = @Content), + @ApiResponse(responseCode = "404", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "인증 피드 등록") + ResponseEntity createCertification( + @Schema(hidden = true) Member member, + @Parameter(description = "피드가 등록되는 스터디 studyId", required = true) Long id, + CertificationCreateRequest request + ); +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationController.java b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationController.java new file mode 100644 index 000000000..e29a5e725 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationController.java @@ -0,0 +1,31 @@ +package com.yigongil.backend.domain.certification; + +import com.yigongil.backend.config.auth.Authorization; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.CertificationCreateRequest; +import java.net.URI; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CertificationController implements CertificationApi { + + private final CertificationService certificationService; + + public CertificationController(CertificationService certificationService) { + this.certificationService = certificationService; + } + + @PostMapping("/studies/{id}/certifications") + public ResponseEntity createCertification( + @Authorization Member member, + @PathVariable Long id, + @RequestBody CertificationCreateRequest request + ) { + Long certificationId = certificationService.createCertification(member, id, request); + return ResponseEntity.created(URI.create("/studies/" + id + "/certifications/" + certificationId)).build(); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationRepository.java b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationRepository.java index 179c0ddc4..4a9dc8290 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationRepository.java @@ -1,5 +1,6 @@ package com.yigongil.backend.domain.certification; +import com.yigongil.backend.exception.NoCertificationException; import java.util.Optional; import org.springframework.data.repository.Repository; @@ -8,4 +9,8 @@ public interface CertificationRepository extends Repository Certification save(Certification feedPost); Optional findByRoundIdAndAuthorId(Long roundId, Long memberId); + + default Certification getByRoundIdAndAuthorId(Long roundId, Long memberId) { + return findByRoundIdAndAuthorId(roundId, memberId).orElseThrow(() -> new NoCertificationException("인증을 찾을 수 없습니다", String.valueOf(memberId))); + } } diff --git a/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationService.java b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationService.java new file mode 100644 index 000000000..e9bc78ed0 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/certification/CertificationService.java @@ -0,0 +1,46 @@ +package com.yigongil.backend.domain.certification; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.round.Round; +import com.yigongil.backend.domain.round.RoundRepository; +import com.yigongil.backend.domain.round.RoundStatus; +import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyRepository; +import com.yigongil.backend.exception.RoundNotFoundException; +import com.yigongil.backend.request.CertificationCreateRequest; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CertificationService { + + private final CertificationRepository certificationRepository; + private final StudyRepository studyRepository; + private final RoundRepository roundRepository; + + public CertificationService(CertificationRepository certificationRepository, StudyRepository studyRepository, RoundRepository roundRepository) { + this.certificationRepository = certificationRepository; + this.studyRepository = studyRepository; + this.roundRepository = roundRepository; + } + + @Transactional + public Long createCertification(Member member, Long studyId, CertificationCreateRequest request) { + Study study = studyRepository.getById(studyId); + return createCertification(study, member, request).getId(); + } + + private Certification createCertification(Study study, Member member, CertificationCreateRequest request) { + Round round = roundRepository.findByStudyIdAndRoundStatus(study.getId(), RoundStatus.IN_PROGRESS) + .orElseThrow(() -> new RoundNotFoundException("", -1L)); + Certification feedPost = Certification.builder() + .author(member) + .study(study) + .round(round) + .content(request.content()) + .imageUrl(request.imageUrl()) + .build(); + round.completeRound(member); + return certificationRepository.save(feedPost); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedApi.java b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedApi.java new file mode 100644 index 000000000..025da99f9 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedApi.java @@ -0,0 +1,33 @@ +package com.yigongil.backend.domain.feedpost; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.FeedPostCreateRequest; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "피드", description = "피드 관련 api") +public interface FeedApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400", content = @Content), + @ApiResponse(responseCode = "401", content = @Content), + @ApiResponse(responseCode = "404", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "일반 피드 등록") + ResponseEntity createFeedPost( + @Schema(hidden = true) Member member, + @Parameter(description = "피드가 등록되는 스터디 studyId", required = true) Long id, + FeedPostCreateRequest request + ); +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedController.java b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedController.java new file mode 100644 index 000000000..ebb29b9e5 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedController.java @@ -0,0 +1,30 @@ +package com.yigongil.backend.domain.feedpost; + +import com.yigongil.backend.config.auth.Authorization; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.FeedPostCreateRequest; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class FeedController implements FeedApi { + + private final FeedService feedService; + + public FeedController(FeedService feedService) { + this.feedService = feedService; + } + + @PostMapping("/studies/{id}/feeds") + public ResponseEntity createFeedPost( + @Authorization Member member, + @PathVariable Long id, + @RequestBody FeedPostCreateRequest request + ) { + feedService.createFeedPost(member, id, request); + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedPost.java b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedPost.java index 1c2b96324..139c096bd 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedPost.java +++ b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedPost.java @@ -1,7 +1,7 @@ package com.yigongil.backend.domain.feedpost; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; import java.time.LocalDateTime; import java.util.Objects; diff --git a/backend/src/main/java/com/yigongil/backend/application/FeedService.java b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedService.java similarity index 57% rename from backend/src/main/java/com/yigongil/backend/application/FeedService.java rename to backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedService.java index a27600cd7..452ee2609 100644 --- a/backend/src/main/java/com/yigongil/backend/application/FeedService.java +++ b/backend/src/main/java/com/yigongil/backend/domain/feedpost/FeedService.java @@ -1,11 +1,9 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.domain.feedpost; -import com.yigongil.backend.domain.feedpost.FeedPost; -import com.yigongil.backend.domain.feedpost.FeedPostRepository; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyRepository; import com.yigongil.backend.request.FeedPostCreateRequest; -import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,24 +11,24 @@ public class FeedService { private final FeedPostRepository feedPostRepository; + private final StudyRepository studyRepository; - public FeedService(FeedPostRepository feedPostRepository) { + public FeedService(FeedPostRepository feedPostRepository, StudyRepository studyRepository) { this.feedPostRepository = feedPostRepository; + this.studyRepository = studyRepository; } @Transactional - public void createFeedPost(Member member, Study study, FeedPostCreateRequest request) { + public void createFeedPost(Member member, Long studyId, FeedPostCreateRequest request) { + Study study = studyRepository.getById(studyId); + FeedPost regularFeedPost = FeedPost.builder() .author(member) .study(study) .imageUrl(request.imageUrl()) .content(request.content()) .build(); - feedPostRepository.save(regularFeedPost); - } - @Transactional(readOnly = true) - public List findFeedPosts(Long studyId, Long oldestFeedPostId) { - return feedPostRepository.findAllByStudyIdStartWithOldestFeedPostId(studyId, oldestFeedPostId); + feedPostRepository.save(regularFeedPost); } } diff --git a/backend/src/main/java/com/yigongil/backend/ui/ImageResourceController.java b/backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceController.java similarity index 91% rename from backend/src/main/java/com/yigongil/backend/ui/ImageResourceController.java rename to backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceController.java index 3ba15aa3c..fc5bb6722 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/ImageResourceController.java +++ b/backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceController.java @@ -1,6 +1,5 @@ -package com.yigongil.backend.ui; +package com.yigongil.backend.domain.image; -import com.yigongil.backend.application.ImageResourceService; import java.net.URI; import org.springframework.context.annotation.Profile; import org.springframework.http.ResponseEntity; diff --git a/backend/src/main/java/com/yigongil/backend/application/ImageResourceService.java b/backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceService.java similarity index 98% rename from backend/src/main/java/com/yigongil/backend/application/ImageResourceService.java rename to backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceService.java index e026bf1fc..9fcfe65e4 100644 --- a/backend/src/main/java/com/yigongil/backend/application/ImageResourceService.java +++ b/backend/src/main/java/com/yigongil/backend/domain/image/ImageResourceService.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.domain.image; import com.yigongil.backend.exception.ImageToBytesException; import com.yigongil.backend.exception.InvalidImageExtensionException; diff --git a/backend/src/main/java/com/yigongil/backend/domain/meetingdayoftheweek/MeetingDayOfTheWeek.java b/backend/src/main/java/com/yigongil/backend/domain/meetingdayoftheweek/MeetingDayOfTheWeek.java deleted file mode 100644 index 393de93b3..000000000 --- a/backend/src/main/java/com/yigongil/backend/domain/meetingdayoftheweek/MeetingDayOfTheWeek.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.yigongil.backend.domain.meetingdayoftheweek; - -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.study.Study; -import java.time.DayOfWeek; -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 lombok.Builder; -import lombok.Getter; - -@Getter -@Entity -public class MeetingDayOfTheWeek extends BaseEntity { - - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Id - private Long id; - - @ManyToOne - @JoinColumn(name = "study_id") - private Study study; - - @Enumerated(EnumType.ORDINAL) - private DayOfWeek dayOfWeek; - - protected MeetingDayOfTheWeek() { - } - - @Builder - public MeetingDayOfTheWeek(Long id, Study study, DayOfWeek dayOfWeek) { - this.id = id; - this.study = study; - this.dayOfWeek = dayOfWeek; - } - - public boolean isSameDayOfWeek(DayOfWeek dayOfWeek) { - return this.dayOfWeek.equals(dayOfWeek); - } - - public boolean comesNext(DayOfWeek dayOfWeek) { - return this.dayOfWeek.compareTo(dayOfWeek) > 0; - } - - public int getOrder() { - return this.dayOfWeek.getValue(); - } -} - - diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/application/MemberService.java b/backend/src/main/java/com/yigongil/backend/domain/member/application/MemberService.java new file mode 100644 index 000000000..7dc3ce494 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/member/application/MemberService.java @@ -0,0 +1,35 @@ +package com.yigongil.backend.domain.member.application; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.member.domain.Nickname; +import com.yigongil.backend.request.ProfileUpdateRequest; +import com.yigongil.backend.response.NicknameValidationResponse; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class MemberService { + + private final MemberRepository memberRepository; + + public MemberService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + @Transactional + public void update(Member member, ProfileUpdateRequest request) { + member.updateProfile(request.nickname(), request.introduction()); + } + + @Transactional + public void delete(Member member) { + memberRepository.delete(member); + } + + @Transactional(readOnly = true) + public NicknameValidationResponse existsByNickname(String nickname) { + boolean exists = memberRepository.existsByNickname(new Nickname(nickname)); + return new NicknameValidationResponse(exists); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/Introduction.java b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Introduction.java similarity index 96% rename from backend/src/main/java/com/yigongil/backend/domain/member/Introduction.java rename to backend/src/main/java/com/yigongil/backend/domain/member/domain/Introduction.java index 0a15bf772..8c98f0cd4 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/member/Introduction.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Introduction.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.member; +package com.yigongil.backend.domain.member.domain; import com.yigongil.backend.exception.InvalidIntroductionLengthException; import java.util.Objects; diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/Member.java b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Member.java similarity index 94% rename from backend/src/main/java/com/yigongil/backend/domain/member/Member.java rename to backend/src/main/java/com/yigongil/backend/domain/member/domain/Member.java index 054eb242d..26b83bf44 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/member/Member.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Member.java @@ -1,6 +1,6 @@ -package com.yigongil.backend.domain.member; +package com.yigongil.backend.domain.member.domain; -import com.yigongil.backend.domain.BaseEntity; +import com.yigongil.backend.domain.base.BaseRootEntity; import com.yigongil.backend.domain.event.MemberDeleteEvent; import java.util.Objects; import javax.persistence.Column; @@ -17,11 +17,7 @@ @Getter @SQLDelete(sql = Member.DELETE_QUERY) @Entity -public class Member extends BaseEntity { - - private static final int MASTER_NUMBER = 0; - private static final int PARTICIPANT_NUMBER = 1; - private static final int MAXIMUM_TIER = 5; +public class Member extends BaseRootEntity { protected static final String DELETE_QUERY = """ update member @@ -32,7 +28,9 @@ public class Member extends BaseEntity { deleted = true where id = ? """; - + private static final int MASTER_NUMBER = 0; + private static final int PARTICIPANT_NUMBER = 1; + private static final int MAXIMUM_TIER = 5; @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Long id; @@ -103,7 +101,7 @@ public String getIntroduction() { @PreRemove public void registerDeleteEvent() { - register(new MemberDeleteEvent(id)); + registerEvent(new MemberDeleteEvent(id)); } public void addExperience(int exp) { diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/MemberRepository.java b/backend/src/main/java/com/yigongil/backend/domain/member/domain/MemberRepository.java similarity index 51% rename from backend/src/main/java/com/yigongil/backend/domain/member/MemberRepository.java rename to backend/src/main/java/com/yigongil/backend/domain/member/domain/MemberRepository.java index 16ce16733..a52375408 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/member/MemberRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/domain/MemberRepository.java @@ -1,5 +1,6 @@ -package com.yigongil.backend.domain.member; +package com.yigongil.backend.domain.member.domain; +import com.yigongil.backend.exception.MemberNotFoundException; import java.util.Optional; import org.springframework.data.repository.Repository; @@ -7,6 +8,13 @@ public interface MemberRepository extends Repository { Optional findByIdAndDeletedFalse(Long id); + default Member getById(Long id) { + return findByIdAndDeletedFalse(id).orElseThrow(() -> new MemberNotFoundException( + "해당 멤버가 존재하지 않습니다.", String.valueOf(id) + ) + ); + } + Optional findByGithubId(String githubId); Member save(Member member); diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/Nickname.java b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Nickname.java similarity index 97% rename from backend/src/main/java/com/yigongil/backend/domain/member/Nickname.java rename to backend/src/main/java/com/yigongil/backend/domain/member/domain/Nickname.java index e21af0e32..4ccc214f0 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/member/Nickname.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Nickname.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.member; +package com.yigongil.backend.domain.member.domain; import com.yigongil.backend.exception.InvalidNicknameLengthException; import com.yigongil.backend.exception.InvalidNicknamePatternException; diff --git a/backend/src/main/java/com/yigongil/backend/domain/member/Tier.java b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Tier.java similarity index 96% rename from backend/src/main/java/com/yigongil/backend/domain/member/Tier.java rename to backend/src/main/java/com/yigongil/backend/domain/member/domain/Tier.java index e128a14cf..56864708a 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/member/Tier.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/domain/Tier.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.member; +package com.yigongil.backend.domain.member.domain; import java.util.Arrays; import java.util.Comparator; diff --git a/backend/src/main/java/com/yigongil/backend/ui/MemberController.java b/backend/src/main/java/com/yigongil/backend/domain/member/ui/MemberController.java similarity index 60% rename from backend/src/main/java/com/yigongil/backend/ui/MemberController.java rename to backend/src/main/java/com/yigongil/backend/domain/member/ui/MemberController.java index ae9037602..8c902f224 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/MemberController.java +++ b/backend/src/main/java/com/yigongil/backend/domain/member/ui/MemberController.java @@ -1,20 +1,17 @@ -package com.yigongil.backend.ui; +package com.yigongil.backend.domain.member.ui; -import com.yigongil.backend.application.MemberService; import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.application.MemberService; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.request.ProfileUpdateRequest; -import com.yigongil.backend.response.MyProfileResponse; import com.yigongil.backend.response.NicknameValidationResponse; import com.yigongil.backend.response.OnboardingCheckResponse; -import com.yigongil.backend.response.ProfileResponse; import com.yigongil.backend.ui.doc.MemberApi; import javax.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; @@ -30,34 +27,10 @@ public MemberController(MemberService memberService) { this.memberService = memberService; } - @GetMapping(path = "/{id}") - public ResponseEntity findProfile(@PathVariable Long id) { - ProfileResponse response = memberService.findById(id); - return ResponseEntity.ok(response); - } - - @GetMapping(path = "/my") - public ResponseEntity findMyProfile(@Authorization Member member) { - ProfileResponse profile = memberService.findById(member.getId()); - MyProfileResponse response = new MyProfileResponse( - profile.id(), - profile.nickname(), - profile.githubId(), - profile.profileImageUrl(), - profile.successRate(), - profile.successfulRoundCount(), - profile.tierProgress(), - profile.tier(), - profile.introduction() - ); - - return ResponseEntity.ok(response); - } - @PatchMapping public ResponseEntity updateProfile( - @Authorization Member member, - @RequestBody @Valid ProfileUpdateRequest request + @Authorization Member member, + @RequestBody @Valid ProfileUpdateRequest request ) { memberService.update(member, request); return ResponseEntity.ok().build(); diff --git a/backend/src/main/java/com/yigongil/backend/domain/report/MemberReport.java b/backend/src/main/java/com/yigongil/backend/domain/report/MemberReport.java index 591b0b65e..f3081ceef 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/report/MemberReport.java +++ b/backend/src/main/java/com/yigongil/backend/domain/report/MemberReport.java @@ -1,6 +1,6 @@ package com.yigongil.backend.domain.report; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidReportException; import java.time.LocalDate; import javax.persistence.Entity; diff --git a/backend/src/main/java/com/yigongil/backend/domain/report/Report.java b/backend/src/main/java/com/yigongil/backend/domain/report/Report.java index b58ffc469..b9976009e 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/report/Report.java +++ b/backend/src/main/java/com/yigongil/backend/domain/report/Report.java @@ -1,7 +1,7 @@ package com.yigongil.backend.domain.report; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidReportContentLengthException; import com.yigongil.backend.exception.InvalidReportTitleLengthException; import java.time.LocalDate; diff --git a/backend/src/main/java/com/yigongil/backend/ui/ReportController.java b/backend/src/main/java/com/yigongil/backend/domain/report/ReportController.java similarity index 91% rename from backend/src/main/java/com/yigongil/backend/ui/ReportController.java rename to backend/src/main/java/com/yigongil/backend/domain/report/ReportController.java index 24454b29a..4196988e8 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/ReportController.java +++ b/backend/src/main/java/com/yigongil/backend/domain/report/ReportController.java @@ -1,8 +1,7 @@ -package com.yigongil.backend.ui; +package com.yigongil.backend.domain.report; -import com.yigongil.backend.application.ReportService; import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.request.MemberReportCreateRequest; import com.yigongil.backend.request.StudyReportCreateRequest; import com.yigongil.backend.ui.doc.ReportApi; diff --git a/backend/src/main/java/com/yigongil/backend/application/ReportService.java b/backend/src/main/java/com/yigongil/backend/domain/report/ReportService.java similarity index 78% rename from backend/src/main/java/com/yigongil/backend/application/ReportService.java rename to backend/src/main/java/com/yigongil/backend/domain/report/ReportService.java index 1656024bf..d0e79b6d5 100644 --- a/backend/src/main/java/com/yigongil/backend/application/ReportService.java +++ b/backend/src/main/java/com/yigongil/backend/domain/report/ReportService.java @@ -1,10 +1,9 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.domain.report; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.report.MemberReport; -import com.yigongil.backend.domain.report.ReportRepository; -import com.yigongil.backend.domain.report.StudyReport; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyService; import com.yigongil.backend.request.MemberReportCreateRequest; import com.yigongil.backend.request.StudyReportCreateRequest; import org.springframework.stereotype.Service; @@ -14,22 +13,22 @@ public class ReportService { private final ReportRepository reportRepository; - private final MemberService memberService; + private final MemberRepository memberRepository; private final StudyService studyService; public ReportService( ReportRepository reportRepository, - MemberService memberService, + MemberRepository memberRepository, StudyService studyService ) { this.reportRepository = reportRepository; - this.memberService = memberService; + this.memberRepository = memberRepository; this.studyService = studyService; } @Transactional public void reportMember(Member reporter, MemberReportCreateRequest request) { - Member reportedMember = memberService.findMemberById(request.reportedMemberId()); + Member reportedMember = memberRepository.getById(request.reportedMemberId()); reportRepository.save( MemberReport.builder() .reporter(reporter) diff --git a/backend/src/main/java/com/yigongil/backend/domain/report/StudyReport.java b/backend/src/main/java/com/yigongil/backend/domain/report/StudyReport.java index 51c998301..642c4c178 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/report/StudyReport.java +++ b/backend/src/main/java/com/yigongil/backend/domain/report/StudyReport.java @@ -1,6 +1,6 @@ package com.yigongil.backend.domain.report; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; import java.time.LocalDate; import javax.persistence.Entity; diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/Round.java b/backend/src/main/java/com/yigongil/backend/domain/round/Round.java index 79795fb48..1cce4b803 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/round/Round.java +++ b/backend/src/main/java/com/yigongil/backend/domain/round/Round.java @@ -1,9 +1,9 @@ package com.yigongil.backend.domain.round; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.meetingdayoftheweek.MeetingDayOfTheWeek; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import static java.util.stream.Collectors.toMap; + +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; import com.yigongil.backend.exception.InvalidTodoLengthException; import com.yigongil.backend.exception.NecessaryTodoNotExistException; @@ -13,6 +13,7 @@ import java.time.LocalDate; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; @@ -25,10 +26,9 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.TableGenerator; import lombok.Builder; import lombok.Getter; -import org.hibernate.annotations.Cascade; -import org.hibernate.annotations.CascadeType; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; @@ -39,13 +39,15 @@ public class Round extends BaseEntity { private static final int MAX_TODO_CONTENT_LENGTH = 20; private static final int MIN_TODO_CONTENT_LENGTH = 1; - @GeneratedValue(strategy = GenerationType.IDENTITY) + @GeneratedValue(strategy = GenerationType.TABLE, generator = "sequence_generator") + @TableGenerator(name = "sequence_generator", table = "round_sequence", + pkColumnName = "sequence_name", pkColumnValue = "id", + initialValue = 1, allocationSize=500) @Id private Long id; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "study_id", nullable = false) - private Study study; + @Column(name = "study_id") + private Long studyId; @Column(length = MAX_TODO_CONTENT_LENGTH) private String mustDo; @@ -54,15 +56,13 @@ public class Round extends BaseEntity { @JoinColumn(name = "master_id", nullable = false) private Member master; - @Cascade(CascadeType.PERSIST) @OnDelete(action = OnDeleteAction.CASCADE) @OneToMany - @JoinColumn(name = "round_id", nullable = false) + @JoinColumn(name = "round_id", nullable = false, updatable = false) private List roundOfMembers = new ArrayList<>(); - @Cascade(CascadeType.PERSIST) - @ManyToOne(fetch = FetchType.LAZY) - private MeetingDayOfTheWeek meetingDayOfTheWeek; + @Enumerated(EnumType.ORDINAL) + private DayOfWeek dayOfWeek; @Column(nullable = false) private Integer weekNumber; @@ -76,26 +76,26 @@ protected Round() { @Builder public Round( Long id, - Study study, + Long studyId, String mustDo, Member master, List roundOfMembers, - MeetingDayOfTheWeek meetingDayOfTheWeek, + DayOfWeek dayOfWeek, Integer weekNumber ) { this.id = id; - this.study = study; + this.studyId = studyId; this.mustDo = mustDo; this.master = master; this.roundOfMembers = roundOfMembers == null ? new ArrayList<>() : roundOfMembers; - this.meetingDayOfTheWeek = meetingDayOfTheWeek; + this.dayOfWeek = dayOfWeek; this.weekNumber = weekNumber; } - public static Round of(MeetingDayOfTheWeek meetingDayOfTheWeek, Study study, Integer weekNumber) { + public static Round of(DayOfWeek dayOfWeek, Study study, Integer weekNumber) { return Round.builder() - .study(study) - .meetingDayOfTheWeek(meetingDayOfTheWeek) + .studyId(study.getId()) + .dayOfWeek(dayOfWeek) .weekNumber(weekNumber) .master(study.getMaster()) .roundOfMembers(RoundOfMember.from(study)) @@ -132,23 +132,23 @@ public void completeRound(Member member) { if (mustDo == null) { throw new NecessaryTodoNotExistException(" 머스트두가 생성되지 않았습니다.", String.valueOf(id)); } - findRoundOfMemberBy(member).completeRound(); + findRoundOfMemberBy(member.getId()).completeRound(); } public boolean isMustDoDone(Member member) { - return findRoundOfMemberBy(member).isDone(); + return findRoundOfMemberBy(member.getId()).isDone(); } public boolean isEndAt(LocalDate date) { - return meetingDayOfTheWeek.isSameDayOfWeek(date.getDayOfWeek()); + return dayOfWeek == date.getDayOfWeek(); } - public RoundOfMember findRoundOfMemberBy(Member member) { + public RoundOfMember findRoundOfMemberBy(Long memberId) { return roundOfMembers.stream() - .filter(roundOfMember -> roundOfMember.isMemberEquals(member)) + .filter(roundOfMember -> roundOfMember.isMemberEquals(memberId)) .findAny() .orElseThrow( - () -> new NotStudyMemberException("해당 스터디의 멤버가 아닙니다.", member.getGithubId()) + () -> new NotStudyMemberException("해당 스터디의 멤버가 아닙니다.", memberId.toString()) ); } @@ -169,50 +169,73 @@ public void finish() { } public boolean isSuccess(Member member) { - return findRoundOfMemberBy(member).isDone(); + return findRoundOfMemberBy(member.getId()).isDone(); } - public boolean isMaster(Member member) { - return master.equals(member); + public boolean isMaster(Long memberId) { + return master.equals(memberId); } public boolean isSameWeek(Integer weekNumber) { return this.weekNumber == weekNumber; } - public boolean isSameDayOfWeek(MeetingDayOfTheWeek meetingDayOfTheWeek) { - return this.meetingDayOfTheWeek.equals(meetingDayOfTheWeek); + public boolean isSameDayOfWeek(DayOfWeek dayOfWeek) { + return this.dayOfWeek.equals(dayOfWeek); } public boolean isNextDayOfWeek(DayOfWeek dayOfWeek) { - return this.meetingDayOfTheWeek.comesNext(dayOfWeek); + return this.dayOfWeek.getValue() > dayOfWeek.getValue(); } public boolean isInProgress() { return roundStatus == RoundStatus.IN_PROGRESS; } + public boolean isNotStarted() { + return roundStatus == RoundStatus.NOT_STARTED; + } + public int calculateLeftDaysFrom(LocalDate date) { - int gap = meetingDayOfTheWeek.getDayOfWeek().getValue() - date.getDayOfWeek().getValue(); + int gap = dayOfWeek.getValue() - date.getDayOfWeek().getValue(); if (gap < 0) { return gap + DayOfWeek.values().length; } return gap; } - public DayOfWeek getDayOfWeek() { - return meetingDayOfTheWeek.getDayOfWeek(); - } - public boolean isBeforeOrSame(Integer minimumWeeks) { return weekNumber <= minimumWeeks; } - public void exit(Member member) { - if (isMaster(member)) { - throw new NotStudyMemberException("스터디장은 스터디를 나갈 수 없습니다.", member.getGithubId()); + public Map calculateExperience(Integer roundScore) { + return roundOfMembers.stream() + .collect(toMap(RoundOfMember::getMember, roundOfMember -> roundOfMember.isDone() ? roundScore : 0)); + } + + public void exit(Long memberId) { + if (isMaster(memberId)) { + throw new NotStudyMemberException("스터디장은 스터디를 나갈 수 없습니다.", memberId.toString()); } - roundOfMembers.remove(findRoundOfMemberBy(member)); + roundOfMembers.remove(findRoundOfMemberBy(memberId)); + } + + public Round createNextWeekRound() { + List list = roundOfMembers.stream() + .map( + roundOfMember -> RoundOfMember.builder() + .member( + roundOfMember.getMember()) + .isDone(false) + .build()) + .toList(); + return Round.builder() + .studyId(studyId) + .dayOfWeek(dayOfWeek) + .weekNumber(weekNumber + 1) + .master(master) + .roundOfMembers(list) + .build(); } @Override diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundApi.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundApi.java new file mode 100644 index 000000000..0b80d19c3 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundApi.java @@ -0,0 +1,41 @@ +package com.yigongil.backend.domain.round; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.MustDoUpdateRequest; +import com.yigongil.backend.response.RoundResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import org.springframework.http.ResponseEntity; + +@Tag(name = "라운드", description = "라운드 관련 api") +public interface RoundApi { + + @SecurityRequirement(name = "token") + @Operation(summary = "주별 회차 정보 조회") + ResponseEntity> findRoundDetailsOfWeek( + @Parameter(description = "조회할 스터디 studyId", required = true) Long studyId, + @Parameter(description = "조회할 주차", required = true) Integer weekNumber + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400"), + @ApiResponse(responseCode = "401"), + @ApiResponse(responseCode = "404") + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = " 머스트두 생성") + ResponseEntity updateMustDo( + @Schema(hidden = true) Member member, + @Parameter(description = " 머스트두를 생성할 라운드 studyId", required = true) Long roundId, + MustDoUpdateRequest request + ); +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundController.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundController.java new file mode 100644 index 000000000..0d9c8d38a --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundController.java @@ -0,0 +1,52 @@ +package com.yigongil.backend.domain.round; + +import com.yigongil.backend.config.auth.Authorization; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.request.MustDoUpdateRequest; +import com.yigongil.backend.response.RoundResponse; +import com.yigongil.backend.response.UpcomingStudyResponse; +import com.yigongil.backend.ui.doc.HomeApi; +import java.util.List; +import javax.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class RoundController implements HomeApi, RoundApi { + + private final RoundService roundService; + + public RoundController(RoundService roundService) { + this.roundService = roundService; + } + + @GetMapping("/home") + public ResponseEntity> home(@Authorization Member member) { + List response = roundService.findCurrentRoundOfStudies(member); + return ResponseEntity.ok(response); + } + + @PutMapping("/rounds/{roundId}/todos") + public ResponseEntity updateMustDo( + @Authorization Member member, + @PathVariable Long roundId, + @RequestBody @Valid MustDoUpdateRequest request + ) { + roundService.updateMustDo(member, roundId, request); + return ResponseEntity.ok().build(); + } + + @GetMapping("/studies/{studyId}/rounds") + public ResponseEntity> findRoundDetailsOfWeek( + @PathVariable Long studyId, + @RequestParam Integer weekNumber + ) { + List roundResponses = roundService.findRoundDetailsOfWeek(studyId, weekNumber); + return ResponseEntity.ok(roundResponses); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMember.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMember.java similarity index 85% rename from backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMember.java rename to backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMember.java index 290cb1ef0..eb06c8692 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMember.java +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMember.java @@ -1,7 +1,7 @@ -package com.yigongil.backend.domain.roundofmember; +package com.yigongil.backend.domain.round; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; import java.util.List; import javax.persistence.Column; @@ -56,7 +56,7 @@ public void completeRound() { this.isDone = true; } - public boolean isMemberEquals(Member member) { - return this.member.equals(member); + public boolean isMemberEquals(Long memberId) { + return this.member.getId().equals(memberId); } } diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberBatchRepository.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberBatchRepository.java new file mode 100644 index 000000000..c0f6b5d5c --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberBatchRepository.java @@ -0,0 +1,8 @@ +package com.yigongil.backend.domain.round; + +import java.util.List; +import java.util.Map; + +public interface RoundOfMemberBatchRepository { + void batchSaveAll(Map> roundOfMembers); +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberJdbcBatchRepository.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberJdbcBatchRepository.java new file mode 100644 index 000000000..2fefbd11b --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundOfMemberJdbcBatchRepository.java @@ -0,0 +1,49 @@ +package com.yigongil.backend.domain.round; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; +import org.springframework.stereotype.Repository; + +@Repository +public class RoundOfMemberJdbcBatchRepository implements RoundOfMemberBatchRepository { + + private final NamedParameterJdbcTemplate namedParameterJdbcTemplate; + + public RoundOfMemberJdbcBatchRepository(NamedParameterJdbcTemplate namedParameterJdbcTemplate) { + this.namedParameterJdbcTemplate = namedParameterJdbcTemplate; + } + + @Override + public void batchSaveAll(Map> roundOfMembersMap) { + String sql = """ + INSERT INTO round_of_member (round_id, member_id, is_done, created_at /* other columns */) + VALUES (:roundId, :memberId, :isDone, :createdAt /* other values */) + """; + + List parameters = new ArrayList<>(); + for (Map.Entry> entry : roundOfMembersMap.entrySet()) { + Long roundId = entry.getKey(); + for (RoundOfMember roundOfMember : entry.getValue()) { + SqlParameterSource param = new MapSqlParameterSource() + .addValue("roundId", roundId) + .addValue("memberId", roundOfMember.getMember().getId()) + .addValue("isDone", roundOfMember.isDone()) + .addValue("createdAt", LocalDateTime.now()); + + // Add other parameters as needed + parameters.add(param); + } + } + + SqlParameterSource[] batch = new SqlParameterSource[parameters.size()]; + batch = parameters.toArray(batch); + + namedParameterJdbcTemplate.batchUpdate(sql, batch); + } + +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundRepository.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundRepository.java index 0138ff6dc..31c36254a 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/round/RoundRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundRepository.java @@ -1,5 +1,7 @@ package com.yigongil.backend.domain.round; +import com.yigongil.backend.exception.RoundNotFoundException; +import java.time.DayOfWeek; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.EntityGraph; @@ -11,4 +13,25 @@ public interface RoundRepository extends Repository { Optional findById(Long id); List findAllByStudyIdAndWeekNumber(Long studyId, Integer weekNumber); + + Round save(Round round); + + List findByRoundStatusAndDayOfWeek( + RoundStatus roundStatus, + DayOfWeek dayOfWeek + ); + + void saveAll(Iterable rounds); + + List findAllByStudyId(Long studyId); + + Optional findByStudyIdAndRoundStatus(Long studyId, RoundStatus roundStatus); + + default Round getByStudyIdAndRoundStatus(Long studyId, RoundStatus roundStatus) { + return findByStudyIdAndRoundStatus(studyId, roundStatus).orElseThrow(() -> new RoundNotFoundException("", -1)); + } + + @EntityGraph(attributePaths = "roundOfMembers") + + List findByStudyIdInAndRoundStatus(Iterable todayDoneStudyIds, RoundStatus roundStatus); } diff --git a/backend/src/main/java/com/yigongil/backend/domain/round/RoundService.java b/backend/src/main/java/com/yigongil/backend/domain/round/RoundService.java new file mode 100644 index 000000000..c148ee3bb --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/round/RoundService.java @@ -0,0 +1,209 @@ +package com.yigongil.backend.domain.round; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.study.ProcessingStatus; +import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyExitedEvent; +import com.yigongil.backend.domain.study.StudyFinishedEvent; +import com.yigongil.backend.domain.study.StudyRepository; +import com.yigongil.backend.domain.study.StudyStartedEvent; +import com.yigongil.backend.exception.CannotEndException; +import com.yigongil.backend.exception.RoundNotFoundException; +import com.yigongil.backend.request.MustDoUpdateRequest; +import com.yigongil.backend.response.RoundResponse; +import com.yigongil.backend.response.UpcomingStudyResponse; +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import javax.persistence.EntityManager; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Service +public class RoundService { + + private final RoundRepository roundRepository; + private final StudyRepository studyRepository; + private final EntityManager entityManager; + private final RoundOfMemberBatchRepository roundOfMemberBatchRepository; + + public RoundService( + RoundRepository roundRepository, + StudyRepository studyRepository, + EntityManager entityManager, + RoundOfMemberBatchRepository roundOfMemberBatchRepository) { + this.roundRepository = roundRepository; + this.studyRepository = studyRepository; + this.entityManager = entityManager; + this.roundOfMemberBatchRepository = roundOfMemberBatchRepository; + } + + @Transactional(readOnly = true) + public List findCurrentRoundOfStudies(Member member) { + List studies = studyRepository.findByMemberAndProcessingStatus(member, ProcessingStatus.PROCESSING); + List upcomingStudyResponses = new ArrayList<>(); + + for (Study study : studies) { + Round currentRound = roundRepository.getByStudyIdAndRoundStatus(study.getId(), RoundStatus.IN_PROGRESS); + + int leftDays = currentRound.calculateLeftDaysFrom(LocalDate.now()); + + upcomingStudyResponses.add( + new UpcomingStudyResponse( + study.getId(), + study.getName(), + currentRound.getMustDo(), + leftDays, + calculateExperience(study.getId(), study.calculateRoundExperience()).get(member), + study.isMaster(member) + ) + ); + } + + return upcomingStudyResponses; + } + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void finishRounds(StudyFinishedEvent event) { + validateCanFinish(event); + Map memberToExperience = calculateExperience(event.studyId(), event.roundExperience()); + + for (Member member : memberToExperience.keySet()) { + member.addExperience(memberToExperience.get(member)); + } + } + + private void validateCanFinish(StudyFinishedEvent event) { + Round currentRound = roundRepository.getByStudyIdAndRoundStatus(event.studyId(), RoundStatus.IN_PROGRESS); + if (currentRound.isBeforeOrSame(event.minimumWeeks())) { + throw new CannotEndException("최소 진행 주차를 채우지 못했습니다.", String.valueOf(event.studyId())); + } + } + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void exit(StudyExitedEvent event) { + List rounds = roundRepository.findAllByStudyId(event.studyId()); + for (Round round : rounds) { + round.exit(event.memberId()); + } + } + + private Map calculateExperience(Long studyId, Integer roundExperience) { + List rounds = roundRepository.findAllByStudyId(studyId); + + Map memberToExperience = new HashMap<>(); + for (Round round : rounds) { + round.calculateExperience(roundExperience) + .forEach(((member, experience) -> memberToExperience.merge(member, experience, Integer::sum))); + } + return memberToExperience; + } + + @Transactional + public void updateMustDo(Member member, Long roundId, MustDoUpdateRequest request) { + Round round = findRoundById(roundId); + round.updateMustDo(member, request.content()); + } + + private Round findRoundById(Long roundId) { + return roundRepository.findById(roundId) + .orElseThrow( + () -> new RoundNotFoundException("존재하지 않는 회차입니다.", roundId)); + } + + @Transactional(readOnly = true) + public List findRoundDetailsOfWeek(Long id, Integer weekNumber) { + List roundsOfWeek = roundRepository.findAllByStudyIdAndWeekNumber(id, weekNumber); + return roundsOfWeek.stream() + .map(RoundResponse::from) + .toList(); + } + + @Transactional + public void proceedRound(LocalDate today) { + List rounds = roundRepository.findByRoundStatusAndDayOfWeek( + RoundStatus.IN_PROGRESS, + today.minusDays(1).getDayOfWeek() + ); + List todayDoneStudyIds = rounds.stream().map(Round::getStudyId).toList(); + Map> collect = roundRepository.findByStudyIdInAndRoundStatus( + todayDoneStudyIds, RoundStatus.NOT_STARTED) + .stream().collect(Collectors.groupingBy(Round::getStudyId)); + + List nextWeekRoundsAll = new ArrayList<>(); + List willBeFinished = new ArrayList<>(); + List willProgress = new ArrayList<>(); + for (Round round : rounds) { + Integer currentWeek = round.getWeekNumber(); + List upcomingCandidates = collect.get(round.getStudyId()); + + Round upcomingRound = upcomingCandidates.stream() + .filter(candidate -> candidate.isSameWeek(currentWeek) && candidate.isNotStarted()) + .min(Comparator.comparing(Round::getDayOfWeek)) + .orElseGet(() -> upcomingCandidates.stream() + .filter(candidate -> candidate.isSameWeek(currentWeek + 1)) + .min(Comparator.comparing(Round::getDayOfWeek)) + .orElseThrow(() -> new IllegalStateException("다음 주 라운드를 안 만들어놓음"))); + + willBeFinished.add(round); + willProgress.add(upcomingRound); + + if (!upcomingRound.isSameWeek(currentWeek)) { + List nextWeekRounds = upcomingCandidates.stream() + .filter(candidate -> candidate.isSameWeek(currentWeek + 1)) + .map(Round::createNextWeekRound) + .toList(); + nextWeekRoundsAll.addAll(nextWeekRounds); + + } + } + willBeFinished.forEach(Round::finish); + willProgress.forEach(Round::proceed); + roundRepository.saveAll(nextWeekRoundsAll); + entityManager.flush(); + + Map> map = nextWeekRoundsAll.stream().collect( + Collectors.toMap(Round::getId, Round::getRoundOfMembers)); + roundOfMemberBatchRepository.batchSaveAll(map); + } + + @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) + public void initializeRounds(StudyStartedEvent event) { + Study study = studyRepository.getById(event.studyId()); + List rounds = new ArrayList<>(); + rounds.addAll(createRoundsOfFirstWeek(study, event.dayOfWeeks(), event.startAt())); + rounds.addAll(createRoundsOf(2, study, event.dayOfWeeks())); + rounds.get(0).proceed(); + for (Round round : rounds) { + roundRepository.save(round); + } + entityManager.flush(); + Map> map = rounds.stream().collect( + Collectors.toMap(Round::getId, Round::getRoundOfMembers)); + roundOfMemberBatchRepository.batchSaveAll(map); + } + + private List createRoundsOfFirstWeek(Study study, List dayOfWeeks, LocalDate startAt) { + List rounds = dayOfWeeks.stream() + .filter(dayOfWeek -> dayOfWeek.compareTo(startAt.getDayOfWeek()) > 0) + .map(dayOfWeek -> Round.of(dayOfWeek, study, 1)) + .toList(); + if (rounds.isEmpty()) { + rounds = createRoundsOf(1, study, dayOfWeeks); + } + return rounds; + } + + private List createRoundsOf(Integer weekNumber, Study study, List dayOfWeeks) { + return dayOfWeeks.stream() + .map(dayOfWeek -> Round.of(dayOfWeek, study, weekNumber)) + .toList(); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/application/ScheduleService.java b/backend/src/main/java/com/yigongil/backend/domain/round/ScheduleService.java similarity index 59% rename from backend/src/main/java/com/yigongil/backend/application/ScheduleService.java rename to backend/src/main/java/com/yigongil/backend/domain/round/ScheduleService.java index cfcfe2496..280a8628e 100644 --- a/backend/src/main/java/com/yigongil/backend/application/ScheduleService.java +++ b/backend/src/main/java/com/yigongil/backend/domain/round/ScheduleService.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.domain.round; import java.time.LocalDate; import org.springframework.scheduling.annotation.Scheduled; @@ -8,15 +8,15 @@ @Service public class ScheduleService { - private final StudyService studyService; + private final RoundService roundService; - public ScheduleService(StudyService studyService) { - this.studyService = studyService; + public ScheduleService(RoundService roundService) { + this.roundService = roundService; } @Scheduled(cron = "0 0 0 * * *") @Transactional public void proceedRoundPerDay() { - studyService.proceedRound(LocalDate.now()); + roundService.proceedRound(LocalDate.now()); } } diff --git a/backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMemberRepository.java b/backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMemberRepository.java deleted file mode 100644 index c694cecc0..000000000 --- a/backend/src/main/java/com/yigongil/backend/domain/roundofmember/RoundOfMemberRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.yigongil.backend.domain.roundofmember; - -import java.util.List; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.repository.Repository; - -public interface RoundOfMemberRepository extends Repository { - - @EntityGraph(attributePaths = "member") - List findAllByIdIn(List ids); -} diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/Study.java b/backend/src/main/java/com/yigongil/backend/domain/study/Study.java index 4082e0aa1..61d2a303a 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/study/Study.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/Study.java @@ -1,13 +1,10 @@ package com.yigongil.backend.domain.study; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.meetingdayoftheweek.MeetingDayOfTheWeek; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; -import com.yigongil.backend.domain.studymember.Role; -import com.yigongil.backend.domain.studymember.StudyMember; -import com.yigongil.backend.domain.studymember.StudyResult; +import com.yigongil.backend.domain.base.BaseRootEntity; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.study.studymember.Role; +import com.yigongil.backend.domain.study.studymember.StudyMember; +import com.yigongil.backend.domain.study.studymember.StudyResult; import com.yigongil.backend.exception.ApplicantAlreadyExistException; import com.yigongil.backend.exception.CannotEndException; import com.yigongil.backend.exception.CannotStartException; @@ -17,14 +14,13 @@ import com.yigongil.backend.exception.InvalidStudyNameLengthException; import com.yigongil.backend.exception.NotStudyMasterException; import com.yigongil.backend.exception.NotStudyMemberException; -import com.yigongil.backend.exception.RoundNotFoundException; import java.time.DayOfWeek; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; -import java.util.Comparator; import java.util.List; +import javax.persistence.CollectionTable; import javax.persistence.Column; +import javax.persistence.ElementCollection; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -37,12 +33,10 @@ import lombok.Getter; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.CascadeType; -import org.hibernate.annotations.OnDelete; -import org.hibernate.annotations.OnDeleteAction; @Getter @Entity -public class Study extends BaseEntity { +public class Study extends BaseRootEntity { private static final int ONE_MEMBER = 1; private static final int MIN_NAME_LENGTH = 1; @@ -51,6 +45,7 @@ public class Study extends BaseEntity { private static final int MAX_MEMBER_SIZE = 8; private static final int INITIAL_ROUND_COUNT = 2; private static final int FIRST_WEEK = 1; + private static final int EXPERIENCE_BASE_UNIT = 1; @GeneratedValue(strategy = GenerationType.IDENTITY) @Id @@ -71,9 +66,10 @@ public class Study extends BaseEntity { private LocalDateTime endAt; - @Cascade(CascadeType.PERSIST) - @OneToMany(mappedBy = "study", orphanRemoval = true, fetch = FetchType.LAZY) - private List meetingDaysOfTheWeek = new ArrayList<>(); + @ElementCollection(targetClass = DayOfWeek.class) + @CollectionTable(name = "day_of_week") + @Enumerated(EnumType.ORDINAL) + private List dayOfWeeks = new ArrayList<>(); @Column(nullable = false) private Integer minimumWeeks; @@ -88,12 +84,6 @@ public class Study extends BaseEntity { @OneToMany(mappedBy = "study", orphanRemoval = true, fetch = FetchType.LAZY) private List studyMembers = new ArrayList<>(); - - @Cascade(CascadeType.PERSIST) - @OnDelete(action = OnDeleteAction.CASCADE) - @OneToMany(mappedBy = "study", orphanRemoval = true, fetch = FetchType.LAZY) - private List rounds = new ArrayList<>(); - protected Study() { } @@ -108,8 +98,7 @@ public Study( Member master, Integer minimumWeeks, Integer meetingDaysCountPerWeek, - Long currentRoundNumber, - List rounds + Long currentRoundNumber ) { name = name.strip(); validateNumberOfMaximumMembers(numberOfMaximumMembers); @@ -127,7 +116,6 @@ public Study( .member(master) .studyResult(StudyResult.NONE) .build()); - this.rounds = rounds == null ? new ArrayList<>() : rounds; this.minimumWeeks = minimumWeeks; this.meetingDaysCountPerWeek = meetingDaysCountPerWeek; } @@ -207,10 +195,6 @@ public int sizeOfCurrentMembers() { .count(); } - public boolean isCurrentRoundEndAt(LocalDate date) { - return getCurrentRound().isEndAt(date); - } - public void start(Member member, List daysOfTheWeek, LocalDateTime startAt) { validateMaster(member); if (processingStatus != ProcessingStatus.RECRUITING) { @@ -224,7 +208,7 @@ public void start(Member member, List daysOfTheWeek, LocalDateTime st this.processingStatus = ProcessingStatus.PROCESSING; this.meetingDaysCountPerWeek = daysOfTheWeek.size(); initializeMeetingDaysOfTheWeek(daysOfTheWeek); - initializeRounds(startAt.toLocalDate()); + registerEvent(new StudyStartedEvent(id, dayOfWeeks, startAt.toLocalDate())); } private void deleteLeftApplicant() { @@ -234,30 +218,8 @@ private void deleteLeftApplicant() { studyMembers.removeAll(currentApplicant); } - private void initializeMeetingDaysOfTheWeek(List daysOfTheWeek) { - this.meetingDaysOfTheWeek.addAll(daysOfTheWeek.stream() - .map(dayOfTheWeek -> MeetingDayOfTheWeek.builder() - .dayOfWeek(dayOfTheWeek) - .study(this) - .build()) - .toList()); - } - - private void initializeRounds(LocalDate startAt) { - this.rounds.addAll(createRoundsOfFirstWeek(startAt)); - this.rounds.addAll(createRoundsOf(FIRST_WEEK + 1)); - rounds.get(0).proceed(); - } - - private List createRoundsOfFirstWeek(final LocalDate startAt) { - List rounds = meetingDaysOfTheWeek.stream() - .filter(meetingDayOfTheWeek -> meetingDayOfTheWeek.comesNext(startAt.getDayOfWeek())) - .map(meetingDayOfTheWeek -> Round.of(meetingDayOfTheWeek, this, FIRST_WEEK)) - .toList(); - if (rounds.isEmpty()) { - rounds = createRoundsOf(FIRST_WEEK); - } - return rounds; + private void initializeMeetingDaysOfTheWeek(List dayOfWeeks) { + this.dayOfWeeks.addAll(dayOfWeeks); } public void validateMaster(Member candidate) { @@ -268,54 +230,24 @@ public void validateMaster(Member candidate) { throw new NotStudyMasterException(" 머스트두를 수정할 권한이 없습니다.", candidate.getNickname()); } - public void updateToNextRound() { - Round nextRound = findUpcomingRoundOf(getCurrentRound().getWeekNumber()); - - if (nextRound.isSameWeek(getCurrentRound().getWeekNumber() + 1)) { - rounds.addAll(createRoundsOf(nextRound.getWeekNumber() + 1)); - } - updateCurrentRound(nextRound); - } - - private void updateCurrentRound(Round upcomingRound) { - upcomingRound.proceed(); - getCurrentRound().finish(); - } - - private Round findUpcomingRoundOf(int weekNumber) { - return rounds.stream() - .filter(round -> round.isSameWeek(weekNumber) && round.isNextDayOfWeek(getCurrentRound().getDayOfWeek())) - .findFirst() - .orElseGet(() -> findFirstRoundOf(weekNumber + 1)); - } - - private Round findFirstRoundOf(int nextWeekNumber) { - return rounds.stream() - .filter(round -> round.isSameWeek(nextWeekNumber) && round.isSameDayOfWeek(findFirstMeetingDayOfTheWeek())) - .findAny() - .orElseThrow(() -> new RoundNotFoundException("다음 주차의 라운드가 존재하지 않습니다.", getCurrentRound().getWeekNumber())); - } - - private MeetingDayOfTheWeek findFirstMeetingDayOfTheWeek() { - return meetingDaysOfTheWeek.stream() - .min(Comparator.comparing(MeetingDayOfTheWeek::getOrder)) - .orElseThrow(); - } - public void finishStudy(Member master) { validateMaster(master); validateStudyCanFinish(); + registerEvent(new StudyFinishedEvent(id, calculateRoundExperience(), minimumWeeks)); studyMembers.forEach(StudyMember::completeSuccessfully); this.processingStatus = ProcessingStatus.END; } + public Integer calculateRoundExperience() { + int defaultRoundExperience = EXPERIENCE_BASE_UNIT * 2; + int additionalExperienceOfPeriodLength = EXPERIENCE_BASE_UNIT * 3 / meetingDaysCountPerWeek + 1; + return defaultRoundExperience + additionalExperienceOfPeriodLength; + } + private void validateStudyCanFinish() { if (isEnd()) { throw new CannotEndException("이미 종료된 스터디입니다.", String.valueOf(id)); } - if (getCurrentRound().isBeforeOrSame(minimumWeeks)) { - throw new CannotEndException("최소 진행 주차를 채우지 못했습니다.", String.valueOf(id)); - } } public Member getMaster() { @@ -341,29 +273,8 @@ public boolean isEnd() { return this.processingStatus == ProcessingStatus.END; } - public void completeRound(Member member) { - getCurrentRound().completeRound(member); - } - - public List getCurrentRoundOfMembers() { - return getCurrentRound().getRoundOfMembers(); - } - - public int calculateSuccessfulRoundCount(Member member) { - return (int) rounds.stream() - .filter(round -> round.isSuccess(member)) - .count(); - } - - public Round getCurrentRound() { - return rounds.stream() - .filter(Round::isInProgress) - .findAny() - .orElseThrow(() -> new RoundNotFoundException("현재 진행중인 라운드가 없습니다.", -1)); - } - public boolean isMaster(Member member) { - return getCurrentRound().isMaster(member); + return getMaster().getId().equals(member.getId()); } public void updateInformation( @@ -385,12 +296,6 @@ public void updateInformation( this.meetingDaysCountPerWeek = meetingDaysCountPerWeek; } - private List createRoundsOf(Integer weekNumber) { - return meetingDaysOfTheWeek.stream() - .map(meetingDayOfTheWeek -> Round.of(meetingDayOfTheWeek, this, weekNumber)) - .toList(); - } - public void apply(Member member) { validateMemberSize(); validateApplicant(member); @@ -414,8 +319,8 @@ private boolean isAlreadyExist(Member member) { } public void exit(Member member) { - rounds.forEach(round -> round.exit(member)); findStudyMemberBy(member).failStudy(); + registerEvent(new StudyExitedEvent(id, member.getId())); } private StudyMember findStudyMemberBy(Member member) { @@ -424,11 +329,4 @@ private StudyMember findStudyMemberBy(Member member) { .findAny() .orElseThrow(() -> new NotStudyMemberException("해당 스터디의 멤버가 아닙니다.", member.getGithubId())); } - - public StudyMember getStudyMemberByMember(Member member) { - return studyMembers.stream() - .filter(studyMember -> studyMember.getMember().equals(member)) - .findAny() - .orElseThrow(() -> new NotStudyMemberException("해당 스터디의 멤버가 아닙니다.", member.getGithubId())); - } } diff --git a/backend/src/main/java/com/yigongil/backend/ui/StudyController.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyController.java similarity index 67% rename from backend/src/main/java/com/yigongil/backend/ui/StudyController.java rename to backend/src/main/java/com/yigongil/backend/domain/study/StudyController.java index 6d8eec19a..604ce2db1 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/StudyController.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyController.java @@ -1,19 +1,11 @@ -package com.yigongil.backend.ui; +package com.yigongil.backend.domain.study; -import com.yigongil.backend.application.StudyService; import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.study.ProcessingStatus; -import com.yigongil.backend.domain.studymember.Role; -import com.yigongil.backend.request.CertificationCreateRequest; -import com.yigongil.backend.request.FeedPostCreateRequest; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.study.studymember.Role; import com.yigongil.backend.request.StudyStartRequest; import com.yigongil.backend.request.StudyUpdateRequest; -import com.yigongil.backend.response.CertificationResponse; -import com.yigongil.backend.response.FeedPostResponse; -import com.yigongil.backend.response.MembersCertificationResponse; import com.yigongil.backend.response.MyStudyResponse; -import com.yigongil.backend.response.RoundResponse; import com.yigongil.backend.response.StudyDetailResponse; import com.yigongil.backend.response.StudyListItemResponse; import com.yigongil.backend.response.StudyMemberResponse; @@ -21,7 +13,6 @@ import com.yigongil.backend.ui.doc.StudyApi; import java.net.URI; import java.util.List; -import java.util.Optional; import javax.validation.Valid; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -134,56 +125,6 @@ public ResponseEntity startStudy( return ResponseEntity.ok().build(); } - @GetMapping("/{id}/feeds") - public ResponseEntity> findFeedPosts( - @PathVariable Long id, - @RequestParam Optional oldestFeedPostId - ) { - List response = studyService.findFeedPosts( - id, - oldestFeedPostId.orElse(Long.MAX_VALUE) - ); - return ResponseEntity.ok(response); - } - - @PostMapping("/{id}/feeds") - public ResponseEntity createFeedPost( - @Authorization Member member, - @PathVariable Long id, - @RequestBody FeedPostCreateRequest request - ) { - studyService.createFeedPost(member, id, request); - return ResponseEntity.ok().build(); - } - - @PostMapping("/{id}/certifications") - public ResponseEntity createCertification( - @Authorization Member member, - @PathVariable Long id, - @RequestBody CertificationCreateRequest request - ) { - Long certificationId = studyService.createCertification(member, id, request); - return ResponseEntity.created(URI.create("/studies/" + id + "/certifications/" + certificationId)).build(); - } - - @GetMapping("/{id}/certifications") - public ResponseEntity findAllMembersCertification( - @Authorization Member member, - @PathVariable Long id - ) { - MembersCertificationResponse response = studyService.findAllMembersCertification(member, id); - return ResponseEntity.ok(response); - } - - @GetMapping("/{id}/rounds/{roundId}/members/{memberId}") - public ResponseEntity findMemberCertification( - @PathVariable Long roundId, - @PathVariable Long memberId - ) { - CertificationResponse response = studyService.findCertification(roundId, memberId); - return ResponseEntity.ok(response); - } - @GetMapping("/{studyId}/members/role") public ResponseEntity getStudyMemberRole( @Authorization Member member, @@ -213,15 +154,6 @@ public ResponseEntity endStudy( return ResponseEntity.ok().build(); } - @GetMapping("/{studyId}/rounds") - public ResponseEntity> findRoundDetailsOfWeek( - @PathVariable Long studyId, - @RequestParam Integer weekNumber - ) { - List roundResponses = studyService.findRoundDetailsOfWeek(studyId, weekNumber); - return ResponseEntity.ok(roundResponses); - } - @DeleteMapping("/{studyId}/exit") public ResponseEntity exitStudy( @Authorization Member member, @@ -231,4 +163,3 @@ public ResponseEntity exitStudy( return ResponseEntity.ok().build(); } } - diff --git a/backend/src/main/java/com/yigongil/backend/application/StudyEventListener.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyEventListener.java similarity index 93% rename from backend/src/main/java/com/yigongil/backend/application/StudyEventListener.java rename to backend/src/main/java/com/yigongil/backend/domain/study/StudyEventListener.java index ae1b68d77..55e1bc774 100644 --- a/backend/src/main/java/com/yigongil/backend/application/StudyEventListener.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyEventListener.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.application; +package com.yigongil.backend.domain.study; import com.yigongil.backend.domain.event.MemberDeleteEvent; import org.springframework.stereotype.Component; diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/StudyExitedEvent.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyExitedEvent.java new file mode 100644 index 000000000..1e4f2d8c2 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyExitedEvent.java @@ -0,0 +1,5 @@ +package com.yigongil.backend.domain.study; + +public record StudyExitedEvent(Long studyId, Long memberId) { + +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/StudyFinishedEvent.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyFinishedEvent.java new file mode 100644 index 000000000..383795b73 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyFinishedEvent.java @@ -0,0 +1,5 @@ +package com.yigongil.backend.domain.study; + +public record StudyFinishedEvent(Long studyId, Integer roundExperience, Integer minimumWeeks) { + +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/StudyRepository.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyRepository.java index ad202dd6f..bbcb1a99a 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/study/StudyRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyRepository.java @@ -1,11 +1,11 @@ package com.yigongil.backend.domain.study; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.study.studymember.Role; import com.yigongil.backend.domain.study.studyquery.StudyQueryRepository; -import com.yigongil.backend.domain.studymember.Role; +import com.yigongil.backend.exception.StudyNotFoundException; import java.util.List; import java.util.Optional; -import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; @@ -15,16 +15,12 @@ public interface StudyRepository extends Repository, StudyQueryRepo Study save(Study study); - @EntityGraph(attributePaths = {"rounds"}) Optional findById(Long studyId); - List findAllByProcessingStatus(ProcessingStatus processingStatus); - @Query(""" select distinct s from Study s join StudyMember sm on s = sm.study - join fetch s.rounds where sm.member = :member and s.processingStatus = :processingStatus """) @@ -49,4 +45,8 @@ List findAllByMasterIdAndProcessingStatus( where s in :studies """) void deleteAll(@Param("studies") Iterable studies); + + default Study getById(Long studyId) { + return findById(studyId).orElseThrow(() -> new StudyNotFoundException("해당 스터디를 찾을 수 없습니다", studyId)); + } } diff --git a/backend/src/main/java/com/yigongil/backend/application/StudyService.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyService.java similarity index 70% rename from backend/src/main/java/com/yigongil/backend/application/StudyService.java rename to backend/src/main/java/com/yigongil/backend/domain/study/StudyService.java index 0abb26941..1bba147d2 100644 --- a/backend/src/main/java/com/yigongil/backend/application/StudyService.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyService.java @@ -1,35 +1,20 @@ -package com.yigongil.backend.application; - -import com.yigongil.backend.domain.certification.Certification; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.round.RoundRepository; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; -import com.yigongil.backend.domain.study.PageStrategy; -import com.yigongil.backend.domain.study.ProcessingStatus; -import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.domain.study.StudyRepository; -import com.yigongil.backend.domain.studymember.Role; -import com.yigongil.backend.domain.studymember.StudyMember; -import com.yigongil.backend.domain.studymember.StudyMemberRepository; -import com.yigongil.backend.domain.studymember.StudyResult; +package com.yigongil.backend.domain.study; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.study.studymember.Role; +import com.yigongil.backend.domain.study.studymember.StudyMember; +import com.yigongil.backend.domain.study.studymember.StudyMemberRepository; +import com.yigongil.backend.domain.study.studymember.StudyResult; import com.yigongil.backend.exception.ApplicantNotFoundException; import com.yigongil.backend.exception.StudyNotFoundException; -import com.yigongil.backend.request.CertificationCreateRequest; -import com.yigongil.backend.request.FeedPostCreateRequest; import com.yigongil.backend.request.StudyStartRequest; import com.yigongil.backend.request.StudyUpdateRequest; -import com.yigongil.backend.response.CertificationResponse; -import com.yigongil.backend.response.FeedPostResponse; -import com.yigongil.backend.response.MembersCertificationResponse; import com.yigongil.backend.response.MyStudyResponse; -import com.yigongil.backend.response.RoundResponse; import com.yigongil.backend.response.StudyDetailResponse; import com.yigongil.backend.response.StudyListItemResponse; import com.yigongil.backend.response.StudyMemberResponse; import com.yigongil.backend.response.StudyMemberRoleResponse; import java.time.DayOfWeek; -import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @@ -43,21 +28,13 @@ public class StudyService { private final StudyRepository studyRepository; private final StudyMemberRepository studyMemberRepository; - private final CertificationService certificationService; - private final FeedService feedService; - private final RoundRepository roundRepository; public StudyService( StudyRepository studyRepository, - StudyMemberRepository studyMemberRepository, - CertificationService certificationService, - FeedService feedService, - final RoundRepository roundRepository) { + StudyMemberRepository studyMemberRepository + ) { this.studyRepository = studyRepository; this.studyMemberRepository = studyMemberRepository; - this.certificationService = certificationService; - this.feedService = feedService; - this.roundRepository = roundRepository; } @Transactional @@ -130,18 +107,6 @@ public void apply(Member member, Long studyId) { study.apply(member); } - @Transactional - public void createFeedPost(Member member, Long studyId, FeedPostCreateRequest request) { - final Study study = findStudyById(studyId); - feedService.createFeedPost(member, study, request); - } - - @Transactional - public Long createCertification(Member member, Long id, CertificationCreateRequest request) { - Study study = findStudyById(id); - return certificationService.createCertification(study, member, request).getId(); - } - public Study findStudyById(Long studyId) { return studyRepository.findById(studyId) .orElseThrow(() -> new StudyNotFoundException("해당 스터디를 찾을 수 없습니다", studyId)); @@ -218,21 +183,13 @@ private StudyMember findApplicantByMemberIdAndStudyId(Long memberId, Long studyI return studyMember; } - @Transactional - public void proceedRound(LocalDate today) { - List studies = studyRepository.findAllByProcessingStatus(ProcessingStatus.PROCESSING); - - studies.stream() - .filter(study -> study.isCurrentRoundEndAt(today.minusDays(1))) - .forEach(Study::updateToNextRound); - } - @Transactional public void start(Member member, Long studyId, StudyStartRequest request) { List meetingDaysOfTheWeek = createDayOfWeek(request.meetingDaysOfTheWeek()); Study study = findStudyById(studyId); study.start(member, meetingDaysOfTheWeek, LocalDateTime.now()); + studyRepository.save(study); } private List createDayOfWeek(List daysOfTheWeek) { @@ -269,27 +226,6 @@ public StudyMemberRoleResponse getMemberRoleOfStudy(Member member, Long studyId) return StudyMemberRoleResponse.from(role); } - @Transactional(readOnly = true) - public MembersCertificationResponse findAllMembersCertification(Member member, Long studyId) { - Study study = findStudyById(studyId); - final List roundOfMembers = study.getCurrentRoundOfMembers(); - return MembersCertificationResponse.of(study.getName(), study.getCurrentRound(), member, roundOfMembers); - } - - @Transactional(readOnly = true) - public CertificationResponse findCertification(Long roundId, Long memberId) { - Certification certification = certificationService.findByRoundIdAndMemberId(roundId, memberId); - return CertificationResponse.from(certification); - } - - @Transactional(readOnly = true) - public List findFeedPosts(Long id, Long oldestFeedPostId) { - return feedService.findFeedPosts(id, oldestFeedPostId) - .stream() - .map(FeedPostResponse::from) - .toList(); - } - private List toRecruitingStudyResponse(Slice studies) { return studies.get() .map(StudyListItemResponse::from) @@ -300,19 +236,13 @@ private List toRecruitingStudyResponse(Slice studi public void finish(Member member, Long studyId) { Study study = findStudyById(studyId); study.finishStudy(member); - } - - @Transactional(readOnly = true) - public List findRoundDetailsOfWeek(Long id, Integer weekNumber) { - List roundsOfWeek = roundRepository.findAllByStudyIdAndWeekNumber(id, weekNumber); - return roundsOfWeek.stream() - .map(RoundResponse::from) - .toList(); + studyRepository.save(study); } @Transactional public void exit(Member member, Long studyId) { Study study = findStudyById(studyId); study.exit(member); + studyRepository.save(study); } } diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/StudyStartedEvent.java b/backend/src/main/java/com/yigongil/backend/domain/study/StudyStartedEvent.java new file mode 100644 index 000000000..d4c4272a7 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/domain/study/StudyStartedEvent.java @@ -0,0 +1,13 @@ +package com.yigongil.backend.domain.study; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.util.List; + +public record StudyStartedEvent( + Long studyId, + List dayOfWeeks, + LocalDate startAt +) { + +} diff --git a/backend/src/main/java/com/yigongil/backend/domain/studymember/Role.java b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/Role.java similarity index 90% rename from backend/src/main/java/com/yigongil/backend/domain/studymember/Role.java rename to backend/src/main/java/com/yigongil/backend/domain/study/studymember/Role.java index 33051f4df..b57cc2e1c 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/studymember/Role.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/Role.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.studymember; +package com.yigongil.backend.domain.study.studymember; import java.util.Arrays; import lombok.Getter; diff --git a/backend/src/main/java/com/yigongil/backend/domain/studymember/RoleConverter.java b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/RoleConverter.java similarity index 80% rename from backend/src/main/java/com/yigongil/backend/domain/studymember/RoleConverter.java rename to backend/src/main/java/com/yigongil/backend/domain/study/studymember/RoleConverter.java index 19f9901ab..d27a1ee40 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/studymember/RoleConverter.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/RoleConverter.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.studymember; +package com.yigongil.backend.domain.study.studymember; import org.springframework.core.convert.converter.Converter; diff --git a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMember.java b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMember.java similarity index 78% rename from backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMember.java rename to backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMember.java index c4168869e..1e3faae93 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMember.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMember.java @@ -1,7 +1,7 @@ -package com.yigongil.backend.domain.studymember; +package com.yigongil.backend.domain.study.studymember; -import com.yigongil.backend.domain.BaseEntity; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.base.BaseEntity; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.Study; import java.util.Objects; import javax.persistence.Column; @@ -22,8 +22,6 @@ @Entity public class StudyMember extends BaseEntity { - private static final int EXPERIENCE_BASE_UNIT = 1; - @GeneratedValue(strategy = GenerationType.IDENTITY) @Id private Long id; @@ -86,18 +84,9 @@ public boolean isMaster() { } public void completeSuccessfully() { - int totalExperience = calculateAccumulatedExperience(); - member.addExperience(totalExperience); this.studyResult = StudyResult.SUCCESS; } - public int calculateAccumulatedExperience() { - int successfulRoundCount = study.calculateSuccessfulRoundCount(member); - int defaultRoundExperience = EXPERIENCE_BASE_UNIT * 2; - int additionalExperienceOfPeriodLength = EXPERIENCE_BASE_UNIT * 3 / study.getMeetingDaysCountPerWeek() + 1; - return successfulRoundCount * (defaultRoundExperience + additionalExperienceOfPeriodLength); - } - public void failStudy() { this.studyResult = StudyResult.FAIL; } diff --git a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMemberRepository.java b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMemberRepository.java similarity index 73% rename from backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMemberRepository.java rename to backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMemberRepository.java index 89a6bc83c..8a7e9d847 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyMemberRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyMemberRepository.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.studymember; +package com.yigongil.backend.domain.study.studymember; import java.util.List; import java.util.Optional; @@ -15,26 +15,16 @@ public interface StudyMemberRepository extends Repository { @EntityGraph(attributePaths = "member") List findAllByStudyIdAndRole(Long studyId, Role role); - @EntityGraph(attributePaths = "member") - List findAllByStudyId(Long studyId); - @EntityGraph(attributePaths = "study") List findAllByMemberId(Long memberId); - @EntityGraph(attributePaths = "member") - List findAllByStudyIdAndRoleNot(Long studyId, Role role); - @EntityGraph(attributePaths = "member") List findAllByStudyIdAndRoleNotAndStudyResult(Long studyId, Role role, StudyResult studyResult); @EntityGraph(attributePaths = "study") List findAllByMemberIdAndRoleNotAndStudyResult(Long memberId, Role role, StudyResult studyResult); - boolean existsByStudyIdAndMemberId(Long studyId, Long memberId); - void delete(StudyMember studyMember); Long countByMemberIdAndStudyResult(Long memberId, StudyResult studyResult); - - void deleteAllByStudyIdAndRole(Long studyId, Role role); } diff --git a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyResult.java b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyResult.java similarity index 53% rename from backend/src/main/java/com/yigongil/backend/domain/studymember/StudyResult.java rename to backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyResult.java index db6ae8f3d..13991b243 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/studymember/StudyResult.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studymember/StudyResult.java @@ -1,4 +1,4 @@ -package com.yigongil.backend.domain.studymember; +package com.yigongil.backend.domain.study.studymember; public enum StudyResult { diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepository.java b/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepository.java index 990c7bcf6..763df1843 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepository.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepository.java @@ -2,7 +2,7 @@ import com.yigongil.backend.domain.study.ProcessingStatus; import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.domain.studymember.Role; +import com.yigongil.backend.domain.study.studymember.Role; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; diff --git a/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImpl.java b/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImpl.java index 57e0ffa50..977eef0fb 100644 --- a/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImpl.java +++ b/backend/src/main/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImpl.java @@ -1,7 +1,8 @@ package com.yigongil.backend.domain.study.studyquery; + import static com.yigongil.backend.domain.study.QStudy.study; -import static com.yigongil.backend.domain.studymember.QStudyMember.studyMember; +import static com.yigongil.backend.domain.study.studymember.QStudyMember.studyMember; import com.querydsl.core.types.OrderSpecifier; import com.querydsl.core.types.Predicate; @@ -10,7 +11,7 @@ import com.querydsl.jpa.impl.JPAQueryFactory; import com.yigongil.backend.domain.study.ProcessingStatus; import com.yigongil.backend.domain.study.Study; -import com.yigongil.backend.domain.studymember.Role; +import com.yigongil.backend.domain.study.studymember.Role; import java.util.ArrayList; import java.util.List; import org.springframework.data.domain.Pageable; diff --git a/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryApi.java b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryApi.java new file mode 100644 index 000000000..a6e85d42d --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryApi.java @@ -0,0 +1,44 @@ +package com.yigongil.backend.query.certification; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.response.CertificationResponse; +import com.yigongil.backend.response.MembersCertificationResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "인증 조회", description = "인증 조회 관련 api") +public interface CertificationQueryApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "스터디 멤버 전체 인증 정보 조회") + ResponseEntity findAllMembersCertification( + @Schema(hidden = true) Member member, + @Parameter(description = "조회하려는 스터디 studyId", required = true) Long id + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "스터디 멤버 단일 인증 게시글 조회") + ResponseEntity findMemberCertification( + @Parameter(description = "인증 게시 회차 studyId") Long roundId, + @Parameter(description = "작성자 studyId") Long memberId + ); +} diff --git a/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryController.java b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryController.java new file mode 100644 index 000000000..4e1be6e44 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryController.java @@ -0,0 +1,38 @@ +package com.yigongil.backend.query.certification; + +import com.yigongil.backend.config.auth.Authorization; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.response.CertificationResponse; +import com.yigongil.backend.response.MembersCertificationResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class CertificationQueryController implements CertificationQueryApi { + + private final CertificationQueryService certificationQueryService; + + public CertificationQueryController(CertificationQueryService certificationQueryService) { + this.certificationQueryService = certificationQueryService; + } + + @GetMapping("/studies/{id}/rounds/{roundId}/members/{memberId}") + public ResponseEntity findMemberCertification( + @PathVariable Long roundId, + @PathVariable Long memberId + ) { + CertificationResponse response = certificationQueryService.findCertification(roundId, memberId); + return ResponseEntity.ok(response); + } + + @GetMapping("/studies/{id}/certifications") + public ResponseEntity findAllMembersCertification( + @Authorization Member member, + @PathVariable Long id + ) { + MembersCertificationResponse response = certificationQueryService.findAllMembersCertification(member, id); + return ResponseEntity.ok(response); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryService.java b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryService.java new file mode 100644 index 000000000..b06f6c4e1 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/certification/CertificationQueryService.java @@ -0,0 +1,48 @@ +package com.yigongil.backend.query.certification; + +import com.yigongil.backend.domain.certification.Certification; +import com.yigongil.backend.domain.certification.CertificationRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.round.Round; +import com.yigongil.backend.domain.round.RoundOfMember; +import com.yigongil.backend.domain.round.RoundRepository; +import com.yigongil.backend.domain.round.RoundStatus; +import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyRepository; +import com.yigongil.backend.response.CertificationResponse; +import com.yigongil.backend.response.MembersCertificationResponse; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class CertificationQueryService { + + private final CertificationRepository certificationRepository; + private final StudyRepository studyRepository; + private final RoundRepository roundRepository; + + + public CertificationQueryService( + CertificationRepository certificationRepository, + StudyRepository studyRepository, + RoundRepository roundRepository + ) { + this.certificationRepository = certificationRepository; + this.studyRepository = studyRepository; + this.roundRepository = roundRepository; + } + + public CertificationResponse findCertification(Long roundId, Long memberId) { + Certification certification = certificationRepository.getByRoundIdAndAuthorId(roundId, memberId); + return CertificationResponse.from(certification); + } + + public MembersCertificationResponse findAllMembersCertification(Member member, Long studyId) { + Study study = studyRepository.getById(studyId); + Round round = roundRepository.getByStudyIdAndRoundStatus(studyId, RoundStatus.IN_PROGRESS); + final List roundOfMembers = round.getRoundOfMembers(); + return MembersCertificationResponse.of(study.getName(), round, member, roundOfMembers); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryApi.java b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryApi.java new file mode 100644 index 000000000..2c7c48b73 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryApi.java @@ -0,0 +1,32 @@ +package com.yigongil.backend.query.feed; + +import com.yigongil.backend.response.FeedPostResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.Optional; +import org.springframework.http.ResponseEntity; + +@Tag(name = "피드", description = "피드 관련 api") +public interface FeedQueryApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "400", content = @Content), + @ApiResponse(responseCode = "401", content = @Content), + @ApiResponse(responseCode = "404", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "피드 조회") + ResponseEntity> findFeedPosts( + @Parameter(description = "조회할 스터디 studyId", required = true) Long id, + @Parameter(description = "마지막으로 본 피드의 아이디, 첫 요청에서는 필요 없음", allowEmptyValue = true) Optional oldestFeedPostId + ); +} diff --git a/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryController.java b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryController.java new file mode 100644 index 000000000..fdea3fc98 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryController.java @@ -0,0 +1,32 @@ +package com.yigongil.backend.query.feed; + +import com.yigongil.backend.response.FeedPostResponse; +import java.util.List; +import java.util.Optional; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class FeedQueryController implements FeedQueryApi { + + private final FeedQueryService feedQueryService; + + public FeedQueryController(FeedQueryService feedQueryService) { + this.feedQueryService = feedQueryService; + } + + @GetMapping("/studies/{id}/feeds") + public ResponseEntity> findFeedPosts( + @PathVariable Long id, + @RequestParam Optional oldestFeedPostId + ) { + List response = feedQueryService.findFeedPosts( + id, + oldestFeedPostId.orElse(Long.MAX_VALUE) + ); + return ResponseEntity.ok(response); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryService.java b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryService.java new file mode 100644 index 000000000..88bedaa6d --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/feed/FeedQueryService.java @@ -0,0 +1,26 @@ +package com.yigongil.backend.query.feed; + +import com.yigongil.backend.domain.feedpost.FeedPostRepository; +import com.yigongil.backend.response.FeedPostResponse; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class FeedQueryService { + + private final FeedPostRepository feedPostRepository; + + public FeedQueryService(FeedPostRepository feedPostRepository) { + this.feedPostRepository = feedPostRepository; + } + + @Transactional(readOnly = true) + public List findFeedPosts(Long studyId, Long oldestFeedPostId) { + return feedPostRepository.findAllByStudyIdStartWithOldestFeedPostId(studyId, oldestFeedPostId) + .stream() + .map(FeedPostResponse::from) + .toList(); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/query/profile/ProfileApi.java b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileApi.java new file mode 100644 index 000000000..c86ce1ad1 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileApi.java @@ -0,0 +1,39 @@ +package com.yigongil.backend.query.profile; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.response.MyProfileResponse; +import com.yigongil.backend.response.ProfileResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; + +@Tag(name = "프로필 조회", description = "프로필 조회 api") +public interface ProfileApi { + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "404", content = @Content) + } + ) + @Operation(summary = "프로필을 조회") + ResponseEntity findProfile( + @Parameter(description = "조회할 회원의 식별자", required = true) Long id + ); + + @ApiResponses( + value = { + @ApiResponse(responseCode = "200"), + @ApiResponse(responseCode = "401", content = @Content) + } + ) + @SecurityRequirement(name = "token") + @Operation(summary = "내 프로필을 조회") + ResponseEntity findMyProfile(@Schema(hidden = true) Member member); +} diff --git a/backend/src/main/java/com/yigongil/backend/query/profile/ProfileController.java b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileController.java new file mode 100644 index 000000000..680c928ec --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileController.java @@ -0,0 +1,46 @@ +package com.yigongil.backend.query.profile; + +import com.yigongil.backend.config.auth.Authorization; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.response.MyProfileResponse; +import com.yigongil.backend.response.ProfileResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/members") +@RestController +public class ProfileController implements ProfileApi { + + private final ProfileService profileService; + + public ProfileController(ProfileService profileService) { + this.profileService = profileService; + } + + @GetMapping(path = "/{id}") + public ResponseEntity findProfile(@PathVariable Long id) { + ProfileResponse response = profileService.findById(id); + return ResponseEntity.ok(response); + } + + @GetMapping(path = "/my") + public ResponseEntity findMyProfile(@Authorization Member member) { + ProfileResponse profile = profileService.findById(member.getId()); + MyProfileResponse response = new MyProfileResponse( + profile.id(), + profile.nickname(), + profile.githubId(), + profile.profileImageUrl(), + profile.successRate(), + profile.successfulRoundCount(), + profile.tierProgress(), + profile.tier(), + profile.introduction() + ); + + return ResponseEntity.ok(response); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/query/profile/ProfileService.java b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileService.java new file mode 100644 index 000000000..8e941e023 --- /dev/null +++ b/backend/src/main/java/com/yigongil/backend/query/profile/ProfileService.java @@ -0,0 +1,81 @@ +package com.yigongil.backend.query.profile; + +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.round.RoundRepository; +import com.yigongil.backend.domain.study.Study; +import com.yigongil.backend.domain.study.StudyService; +import com.yigongil.backend.domain.study.studymember.StudyMember; +import com.yigongil.backend.domain.study.studymember.StudyMemberRepository; +import com.yigongil.backend.response.FinishedStudyResponse; +import com.yigongil.backend.response.ProfileResponse; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class ProfileService { + + private final StudyMemberRepository studyMemberRepository; + private final StudyService studyService; + private final MemberRepository memberRepository; + private final RoundRepository roundRepository; + + + public ProfileService(StudyMemberRepository studyMemberRepository, StudyService studyService, + MemberRepository memberRepository, RoundRepository roundRepository) { + this.studyMemberRepository = studyMemberRepository; + this.studyService = studyService; + this.memberRepository = memberRepository; + this.roundRepository = roundRepository; + } + + public ProfileResponse findById(Long id) { + Member member = memberRepository.getById(id); + + List finishedStudyResponses = studyMemberRepository.findAllByMemberId(id) + .stream() + .filter(StudyMember::isStudyEnd) + .map(this::createFinishedStudyResponse) + .toList(); + + return new ProfileResponse( + member.getId(), + member.getNickname(), + member.getGithubId(), + member.getProfileImageUrl(), + studyService.calculateSuccessRate(member), + calculateNumberOfSuccessRounds(member), + member.calculateProgress(), + member.getTier().getOrder(), + member.getIntroduction(), + finishedStudyResponses + ); + } + + private FinishedStudyResponse createFinishedStudyResponse(StudyMember studyMember) { + Study study = studyMember.getStudy(); + return new FinishedStudyResponse( + study.getId(), + study.getName(), + study.calculateAverageTier(), + study.sizeOfCurrentMembers(), + study.getNumberOfMaximumMembers(), + studyMember.isSuccess() + ); + } + + private int calculateNumberOfSuccessRounds(Member member) { + List studies = studyMemberRepository.findAllByMemberId(member.getId()).stream() + .filter(StudyMember::isNotApplicant) + .map(StudyMember::getStudy) + .toList(); + + return (int) studies.stream() + .map(study -> roundRepository.findAllByStudyId(study.getId())) + .flatMap(List::stream) + .filter(round -> round.isMustDoDone(member)) + .count(); + } +} diff --git a/backend/src/main/java/com/yigongil/backend/request/MemberReportCreateRequest.java b/backend/src/main/java/com/yigongil/backend/request/MemberReportCreateRequest.java index 50c183cf7..8cfede9af 100644 --- a/backend/src/main/java/com/yigongil/backend/request/MemberReportCreateRequest.java +++ b/backend/src/main/java/com/yigongil/backend/request/MemberReportCreateRequest.java @@ -7,7 +7,7 @@ import javax.validation.constraints.Positive; public record MemberReportCreateRequest( - @Schema(description = "신고할 회원 id", example = "1") + @Schema(description = "신고할 회원 studyId", example = "1") @Positive(message = "피신고자 식별자는 양수만 입력 가능합니다.") Long reportedMemberId, diff --git a/backend/src/main/java/com/yigongil/backend/request/StudyReportCreateRequest.java b/backend/src/main/java/com/yigongil/backend/request/StudyReportCreateRequest.java index aa7485879..95fcea387 100644 --- a/backend/src/main/java/com/yigongil/backend/request/StudyReportCreateRequest.java +++ b/backend/src/main/java/com/yigongil/backend/request/StudyReportCreateRequest.java @@ -7,7 +7,7 @@ import javax.validation.constraints.Positive; public record StudyReportCreateRequest( - @Schema(description = "신고할 스터디 id", example = "1") + @Schema(description = "신고할 스터디 studyId", example = "1") @Positive(message = "신고할 스터디의 식별자는 양수만 입력 가능합니다.") Long reportedStudyId, diff --git a/backend/src/main/java/com/yigongil/backend/response/MemberCertificationResponse.java b/backend/src/main/java/com/yigongil/backend/response/MemberCertificationResponse.java index 4deb3edb2..d2b205503 100644 --- a/backend/src/main/java/com/yigongil/backend/response/MemberCertificationResponse.java +++ b/backend/src/main/java/com/yigongil/backend/response/MemberCertificationResponse.java @@ -1,6 +1,6 @@ package com.yigongil.backend.response; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.round.RoundOfMember; import java.util.List; public record MemberCertificationResponse( diff --git a/backend/src/main/java/com/yigongil/backend/response/MemberOfRoundResponse.java b/backend/src/main/java/com/yigongil/backend/response/MemberOfRoundResponse.java index 6d31358f8..5dcd9712a 100644 --- a/backend/src/main/java/com/yigongil/backend/response/MemberOfRoundResponse.java +++ b/backend/src/main/java/com/yigongil/backend/response/MemberOfRoundResponse.java @@ -1,7 +1,7 @@ package com.yigongil.backend.response; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.round.RoundOfMember; import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; diff --git a/backend/src/main/java/com/yigongil/backend/response/MembersCertificationResponse.java b/backend/src/main/java/com/yigongil/backend/response/MembersCertificationResponse.java index 0ce32a865..8ed9212f6 100644 --- a/backend/src/main/java/com/yigongil/backend/response/MembersCertificationResponse.java +++ b/backend/src/main/java/com/yigongil/backend/response/MembersCertificationResponse.java @@ -1,8 +1,8 @@ package com.yigongil.backend.response; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.round.RoundOfMember; import com.yigongil.backend.exception.NotStudyMemberException; import java.util.List; @@ -15,7 +15,7 @@ public record MembersCertificationResponse( public static MembersCertificationResponse of(String studyName, Round upcomingRound, Member requestingMember, List roundOfMembers) { List others = roundOfMembers.stream() - .filter(roundOfMember -> !roundOfMember.isMemberEquals(requestingMember)) + .filter(roundOfMember -> !roundOfMember.isMemberEquals(requestingMember.getId())) .map(roundOfMember -> new MemberCertificationResponse( roundOfMember.getMember().getId(), roundOfMember.getMember().getNickname(), @@ -23,7 +23,7 @@ public static MembersCertificationResponse of(String studyName, Round upcomingRo roundOfMember.isDone())) .toList(); MemberCertificationResponse me = roundOfMembers.stream() - .filter(roundOfMember -> roundOfMember.isMemberEquals(requestingMember)) + .filter(roundOfMember -> roundOfMember.isMemberEquals(requestingMember.getId())) .findAny() .map(roundOfMember -> new MemberCertificationResponse( roundOfMember.getMember().getId(), diff --git a/backend/src/main/java/com/yigongil/backend/response/StudyMemberRoleResponse.java b/backend/src/main/java/com/yigongil/backend/response/StudyMemberRoleResponse.java index fa2458dbc..28c63053b 100644 --- a/backend/src/main/java/com/yigongil/backend/response/StudyMemberRoleResponse.java +++ b/backend/src/main/java/com/yigongil/backend/response/StudyMemberRoleResponse.java @@ -1,6 +1,6 @@ package com.yigongil.backend.response; -import com.yigongil.backend.domain.studymember.Role; +import com.yigongil.backend.domain.study.studymember.Role; import io.swagger.v3.oas.annotations.media.Schema; public record StudyMemberRoleResponse( diff --git a/backend/src/main/java/com/yigongil/backend/ui/HomeController.java b/backend/src/main/java/com/yigongil/backend/ui/HomeController.java deleted file mode 100644 index 06bdc2722..000000000 --- a/backend/src/main/java/com/yigongil/backend/ui/HomeController.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.yigongil.backend.ui; - -import com.yigongil.backend.application.RoundService; -import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.response.UpcomingStudyResponse; -import com.yigongil.backend.ui.doc.HomeApi; -import java.util.List; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RequestMapping("/home") -@RestController -public class HomeController implements HomeApi { - - private final RoundService roundService; - - public HomeController(RoundService roundService) { - this.roundService = roundService; - } - - @GetMapping - public ResponseEntity> home(@Authorization Member member) { - List response = roundService.findCurrentRoundOfStudies(member); - return ResponseEntity.ok(response); - } -} diff --git a/backend/src/main/java/com/yigongil/backend/ui/LoginController.java b/backend/src/main/java/com/yigongil/backend/ui/LoginController.java index 9124a077c..b569b311c 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/LoginController.java +++ b/backend/src/main/java/com/yigongil/backend/ui/LoginController.java @@ -1,6 +1,6 @@ package com.yigongil.backend.ui; -import com.yigongil.backend.application.AuthService; +import com.yigongil.backend.config.auth.AuthService; import com.yigongil.backend.request.TokenRequest; import com.yigongil.backend.response.TokenResponse; import com.yigongil.backend.ui.doc.LoginApi; diff --git a/backend/src/main/java/com/yigongil/backend/ui/MustDoController.java b/backend/src/main/java/com/yigongil/backend/ui/MustDoController.java deleted file mode 100644 index 9f18013b1..000000000 --- a/backend/src/main/java/com/yigongil/backend/ui/MustDoController.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.yigongil.backend.ui; - -import com.yigongil.backend.application.MustDoService; -import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.request.MustDoUpdateRequest; -import com.yigongil.backend.ui.doc.MustDoApi; -import javax.validation.Valid; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RequestMapping("/rounds/{roundId}") -@RestController -public class MustDoController implements MustDoApi { - - private final MustDoService mustDoService; - - public MustDoController(MustDoService mustDoService) { - this.mustDoService = mustDoService; - } - - @PutMapping("/todos") - public ResponseEntity updateMustDo( - @Authorization Member member, - @PathVariable Long roundId, - @RequestBody @Valid MustDoUpdateRequest request - ) { - mustDoService.updateMustDo(member, roundId, request); - return ResponseEntity.ok().build(); - } -} diff --git a/backend/src/main/java/com/yigongil/backend/ui/doc/HomeApi.java b/backend/src/main/java/com/yigongil/backend/ui/doc/HomeApi.java index c4ff6a5c4..25f235f03 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/doc/HomeApi.java +++ b/backend/src/main/java/com/yigongil/backend/ui/doc/HomeApi.java @@ -1,6 +1,6 @@ package com.yigongil.backend.ui.doc; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.response.UpcomingStudyResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; diff --git a/backend/src/main/java/com/yigongil/backend/ui/doc/MemberApi.java b/backend/src/main/java/com/yigongil/backend/ui/doc/MemberApi.java index 762d3a51a..5addbf14d 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/doc/MemberApi.java +++ b/backend/src/main/java/com/yigongil/backend/ui/doc/MemberApi.java @@ -1,11 +1,9 @@ package com.yigongil.backend.ui.doc; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.request.ProfileUpdateRequest; -import com.yigongil.backend.response.MyProfileResponse; import com.yigongil.backend.response.NicknameValidationResponse; import com.yigongil.backend.response.OnboardingCheckResponse; -import com.yigongil.backend.response.ProfileResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -19,27 +17,6 @@ @Tag(name = "회원", description = "회원 관련 api") public interface MemberApi { - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "404", content = @Content) - } - ) - @Operation(summary = "프로필을 조회") - ResponseEntity findProfile( - @Parameter(description = "조회할 회원의 식별자", required = true) Long id - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "내 프로필을 조회") - ResponseEntity findMyProfile(@Schema(hidden = true) Member member); - @ApiResponses( value = { @ApiResponse(responseCode = "200"), diff --git a/backend/src/main/java/com/yigongil/backend/ui/doc/MustDoApi.java b/backend/src/main/java/com/yigongil/backend/ui/doc/MustDoApi.java deleted file mode 100644 index 7c824cea3..000000000 --- a/backend/src/main/java/com/yigongil/backend/ui/doc/MustDoApi.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.yigongil.backend.ui.doc; - -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.request.MustDoUpdateRequest; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; -import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.http.ResponseEntity; - -@Tag(name = "머스트두", description = "머스트두 관련 api") -public interface MustDoApi { - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "400"), - @ApiResponse(responseCode = "401"), - @ApiResponse(responseCode = "404") - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = " 머스트두 생성") - ResponseEntity updateMustDo( - @Schema(hidden = true) Member member, - @Parameter(description = " 머스트두를 생성할 라운드 id", required = true) Long roundId, - MustDoUpdateRequest request - ); -} diff --git a/backend/src/main/java/com/yigongil/backend/ui/doc/ReportApi.java b/backend/src/main/java/com/yigongil/backend/ui/doc/ReportApi.java index a64709007..ddbec5b28 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/doc/ReportApi.java +++ b/backend/src/main/java/com/yigongil/backend/ui/doc/ReportApi.java @@ -1,7 +1,7 @@ package com.yigongil.backend.ui.doc; import com.yigongil.backend.config.auth.Authorization; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.request.MemberReportCreateRequest; import com.yigongil.backend.request.StudyReportCreateRequest; import io.swagger.v3.oas.annotations.Operation; diff --git a/backend/src/main/java/com/yigongil/backend/ui/doc/StudyApi.java b/backend/src/main/java/com/yigongil/backend/ui/doc/StudyApi.java index e7d7f14e2..53c61ff1d 100644 --- a/backend/src/main/java/com/yigongil/backend/ui/doc/StudyApi.java +++ b/backend/src/main/java/com/yigongil/backend/ui/doc/StudyApi.java @@ -1,16 +1,10 @@ package com.yigongil.backend.ui.doc; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.ProcessingStatus; -import com.yigongil.backend.request.CertificationCreateRequest; -import com.yigongil.backend.request.FeedPostCreateRequest; import com.yigongil.backend.request.StudyStartRequest; import com.yigongil.backend.request.StudyUpdateRequest; -import com.yigongil.backend.response.CertificationResponse; -import com.yigongil.backend.response.FeedPostResponse; -import com.yigongil.backend.response.MembersCertificationResponse; import com.yigongil.backend.response.MyStudyResponse; -import com.yigongil.backend.response.RoundResponse; import com.yigongil.backend.response.StudyDetailResponse; import com.yigongil.backend.response.StudyListItemResponse; import com.yigongil.backend.response.StudyMemberResponse; @@ -25,7 +19,6 @@ import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; -import java.util.Optional; import org.springframework.http.ResponseEntity; @Tag(name = "스터디", description = "스터디 관련 api") @@ -56,7 +49,7 @@ ResponseEntity createStudy( @Operation(summary = "스터디 정보 수정") ResponseEntity updateStudy( @Schema(hidden = true) Member member, - @Parameter(description = "수정할 스터디 id", required = true) Long studyId, + @Parameter(description = "수정할 스터디 studyId", required = true) Long studyId, StudyUpdateRequest request ); @@ -72,7 +65,7 @@ ResponseEntity updateStudy( @Operation(summary = "스터디 지원") ResponseEntity applyStudy( @Schema(hidden = true) Member member, - @Parameter(description = "지원할 스터디 id", required = true) Long studyId + @Parameter(description = "지원할 스터디 studyId", required = true) Long studyId ); @ApiResponses( @@ -87,8 +80,8 @@ ResponseEntity applyStudy( @Operation(summary = "스터디 지원 수락") ResponseEntity permitApplicant( @Schema(hidden = true) Member master, - @Parameter(description = "수락할 스터디 id", required = true) Long studyId, - @Parameter(description = "지원한 회원 id", required = true) Long memberId + @Parameter(description = "수락할 스터디 studyId", required = true) Long studyId, + @Parameter(description = "지원한 회원 studyId", required = true) Long memberId ); @ApiResponses( @@ -103,7 +96,7 @@ ResponseEntity permitApplicant( @Operation(summary = "스터디 지원 취소") ResponseEntity deleteApplicant( @Schema(hidden = true) Member member, - @Parameter(description = "지원 취소할 스터디 id", required = true) Long studyId + @Parameter(description = "지원 취소할 스터디 studyId", required = true) Long studyId ); @ApiResponses( @@ -116,7 +109,7 @@ ResponseEntity deleteApplicant( @SecurityRequirement(name = "token") @Operation(summary = "스터디 상세 조회") ResponseEntity viewStudyDetail( - @Parameter(description = "조회할 스터디 id", required = true) Long id + @Parameter(description = "조회할 스터디 studyId", required = true) Long id ); @ApiResponses( @@ -142,7 +135,7 @@ ResponseEntity> findStudies( @SecurityRequirement(name = "token") @Operation(summary = "스터디 지원자 조회") ResponseEntity> findApplicantOfStudy( - @Parameter(description = "조회할 스터디 id", required = true) Long id, + @Parameter(description = "조회할 스터디 studyId", required = true) Long id, @Schema(hidden = true) Member master ); @@ -169,84 +162,10 @@ ResponseEntity> findApplicantOfStudy( @Operation(summary = "스터디 시작") ResponseEntity startStudy( @Schema(hidden = true) Member member, - @Parameter(description = "시작할 스터디 id", required = true) Long id, + @Parameter(description = "시작할 스터디 studyId", required = true) Long id, StudyStartRequest studyStartRequest ); - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "400", content = @Content), - @ApiResponse(responseCode = "401", content = @Content), - @ApiResponse(responseCode = "404", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "피드 조회") - ResponseEntity> findFeedPosts( - @Parameter(description = "조회할 스터디 id", required = true) Long id, - @Parameter(description = "마지막으로 본 피드의 아이디, 첫 요청에서는 필요 없음", allowEmptyValue = true) Optional oldestFeedPostId - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "400", content = @Content), - @ApiResponse(responseCode = "401", content = @Content), - @ApiResponse(responseCode = "404", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "일반 피드 등록") - ResponseEntity createFeedPost( - @Schema(hidden = true) Member member, - @Parameter(description = "피드가 등록되는 스터디 id", required = true) Long id, - FeedPostCreateRequest request - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "400", content = @Content), - @ApiResponse(responseCode = "401", content = @Content), - @ApiResponse(responseCode = "404", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "인증 피드 등록") - ResponseEntity createCertification( - @Schema(hidden = true) Member member, - @Parameter(description = "피드가 등록되는 스터디 id", required = true) Long id, - CertificationCreateRequest request - ); - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "스터디 멤버 전체 인증 정보 조회") - ResponseEntity findAllMembersCertification( - @Schema(hidden = true) Member member, - @Parameter(description = "조회하려는 스터디 id", required = true) Long id - ); - - - @ApiResponses( - value = { - @ApiResponse(responseCode = "200"), - @ApiResponse(responseCode = "401", content = @Content) - } - ) - @SecurityRequirement(name = "token") - @Operation(summary = "스터디 멤버 단일 인증 게시글 조회") - ResponseEntity findMemberCertification( - @Parameter(description = "인증 게시 회차 id") Long roundId, - @Parameter(description = "작성자 id") Long memberId - ); - @ApiResponses( value = { @ApiResponse(responseCode = "200"), @@ -257,13 +176,6 @@ ResponseEntity findMemberCertification( @Operation(summary = "스터디 멤버 역할 조회") ResponseEntity getStudyMemberRole( @Schema(hidden = true) Member member, - @Parameter(description = "멤버가 속해 있는 스터디 id", required = true) Long studyId - ); - - @SecurityRequirement(name = "token") - @Operation(summary = "주별 회차 정보 조회") - ResponseEntity> findRoundDetailsOfWeek( - @Parameter(description = "조회할 스터디 id", required = true) Long studyId, - @Parameter(description = "조회할 주차", required = true) Integer weekNumber + @Parameter(description = "멤버가 속해 있는 스터디 studyId", required = true) Long studyId ); } diff --git a/backend/src/main/java/com/yigongil/backend/utils/querycounter/PreparedStatementProxyHandler.java b/backend/src/main/java/com/yigongil/backend/utils/querycounter/PreparedStatementProxyHandler.java index bf0c1fda1..c2b8b6c59 100644 --- a/backend/src/main/java/com/yigongil/backend/utils/querycounter/PreparedStatementProxyHandler.java +++ b/backend/src/main/java/com/yigongil/backend/utils/querycounter/PreparedStatementProxyHandler.java @@ -25,7 +25,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Invocati private boolean isExecuteQuery(Method method) { String methodName = method.getName(); - return methodName.equals("executeQuery") || methodName.equals("execute") || methodName.equals("executeUpdate"); + return methodName.startsWith("execute"); } private boolean isInRequestScope() { diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 322223ef3..f9a5511f9 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -47,7 +47,7 @@ logging: oauth2: github: client: - id: github-client-id + id: github-client-studyId secret: github-secret github-login-uri: https://github.com/login/oauth/authorize provider: diff --git a/backend/src/main/resources/db/migration/V13_remove_meeting_day_of_the_week_In_round.sql b/backend/src/main/resources/db/migration/V13_remove_meeting_day_of_the_week_In_round.sql new file mode 100644 index 000000000..4c8aaf07c --- /dev/null +++ b/backend/src/main/resources/db/migration/V13_remove_meeting_day_of_the_week_In_round.sql @@ -0,0 +1,10 @@ +ALTER TABLE round ADD day_of_week INTEGER; + +UPDATE round + INNER JOIN meeting_day_of_the_week + ON round.meeting_day_of_the_week_id = meeting_day_of_the_week.id +SET round.day_of_week = meeting_day_of_the_week.day_of_week; + +alter table round drop foreign key FKp9ga9uq8uvomn6vytcr34vfl5; +ALTER TABLE round DROP COLUMN meeting_day_of_the_week_id; + diff --git a/backend/src/main/resources/yigongil-private b/backend/src/main/resources/yigongil-private index 18f1ca643..d04a1280d 160000 --- a/backend/src/main/resources/yigongil-private +++ b/backend/src/main/resources/yigongil-private @@ -1 +1 @@ -Subproject commit 18f1ca643188ea10e5a6532c47ca9306c25be390 +Subproject commit d04a1280d4b2222beea8989c1694191e371ffce4 diff --git a/backend/src/test/java/com/yigongil/backend/acceptance/steps/ApplySteps.java b/backend/src/test/java/com/yigongil/backend/acceptance/steps/ApplySteps.java index 494afa606..5cccfed51 100644 --- a/backend/src/test/java/com/yigongil/backend/acceptance/steps/ApplySteps.java +++ b/backend/src/test/java/com/yigongil/backend/acceptance/steps/ApplySteps.java @@ -37,6 +37,25 @@ public ApplySteps(SharedContext sharedContext) { sharedContext.setResponse(response); } + @Given("깃허브 아이디가 {string}인 멤버가 이름이 {string}스터디 {int}개에 신청한다.") + public void 스터디_신청(String githubId, String studyName, Integer count) { + String token = sharedContext.getToken(githubId); + for (int i = 0; i < count; i++) { + String realName = studyName + i; + ExtractableResponse response = given().log() + .all() + .header(HttpHeaders.AUTHORIZATION, token) + .when() + .post("/studies/" + sharedContext.getParameter(realName) + "/applicants") + .then() + .log() + .all() + .extract(); + + sharedContext.setResponse(response); + } + } + @When("{string}가 이름이 {string}인 스터디의 신청자를 조회한다.") public void 스터디_신청자_조회(String masterGithubId, String studyName) { ExtractableResponse response = @@ -80,6 +99,26 @@ public ApplySteps(SharedContext sharedContext) { sharedContext.setResponse(response); } + @When("{string}가 {string}의 {string} 스터디 {int}개 신청을 수락한다.") + public void 스터디_신청_수락(String masterName, String memberName, String studyName, Integer count) { + for (int i = 0; i < count; i++) { + String realName = studyName + i; + Object studyId = sharedContext.getParameter(realName); + Object memberId = sharedContext.getParameter(memberName); + + ExtractableResponse response = + given().log().all() + .header(HttpHeaders.AUTHORIZATION, sharedContext.getToken(masterName)) + .when() + .patch("/studies/{studyId}/applicants/{memberId}", studyId, memberId) + .then() + .log().all() + .extract(); + + sharedContext.setResponse(response); + } + } + @Then("{string}는 {string} 스터디의 스터디원으로 추가되어 있다.") public void 스터디원_추가_완료(String memberName, String studyName) { Long memberId = sharedContext.getId(memberName); diff --git a/backend/src/test/java/com/yigongil/backend/acceptance/steps/DatabaseCleaner.java b/backend/src/test/java/com/yigongil/backend/acceptance/steps/DatabaseCleaner.java index 32183f585..784afdaca 100644 --- a/backend/src/test/java/com/yigongil/backend/acceptance/steps/DatabaseCleaner.java +++ b/backend/src/test/java/com/yigongil/backend/acceptance/steps/DatabaseCleaner.java @@ -23,7 +23,10 @@ public void init() { this.tables = entityManager.getMetamodel().getEntities().stream() .filter(entityType -> entityType.getJavaType().getSuperclass() .getSimpleName() - .equals("BaseEntity")) + .equals("BaseEntity") + || entityType.getJavaType().getSuperclass() + .getSimpleName() + .equals("BaseRootEntity")) .map(EntityType::getName) .map(this::toSnake) .toList(); @@ -48,6 +51,7 @@ public void clean() { for (String table : tables) { entityManager.createNativeQuery("truncate table " + table).executeUpdate(); } + entityManager.createNativeQuery("truncate table day_of_week").executeUpdate(); entityManager.createNativeQuery("set foreign_key_checks = 1").executeUpdate(); } diff --git a/backend/src/test/java/com/yigongil/backend/acceptance/steps/MyStudyFindSteps.java b/backend/src/test/java/com/yigongil/backend/acceptance/steps/MyStudyFindSteps.java index c828d3de0..6455d09e4 100644 --- a/backend/src/test/java/com/yigongil/backend/acceptance/steps/MyStudyFindSteps.java +++ b/backend/src/test/java/com/yigongil/backend/acceptance/steps/MyStudyFindSteps.java @@ -4,7 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import com.yigongil.backend.domain.studymember.Role; +import com.yigongil.backend.domain.study.studymember.Role; import com.yigongil.backend.response.MyStudyResponse; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; diff --git a/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudyProgressStep.java b/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudyProgressStep.java index ebee4956d..6a693c84b 100644 --- a/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudyProgressStep.java +++ b/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudyProgressStep.java @@ -29,12 +29,14 @@ public StudyProgressStep(SharedContext sharedContext) { @Given("{int}일이 지난다.") public void 시간_소요(int days) { - given().when() - .put("/fake/proceed?days=" + days) - .then() - .log() - .all() - .extract(); + for (int i = 1; i <= days; i++) { + given().log().all().when() + .put("/fake/proceed?days=" + i) + .then() + .log() + .all() + .extract(); + } } @Given("이번주 일요일이 됐다.") @@ -43,7 +45,7 @@ public StudyProgressStep(SharedContext sharedContext) { LocalDate sunday = today.with(DayOfWeek.SUNDAY); long daysUntilSunday = ChronoUnit.DAYS.between(today, sunday); - given().when() + given().log().all().when() .put("/fake/proceed?days=" + daysUntilSunday) .then() .log() diff --git a/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudySteps.java b/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudySteps.java index 7a234ffcf..a1f3e7a5e 100644 --- a/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudySteps.java +++ b/backend/src/test/java/com/yigongil/backend/acceptance/steps/StudySteps.java @@ -75,6 +75,43 @@ public StudySteps(ObjectMapper objectMapper, SharedContext sharedContext) { sharedContext.setParameter(name, studyId); } + @Given("{string}가 제목-{string}, 정원-{string}명, 최소 주차-{string}주, 주당 진행 횟수-{string}회, 소개-{string}로 스터디를 {int}개 개설한다.") + public void 스터디를_개설한다( + String masterGithubId, + String name, + String numberOfMaximumMembers, + String minimumWeeks, + String meetingDaysCountPerWeek, + String introduction, + Integer count + ) throws JsonProcessingException { + for (int i = 0; i < count; i++) { + String realName = name + i; + StudyUpdateRequest request = new StudyUpdateRequest( + realName, + Integer.parseInt(numberOfMaximumMembers), + Integer.parseInt(minimumWeeks), + Integer.parseInt(meetingDaysCountPerWeek), + introduction + ); + String token = sharedContext.getToken(masterGithubId); + + String location = given().log().all() + .header(HttpHeaders.AUTHORIZATION, token) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(objectMapper.writeValueAsString(request)) + .when() + .post("/studies") + .then().log().all() + .extract() + .header(HttpHeaders.LOCATION); + + String studyId = location.substring(location.lastIndexOf("/") + 1); + + sharedContext.setParameter(realName, studyId); + } + } + @When("모집 중인 스터디 탭을 클릭한다.") public void 모집_중인_스터디를_요청한다() { ExtractableResponse response = given().log().all() @@ -225,6 +262,24 @@ public StudySteps(ObjectMapper objectMapper, SharedContext sharedContext) { .then().log().all(); } + @Given("{string}가 이름이 {string}인 스터디 {int}개를 {string}에 진행되도록 하여 시작한다.") + public void 스터디_시작(String memberGithubId, String studyName, Integer count, String days) { + String token = sharedContext.getToken(memberGithubId); + for (int i = 0; i < count; i++) { + String realName = studyName + i; + String studyId = (String) sharedContext.getParameter(realName); + StudyStartRequest request = new StudyStartRequest(Arrays.stream(days.split(",")).map(String::strip).toList()); + + given().log().all() + .header(HttpHeaders.AUTHORIZATION, token) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when() + .patch("/studies/" + studyId + "/start") + .then().log().all(); + } + } + @Given("{string}가 이름이 {string}인 스터디를 내일에 해당하는 요일에 진행되도록 하여 시작한다.") public void 스터디_시작_내일에_해당하는_요일(String memberGithubId, String studyName) { String token = sharedContext.getToken(memberGithubId); diff --git a/backend/src/test/java/com/yigongil/backend/application/MustDoServiceTest.java b/backend/src/test/java/com/yigongil/backend/application/MustDoServiceTest.java index 1ae6eb943..38bb2be9e 100644 --- a/backend/src/test/java/com/yigongil/backend/application/MustDoServiceTest.java +++ b/backend/src/test/java/com/yigongil/backend/application/MustDoServiceTest.java @@ -4,10 +4,11 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.BDDMockito.willReturn; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.round.Round; import com.yigongil.backend.domain.round.RoundRepository; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.round.RoundOfMember; +import com.yigongil.backend.domain.round.RoundService; import com.yigongil.backend.exception.InvalidTodoLengthException; import com.yigongil.backend.fixture.MemberFixture; import com.yigongil.backend.request.MustDoUpdateRequest; @@ -25,7 +26,7 @@ class MustDoServiceTest { @InjectMocks - private MustDoService mustDoService; + private RoundService roundService; @Mock private RoundRepository roundRepository; @@ -60,7 +61,7 @@ class _머스트두 { //when //then - assertDoesNotThrow(() -> mustDoService.updateMustDo(member, round.getId(), request)); + assertDoesNotThrow(() -> roundService.updateMustDo(member, round.getId(), request)); } @Test @@ -72,7 +73,7 @@ class _머스트두 { //when //then - assertThatThrownBy(() -> mustDoService.updateMustDo(member, round.getId(), request)) + assertThatThrownBy(() -> roundService.updateMustDo(member, round.getId(), request)) .isInstanceOf(InvalidTodoLengthException.class); } } diff --git a/backend/src/test/java/com/yigongil/backend/application/StudyServiceTest.java b/backend/src/test/java/com/yigongil/backend/application/StudyServiceTest.java index 3c6b4ce1a..4d4c34b89 100644 --- a/backend/src/test/java/com/yigongil/backend/application/StudyServiceTest.java +++ b/backend/src/test/java/com/yigongil/backend/application/StudyServiceTest.java @@ -4,10 +4,10 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.RoundRepository; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.StudyRepository; -import com.yigongil.backend.domain.studymember.StudyMemberRepository; +import com.yigongil.backend.domain.study.StudyService; +import com.yigongil.backend.domain.study.studymember.StudyMemberRepository; import com.yigongil.backend.exception.StudyNotFoundException; import com.yigongil.backend.fixture.MemberFixture; import java.util.Optional; @@ -26,10 +26,7 @@ class StudyServiceTest { private final StudyService studyService = new StudyService( studyRepository, - studyMemberRepository, - mock(CertificationService.class), - mock(FeedService.class), - mock(RoundRepository.class) + studyMemberRepository ); @Nested diff --git a/backend/src/test/java/com/yigongil/backend/domain/member/MemberTest.java b/backend/src/test/java/com/yigongil/backend/domain/member/MemberTest.java index 747b1481f..c8bec545e 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/member/MemberTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/member/MemberTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidIntroductionLengthException; import com.yigongil.backend.exception.InvalidNicknameLengthException; import com.yigongil.backend.exception.InvalidNicknamePatternException; diff --git a/backend/src/test/java/com/yigongil/backend/domain/member/TierTest.java b/backend/src/test/java/com/yigongil/backend/domain/member/TierTest.java index 5fde9d30e..34c86ea0f 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/member/TierTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/member/TierTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; +import com.yigongil.backend.domain.member.domain.Tier; import org.junit.jupiter.api.Test; class TierTest { diff --git a/backend/src/test/java/com/yigongil/backend/domain/report/ReportTest.java b/backend/src/test/java/com/yigongil/backend/domain/report/ReportTest.java index c3acd7cf8..1f234bdb8 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/report/ReportTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/report/ReportTest.java @@ -2,7 +2,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidReportContentLengthException; import com.yigongil.backend.exception.InvalidReportException; import com.yigongil.backend.exception.InvalidReportTitleLengthException; diff --git a/backend/src/test/java/com/yigongil/backend/domain/round/RoundTest.java b/backend/src/test/java/com/yigongil/backend/domain/round/RoundTest.java index 9001a9230..62126523b 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/round/RoundTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/round/RoundTest.java @@ -3,7 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidTodoLengthException; import com.yigongil.backend.exception.NotStudyMasterException; import com.yigongil.backend.fixture.RoundFixture; diff --git a/backend/src/test/java/com/yigongil/backend/domain/study/StudyTest.java b/backend/src/test/java/com/yigongil/backend/domain/study/StudyTest.java index c41b72e1d..705a1b965 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/study/StudyTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/study/StudyTest.java @@ -3,8 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.exception.InvalidMemberSizeException; import com.yigongil.backend.exception.InvalidProcessingStatusException; import com.yigongil.backend.fixture.MemberFixture; @@ -60,39 +59,6 @@ void setUp() { LOCAL_DATE_OF_MONDAY.atStartOfDay()); } - @Test - void 현재_주차의_라운드가_끝이나면_다음_주차의_첫_번째_라운드가_현재_주차가_된다() { - // given - Round previousRound = study.getCurrentRound(); - - // when - study.updateToNextRound(); - Round currentRound = study.getCurrentRound(); - - // then - assertThat(currentRound.isSameWeek(previousRound.getWeekNumber() + 1)).isTrue(); - } - - @Test - void 스터디의_현재_라운드가_종료되는_날이면_true를_반환한다() { - // given - // when - boolean actual1 = study.isCurrentRoundEndAt(LOCAL_DATE_OF_MONDAY); - - // then - assertThat(actual1).isTrue(); - } - - @Test - void 스터디의_현재_라운드가_종료되는_날이_아니면_false를_반환한다() { - // given - // when - boolean actual = study.isCurrentRoundEndAt(LOCAL_DATE_OF_MONDAY.plusDays(1)); - - // then - assertThat(actual).isFalse(); - } - @Test void 모집중이지_않은_스터디에_Member를_추가하면_예외가_발생한다() { // given @@ -122,21 +88,6 @@ void setUp() { .isInstanceOf(InvalidProcessingStatusException.class); } } - - @Test - void 스터디를_다음_라운드로_넘기면_현재_라운드가_다음_라운드로_변한다() { - // given - study.start(study.getMaster(), List.of(DayOfWeek.TUESDAY, DayOfWeek.THURSDAY), - LOCAL_DATE_OF_MONDAY.atStartOfDay()); - Round currentRound = study.getCurrentRound(); - - // when - study.updateToNextRound(); - Round nextRound = study.getCurrentRound(); - - // then - assertThat(nextRound.isNextDayOfWeek(currentRound.getDayOfWeek())).isTrue(); - } } @Test diff --git a/backend/src/test/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImplTest.java b/backend/src/test/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImplTest.java index a8e562b93..ee7ac6a1b 100644 --- a/backend/src/test/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImplTest.java +++ b/backend/src/test/java/com/yigongil/backend/domain/study/studyquery/StudyQueryRepositoryImplTest.java @@ -4,8 +4,8 @@ import com.yigongil.backend.config.JpaConfig; import com.yigongil.backend.config.QueryFactoryConfig; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; import com.yigongil.backend.domain.study.PageStrategy; import com.yigongil.backend.domain.study.ProcessingStatus; import com.yigongil.backend.domain.study.Study; diff --git a/backend/src/test/java/com/yigongil/backend/fake/FakeController.java b/backend/src/test/java/com/yigongil/backend/fake/FakeController.java index bc7b5ed20..5a7af691b 100644 --- a/backend/src/test/java/com/yigongil/backend/fake/FakeController.java +++ b/backend/src/test/java/com/yigongil/backend/fake/FakeController.java @@ -1,9 +1,9 @@ package com.yigongil.backend.fake; -import com.yigongil.backend.application.StudyService; import com.yigongil.backend.config.auth.JwtTokenProvider; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.round.RoundService; import com.yigongil.backend.response.TokenResponse; import java.time.LocalDate; import java.util.Optional; @@ -22,12 +22,16 @@ public class FakeController { private final MemberRepository memberRepository; private final JwtTokenProvider jwtTokenProvider; - private final StudyService studyService; + private final RoundService roundService; - public FakeController(MemberRepository memberRepository, JwtTokenProvider jwtTokenProvider, StudyService studyService) { + public FakeController( + MemberRepository memberRepository, + JwtTokenProvider jwtTokenProvider, + RoundService roundService + ) { this.memberRepository = memberRepository; this.jwtTokenProvider = jwtTokenProvider; - this.studyService = studyService; + this.roundService = roundService; } @GetMapping("/login/fake/tokens") @@ -47,9 +51,7 @@ public ResponseEntity createFakeToken(@RequestParam String github @PutMapping("/fake/proceed") public ResponseEntity proceed(@RequestParam Integer days) { - for (int i = 1; i <= days; i++) { - studyService.proceedRound(LocalDate.now().plusDays(i)); - } + roundService.proceedRound(LocalDate.now().plusDays(days)); return ResponseEntity.ok().build(); } } diff --git a/backend/src/test/java/com/yigongil/backend/fixture/MemberFixture.java b/backend/src/test/java/com/yigongil/backend/fixture/MemberFixture.java index 3fe1501e5..17d1e0c9e 100644 --- a/backend/src/test/java/com/yigongil/backend/fixture/MemberFixture.java +++ b/backend/src/test/java/com/yigongil/backend/fixture/MemberFixture.java @@ -1,6 +1,6 @@ package com.yigongil.backend.fixture; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; public enum MemberFixture { diff --git a/backend/src/test/java/com/yigongil/backend/fixture/RoundFixture.java b/backend/src/test/java/com/yigongil/backend/fixture/RoundFixture.java index c892b6249..38facac89 100644 --- a/backend/src/test/java/com/yigongil/backend/fixture/RoundFixture.java +++ b/backend/src/test/java/com/yigongil/backend/fixture/RoundFixture.java @@ -1,8 +1,8 @@ package com.yigongil.backend.fixture; -import com.yigongil.backend.domain.member.Member; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.round.Round; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.round.RoundOfMember; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/backend/src/test/java/com/yigongil/backend/fixture/RoundOfMemberFixture.java b/backend/src/test/java/com/yigongil/backend/fixture/RoundOfMemberFixture.java index c44b5bda4..055e63b0e 100644 --- a/backend/src/test/java/com/yigongil/backend/fixture/RoundOfMemberFixture.java +++ b/backend/src/test/java/com/yigongil/backend/fixture/RoundOfMemberFixture.java @@ -1,7 +1,7 @@ package com.yigongil.backend.fixture; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.roundofmember.RoundOfMember; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.round.RoundOfMember; public enum RoundOfMemberFixture { diff --git a/backend/src/test/java/com/yigongil/backend/fixture/StudyFixture.java b/backend/src/test/java/com/yigongil/backend/fixture/StudyFixture.java index 8a6745942..3f70cfd73 100644 --- a/backend/src/test/java/com/yigongil/backend/fixture/StudyFixture.java +++ b/backend/src/test/java/com/yigongil/backend/fixture/StudyFixture.java @@ -1,19 +1,14 @@ package com.yigongil.backend.fixture; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.round.Round; +import com.yigongil.backend.domain.member.domain.Member; import com.yigongil.backend.domain.study.ProcessingStatus; import com.yigongil.backend.domain.study.Study; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; public enum StudyFixture { 자바_스터디_모집중(null, LocalDateTime.now(), "자바", "스터디소개", ProcessingStatus.RECRUITING, 4, 2, 4), - 자바_스터디_모집중_정원_2(null, LocalDateTime.now(), "자바", "스터디소개", ProcessingStatus.RECRUITING, 2, 2, 4) - ; + 자바_스터디_모집중_정원_2(null, LocalDateTime.now(), "자바", "스터디소개", ProcessingStatus.RECRUITING, 2, 2, 4); private final Long id; private final String name; @@ -59,19 +54,4 @@ public Study toStudyWithMaster(Member master) { .minimumWeeks(minimumWeeks) .build(); } - - public Study toStudyWithRounds(RoundFixture... roundFixtures) { - List rounds = new ArrayList<>(Arrays.stream(roundFixtures) - .map(RoundFixture::toRound) - .toList()); - - return Study.builder() - .id(id) - .name(name) - .introduction(introduction) - .processingStatus(processingStatus) - .numberOfMaximumMembers(numberOfMaximumMember) - .rounds(rounds) - .build(); - } } diff --git a/backend/src/test/java/com/yigongil/backend/query/profile/ui/ProfileControllerTest.java b/backend/src/test/java/com/yigongil/backend/query/profile/ui/ProfileControllerTest.java new file mode 100644 index 000000000..2091d9217 --- /dev/null +++ b/backend/src/test/java/com/yigongil/backend/query/profile/ui/ProfileControllerTest.java @@ -0,0 +1,79 @@ +package com.yigongil.backend.query.profile.ui; + +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.yigongil.backend.config.AuthConfig; +import com.yigongil.backend.config.auth.AuthContext; +import com.yigongil.backend.config.auth.JwtTokenProvider; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.fixture.MemberFixture; +import com.yigongil.backend.query.profile.ProfileController; +import com.yigongil.backend.query.profile.ProfileService; +import com.yigongil.backend.response.ProfileResponse; +import com.yigongil.backend.ui.exceptionhandler.InternalServerErrorMessageConverter; +import com.yigongil.backend.utils.querycounter.ApiQueryCounter; +import java.util.Collections; +import java.util.Optional; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; + +@Import(AuthConfig.class) +@WebMvcTest(ProfileController.class) +class ProfileControllerTest { + + @Autowired + private MockMvc mockMvc; + + @MockBean + private ProfileService profileService; + + @MockBean + private MemberRepository memberRepository; + + @MockBean + private AuthContext authContext; + + @MockBean + private JwtTokenProvider tokenProvider; + + @MockBean + private ApiQueryCounter apiQueryCounter; + + @MockBean + private InternalServerErrorMessageConverter internalServerErrorMessageConverter; + + @Test + void 프로필_정보를_조회한다() throws Exception { + Member member = MemberFixture.김진우.toMember(); + given(memberRepository.findByIdAndDeletedFalse(1L)).willReturn(Optional.of(member)); + given(profileService.findById(1L)).willReturn( + new ProfileResponse( + member.getId(), + member.getNickname(), + member.getGithubId(), + member.getProfileImageUrl(), + 0.0, + 0, + 0, + member.getExperience(), + member.getIntroduction(), + Collections.emptyList() + ) + ); + + mockMvc.perform(get("/members/1")) + .andDo(print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.nickname").value(member.getNickname())) + .andExpect(jsonPath("$.githubId").value(member.getGithubId())); + } +} diff --git a/backend/src/test/java/com/yigongil/backend/ui/MemberControllerTest.java b/backend/src/test/java/com/yigongil/backend/ui/MemberControllerTest.java index 018552aec..416691f9b 100644 --- a/backend/src/test/java/com/yigongil/backend/ui/MemberControllerTest.java +++ b/backend/src/test/java/com/yigongil/backend/ui/MemberControllerTest.java @@ -13,12 +13,13 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.yigongil.backend.application.MemberService; import com.yigongil.backend.config.AuthConfig; import com.yigongil.backend.config.auth.AuthContext; import com.yigongil.backend.config.auth.JwtTokenProvider; -import com.yigongil.backend.domain.member.Member; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.Member; +import com.yigongil.backend.domain.member.ui.MemberController; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.member.application.MemberService; import com.yigongil.backend.fixture.MemberFixture; import com.yigongil.backend.request.ProfileUpdateRequest; import com.yigongil.backend.response.NicknameValidationResponse; @@ -64,34 +65,6 @@ class MemberControllerTest { @MockBean private InternalServerErrorMessageConverter internalServerErrorMessageConverter; - - - @Test - void 프로필_정보를_조회한다() throws Exception { - Member member = MemberFixture.김진우.toMember(); - given(memberRepository.findByIdAndDeletedFalse(1L)).willReturn(Optional.of(member)); - given(memberService.findById(1L)).willReturn( - new ProfileResponse( - member.getId(), - member.getNickname(), - member.getGithubId(), - member.getProfileImageUrl(), - 0.0, - 0, - 0, - member.getExperience(), - member.getIntroduction(), - Collections.emptyList() - ) - ); - - mockMvc.perform(get("/members/1")) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.nickname").value(member.getNickname())) - .andExpect(jsonPath("$.githubId").value(member.getGithubId())); - } - @Test void 프로필_정보를_업데이트_한다() throws Exception { ProfileUpdateRequest request = new ProfileUpdateRequest("수정된김진우", "수정된자기소개"); diff --git a/backend/src/test/java/com/yigongil/backend/ui/MustDoControllerTest.java b/backend/src/test/java/com/yigongil/backend/ui/RoundControllerTest.java similarity index 83% rename from backend/src/test/java/com/yigongil/backend/ui/MustDoControllerTest.java rename to backend/src/test/java/com/yigongil/backend/ui/RoundControllerTest.java index 5ef4b6692..363592b6c 100644 --- a/backend/src/test/java/com/yigongil/backend/ui/MustDoControllerTest.java +++ b/backend/src/test/java/com/yigongil/backend/ui/RoundControllerTest.java @@ -10,10 +10,11 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.yigongil.backend.application.MustDoService; import com.yigongil.backend.config.auth.AuthContext; import com.yigongil.backend.config.auth.JwtTokenProvider; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.round.RoundController; +import com.yigongil.backend.domain.round.RoundService; import com.yigongil.backend.fixture.MemberFixture; import com.yigongil.backend.request.MustDoUpdateRequest; import com.yigongil.backend.ui.exceptionhandler.InternalServerErrorMessageConverter; @@ -28,8 +29,8 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; -@WebMvcTest(MustDoController.class) -class MustDoControllerTest { +@WebMvcTest(RoundController.class) +class RoundControllerTest { @Autowired private MockMvc mockMvc; @@ -38,7 +39,7 @@ class MustDoControllerTest { private ObjectMapper objectMapper; @MockBean - private MustDoService mustDoService; + private RoundService roundService; @MockBean private MemberRepository memberRepository; @@ -64,7 +65,7 @@ void setUp() { void 머스트두를_생성한다() throws Exception { MustDoUpdateRequest request = new MustDoUpdateRequest("첫 머스트두"); - willDoNothing().given(mustDoService).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); + willDoNothing().given(roundService).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); mockMvc.perform(put("/rounds/1/todos") .header(HttpHeaders.AUTHORIZATION, "1") @@ -78,7 +79,7 @@ void setUp() { void 머스트두를_업데이트한다() throws Exception { MustDoUpdateRequest request = new MustDoUpdateRequest("수정"); - willDoNothing().given(mustDoService).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); + willDoNothing().given(roundService).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); mockMvc.perform(put("/rounds/1/todos") .header(HttpHeaders.AUTHORIZATION, "1") @@ -87,6 +88,6 @@ void setUp() { .andDo(print()) .andExpect(status().isOk()); - verify(mustDoService, only()).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); + verify(roundService, only()).updateMustDo(MemberFixture.김진우.toMember(), 1L, request); } } diff --git a/backend/src/test/java/com/yigongil/backend/ui/StudyControllerTest.java b/backend/src/test/java/com/yigongil/backend/ui/StudyControllerTest.java index b3262f7d3..16d981d1e 100644 --- a/backend/src/test/java/com/yigongil/backend/ui/StudyControllerTest.java +++ b/backend/src/test/java/com/yigongil/backend/ui/StudyControllerTest.java @@ -9,13 +9,14 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.yigongil.backend.application.CertificationService; -import com.yigongil.backend.application.FeedService; -import com.yigongil.backend.application.MustDoService; -import com.yigongil.backend.application.StudyService; import com.yigongil.backend.config.auth.AuthContext; import com.yigongil.backend.config.auth.JwtTokenProvider; -import com.yigongil.backend.domain.member.MemberRepository; +import com.yigongil.backend.domain.certification.CertificationService; +import com.yigongil.backend.domain.feedpost.FeedService; +import com.yigongil.backend.domain.member.domain.MemberRepository; +import com.yigongil.backend.domain.round.RoundService; +import com.yigongil.backend.domain.study.StudyController; +import com.yigongil.backend.domain.study.StudyService; import com.yigongil.backend.fixture.MemberFixture; import com.yigongil.backend.request.StudyUpdateRequest; import com.yigongil.backend.ui.exceptionhandler.InternalServerErrorMessageConverter; @@ -43,7 +44,7 @@ class StudyControllerTest { private StudyService studyService; @MockBean - private MustDoService mustDoService; + private RoundService roundService; @MockBean private MemberRepository memberRepository; diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml index 7fdfd8537..3e3374ecc 100644 --- a/backend/src/test/resources/application.yml +++ b/backend/src/test/resources/application.yml @@ -15,23 +15,27 @@ spring: jpa: properties: hibernate: - format_sql: true - show_sql: true + jdbc: + batch_size: 500 + order_inserts: true + order_updates: true + batch_versioned_data: true hibernate: ddl-auto: update + database-platform: org.hibernate.dialect.MySQL5InnoDBDialect flyway: enabled: false logging: level: - org.hibernate.type: trace - org.hibernate.sql: debug + org.hibernate.type: error + org.hibernate.sql: error oauth2: github: client: - id: github-client-id + id: github-client-studyId secret: github-secret github-login-uri: https://github.com/login/oauth/authorize provider: diff --git a/backend/src/test/resources/features/proceed_test.feature b/backend/src/test/resources/features/proceed_test.feature new file mode 100644 index 000000000..6249365f0 --- /dev/null +++ b/backend/src/test/resources/features/proceed_test.feature @@ -0,0 +1,31 @@ +Feature: 스터디를 진행 성능 테스트 + Scenario Outline: 스터디 진행 성능 테스트 + Given "jinwoo"의 깃허브 아이디로 회원가입을 한다. + Given "jinwoo"가 제목-"자바숫자", 정원-"8"명, 최소 주차-"7"주, 주당 진행 횟수-"3"회, 소개-"스터디소개1"로 스터디를 개 개설한다. + Given "noiman1"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman1"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman1"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman2"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman2"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman2"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman3"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman3"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman3"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman4"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman4"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman4"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman5"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman5"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman5"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman6"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman6"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman6"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "noiman7"의 깃허브 아이디로 회원가입을 한다. + Given 깃허브 아이디가 "noiman7"인 멤버가 이름이 "자바숫자"스터디 개에 신청한다. + Given "jinwoo"가 "noiman7"의 "자바숫자" 스터디 개 신청을 수락한다. + Given "jinwoo"가 이름이 "자바숫자"인 스터디 개를 "MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY"에 진행되도록 하여 시작한다. + Given 5일이 지난다. + + Examples: + | count | + | 100 |