From c00c512df1553ab37424b692835273e63a232601 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Tue, 13 Sep 2022 17:58:37 +0900 Subject: [PATCH 001/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20cache=20key=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/api.ts | 1 - frontend/src/pages/AuthPage/AuthPage.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frontend/src/constants/api.ts b/frontend/src/constants/api.ts index 08de8588..c47a2f4e 100644 --- a/frontend/src/constants/api.ts +++ b/frontend/src/constants/api.ts @@ -6,7 +6,6 @@ const API = { const API_URL = process.env.API_URL; const CACHE_KEY = { - AUTH: 'auth', CATEGORIES: 'categories', CATEGORY: 'category', ENTER: 'enter', diff --git a/frontend/src/pages/AuthPage/AuthPage.tsx b/frontend/src/pages/AuthPage/AuthPage.tsx index 2e7fdfc2..9bec9cd7 100644 --- a/frontend/src/pages/AuthPage/AuthPage.tsx +++ b/frontend/src/pages/AuthPage/AuthPage.tsx @@ -7,7 +7,7 @@ import { useRecoilState } from 'recoil'; import { userState } from '@/recoil/atoms'; import { PATH } from '@/constants'; -import { API, CACHE_KEY } from '@/constants/api'; +import { API } from '@/constants/api'; import { getSearchParam } from '@/utils'; import { setAccessToken } from '@/utils/storage'; @@ -20,7 +20,7 @@ function AuthPage() { const code = getSearchParam(API.AUTH_CODE_KEY); - const { mutate } = useMutation(CACHE_KEY.AUTH, () => loginApi.auth(code), { + const { mutate } = useMutation(() => loginApi.auth(code), { onError: () => onErrorAuth(), onSuccess: (data) => onSuccessAuth(data), }); From 7202d8289cd6f300930abef377179da2a8a278f6 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 14 Sep 2022 14:10:30 +0900 Subject: [PATCH 002/148] =?UTF-8?q?fix:=20=EC=A0=84=EC=97=AD=20=EC=82=AC?= =?UTF-8?q?=EC=9A=A9=EC=9E=90=20=EC=83=81=ED=83=9C=EA=B0=80=20=EC=B4=88?= =?UTF-8?q?=EA=B8=B0=ED=99=94=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useUserValue.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frontend/src/hooks/useUserValue.ts b/frontend/src/hooks/useUserValue.ts index a24c04f6..ced217f8 100644 --- a/frontend/src/hooks/useUserValue.ts +++ b/frontend/src/hooks/useUserValue.ts @@ -1,6 +1,6 @@ import { AxiosError, AxiosResponse } from 'axios'; import { useQuery } from 'react-query'; -import { useRecoilState, useResetRecoilState, useSetRecoilState } from 'recoil'; +import { useRecoilState, useSetRecoilState } from 'recoil'; import { ProfileType } from '@/@types/profile'; @@ -15,8 +15,6 @@ import profileApi from '@/api/profile'; function useUserValue() { const [user, setUser] = useRecoilState(userState); - const resetUser = useResetRecoilState(userState); - const setSideBarOpen = useSetRecoilState(sideBarState); const { isLoading, isSuccess } = useQuery( @@ -44,9 +42,9 @@ function useUserValue() { ); const onErrorValidate = () => { + setUser({ accessToken: '' }); setSideBarOpen(false); removeAccessToken(); - resetUser(); }; return { isAuthenticating: isLoading, user }; From 932c7029e597264df320d28ab6fec429d47177f0 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 14 Sep 2022 15:57:08 +0900 Subject: [PATCH 003/148] =?UTF-8?q?perf:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20access=20token=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EC=9A=94=EC=B2=AD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 11 ++++------- .../src/components/PublicRoute/PublicRoute.tsx | 15 --------------- frontend/src/pages/MainPage/MainPage.tsx | 12 +++++++----- 3 files changed, 11 insertions(+), 27 deletions(-) delete mode 100644 frontend/src/components/PublicRoute/PublicRoute.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index a9fa4487..ea4a8e8d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -7,7 +7,6 @@ import useSnackBar from '@/hooks/useSnackBar'; import ErrorBoundary from '@/components/@common/ErrorBoundary/ErrorBoundary'; import NavBar from '@/components/NavBar/NavBar'; import ProtectRoute from '@/components/ProtectRoute/ProtectRoute'; -import PublicRoute from '@/components/PublicRoute/PublicRoute'; import SideBar from '@/components/SideBar/SideBar'; import SnackBar from '@/components/SnackBar/SnackBar'; import AuthPage from '@/pages/AuthPage/AuthPage'; @@ -51,12 +50,10 @@ function App() { - }> - } /> - } /> - } /> - } /> - + } /> + } /> + } /> + } /> }> } /> } /> diff --git a/frontend/src/components/PublicRoute/PublicRoute.tsx b/frontend/src/components/PublicRoute/PublicRoute.tsx deleted file mode 100644 index b6b15b61..00000000 --- a/frontend/src/components/PublicRoute/PublicRoute.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { Outlet } from 'react-router-dom'; - -import useUserValue from '@/hooks/useUserValue'; - -function PublicRoute() { - const { isAuthenticating } = useUserValue(); - - if (isAuthenticating) { - return <>; - } - - return ; -} - -export default PublicRoute; diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx index adaac4fd..3b9e9787 100644 --- a/frontend/src/pages/MainPage/MainPage.tsx +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -1,14 +1,16 @@ -import { useRecoilValue } from 'recoil'; - -import { userState } from '@/recoil/atoms'; +import useUserValue from '@/hooks/useUserValue'; import CalendarPage from '@/pages/CalendarPage/CalendarPage'; import StartPage from '@/pages/StartPage/StartPage'; function MainPage() { - const { accessToken } = useRecoilValue(userState); + const { isAuthenticating, user } = useUserValue(); + + if (isAuthenticating) { + return <>; + } - if (!accessToken) { + if (!user.accessToken) { return ; } From f2c0b7b566f1246bee2087b0389a94cb2b3bc9d4 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:30:41 +0900 Subject: [PATCH 004/148] =?UTF-8?q?feat:=20MemberRepository=20=EC=98=88?= =?UTF-8?q?=EC=99=B8=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?default=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/member/domain/MemberRepository.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java index 602d671b..0c815569 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.member.domain; +import com.allog.dallog.domain.member.exception.NoSuchMemberException; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -8,4 +9,14 @@ public interface MemberRepository extends JpaRepository { Optional findByEmail(final String email); boolean existsByEmail(final String email); + + default Member getById(final Long id) { + return findById(id) + .orElseThrow(NoSuchMemberException::new); + } + + default Member getByEmail(final String email) { + return findByEmail(email) + .orElseThrow(NoSuchMemberException::new); + } } From 839ce1fe0ce574b01f7d320df06a7f0f0426a900 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:31:16 +0900 Subject: [PATCH 005/148] =?UTF-8?q?feat:=20OAuthTokenRepository=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EC=B2=98=EB=A6=AC=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20default=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/auth/domain/OAuthTokenRepository.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepository.java b/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepository.java index c95cf2f8..7d8e3135 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.auth.domain; +import com.allog.dallog.domain.auth.exception.NoSuchOAuthTokenException; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,4 +11,9 @@ public interface OAuthTokenRepository extends JpaRepository { Optional findByMemberId(final Long memberId); void deleteByMemberId(final Long memberId); + + default OAuthToken getByMemberId(final Long memberId) { + return findByMemberId(memberId) + .orElseThrow(NoSuchOAuthTokenException::new); + } } From 903e8ce0807757e8c3cb56be5414390e6db56579 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:31:49 +0900 Subject: [PATCH 006/148] =?UTF-8?q?feat:=20=ED=86=A0=ED=81=B0=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/application/AuthService.java | 72 +++++++++++++------ 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index fdcb2dd1..48749d9e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -1,14 +1,23 @@ package com.allog.dallog.domain.auth.application; +import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; + import com.allog.dallog.domain.auth.domain.OAuthToken; import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; import com.allog.dallog.domain.auth.dto.OAuthMember; import com.allog.dallog.domain.auth.dto.request.TokenRequest; import com.allog.dallog.domain.auth.dto.response.TokenResponse; -import com.allog.dallog.domain.auth.exception.NoSuchOAuthTokenException; -import com.allog.dallog.domain.composition.application.RegisterService; -import com.allog.dallog.domain.member.application.MemberService; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; +import com.allog.dallog.domain.member.domain.SocialType; +import com.allog.dallog.domain.member.exception.NoSuchMemberException; +import com.allog.dallog.domain.subscription.domain.Color; +import com.allog.dallog.domain.subscription.domain.ColorPickerStrategy; +import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; +import java.util.concurrent.ThreadLocalRandom; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -16,21 +25,28 @@ @Service public class AuthService { + private static final String PERSONAL_CATEGORY_NAME = "내 일정"; + private static final ColorPickerStrategy PICK_RANDOM_STRATEGY = () -> ThreadLocalRandom.current() + .nextInt(Color.values().length); + + private final MemberRepository memberRepository; + private final CategoryRepository categoryRepository; + private final SubscriptionRepository subscriptionRepository; + private final OAuthTokenRepository oAuthTokenRepository; private final OAuthUri oAuthUri; private final OAuthClient oAuthClient; - private final MemberService memberService; - private final RegisterService registerService; - private final OAuthTokenRepository oAuthTokenRepository; private final TokenProvider tokenProvider; - public AuthService(final OAuthUri oAuthUri, final OAuthClient oAuthClient, final MemberService memberService, - final RegisterService registerService, final OAuthTokenRepository oAuthTokenRepository, - final TokenProvider tokenProvider) { + public AuthService(final MemberRepository memberRepository, final CategoryRepository categoryRepository, + final SubscriptionRepository subscriptionRepository, + final OAuthTokenRepository oAuthTokenRepository, final OAuthUri oAuthUri, + final OAuthClient oAuthClient, final TokenProvider tokenProvider) { + this.memberRepository = memberRepository; + this.categoryRepository = categoryRepository; + this.subscriptionRepository = subscriptionRepository; + this.oAuthTokenRepository = oAuthTokenRepository; this.oAuthUri = oAuthUri; this.oAuthClient = oAuthClient; - this.memberService = memberService; - this.registerService = registerService; - this.oAuthTokenRepository = oAuthTokenRepository; this.tokenProvider = tokenProvider; } @@ -44,7 +60,13 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { String redirectUri = tokenRequest.getRedirectUri(); OAuthMember oAuthMember = oAuthClient.getOAuthMember(code, redirectUri); - Member foundMember = getMember(oAuthMember); + + if (!memberRepository.existsByEmail(oAuthMember.getEmail())) { + Member savedMember = memberRepository.save(parseMember(oAuthMember)); + Category savedCategory = saveCategory(savedMember); + saveSubscription(savedMember, savedCategory); + } + Member foundMember = memberRepository.getByEmail(oAuthMember.getEmail()); OAuthToken oAuthToken = getOAuthToken(oAuthMember, foundMember); oAuthToken.change(oAuthMember.getRefreshToken()); @@ -53,12 +75,18 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { return new TokenResponse(accessToken); } - private Member getMember(final OAuthMember oAuthMember) { - if (!memberService.existsByEmail(oAuthMember.getEmail())) { - registerService.register(oAuthMember); - } + private Category saveCategory(final Member savedMember) { + return categoryRepository.save(new Category(PERSONAL_CATEGORY_NAME, savedMember, PERSONAL)); + } - return memberService.getByEmail(oAuthMember.getEmail()); + private Subscription saveSubscription(final Member savedMember, final Category savedCategory) { + Color randomColor = Color.pickAny(PICK_RANDOM_STRATEGY); + return subscriptionRepository.save(new Subscription(savedMember, savedCategory, randomColor)); + } + + private Member parseMember(final OAuthMember oAuthMember) { + return new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), oAuthMember.getProfileImageUrl(), + SocialType.GOOGLE); } private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member foundMember) { @@ -66,14 +94,16 @@ private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member fou oAuthTokenRepository.save(new OAuthToken(foundMember, oAuthMember.getRefreshToken())); } - return oAuthTokenRepository.findByMemberId(foundMember.getId()) - .orElseThrow(NoSuchOAuthTokenException::new); + return oAuthTokenRepository.getByMemberId(foundMember.getId()); } public Long extractMemberId(final String accessToken) { tokenProvider.validateToken(accessToken); Long memberId = Long.valueOf(tokenProvider.getPayload(accessToken)); - memberService.validateExistsMember(memberId); + if (!memberRepository.existsById(memberId)) { + throw new NoSuchMemberException(); + } + return memberId; } } From 60961a93edd4e0d532a2c2ef26ff90104fed6c37 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:32:16 +0900 Subject: [PATCH 007/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/application/MemberService.java | 47 ++++++++-------- .../application/CalendarServiceTest.java | 49 ++++++++--------- .../member/application/MemberServiceTest.java | 53 ------------------- 3 files changed, 48 insertions(+), 101 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java index 53cd1307..690f0c3f 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java @@ -1,14 +1,18 @@ package com.allog.dallog.domain.member.application; import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; -import com.allog.dallog.domain.member.exception.NoSuchMemberException; +import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; +import java.util.List; +import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,12 +21,18 @@ public class MemberService { private final MemberRepository memberRepository; + private final CategoryRepository categoryRepository; + private final ScheduleRepository scheduleRepository; private final SubscriptionRepository subscriptionRepository; private final OAuthTokenRepository oAuthTokenRepository; - public MemberService(final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, + public MemberService(final MemberRepository memberRepository, final CategoryRepository categoryRepository, + final ScheduleRepository scheduleRepository, + final SubscriptionRepository subscriptionRepository, final OAuthTokenRepository oAuthTokenRepository) { this.memberRepository = memberRepository; + this.categoryRepository = categoryRepository; + this.scheduleRepository = scheduleRepository; this.subscriptionRepository = subscriptionRepository; this.oAuthTokenRepository = oAuthTokenRepository; } @@ -34,7 +44,7 @@ public MemberResponse save(final Member member) { } public MemberResponse findById(final Long id) { - return new MemberResponse(getMember(id)); + return new MemberResponse(memberRepository.getById(id)); } public MemberResponse findBySubscriptionId(final Long subscriptionId) { @@ -47,33 +57,22 @@ public MemberResponse findBySubscriptionId(final Long subscriptionId) { @Transactional public void update(final Long id, final MemberUpdateRequest request) { - Member member = getMember(id); + Member member = memberRepository.getById(id); member.change(request.getDisplayName()); } @Transactional public void deleteById(final Long id) { - oAuthTokenRepository.deleteByMemberId(id); - memberRepository.deleteById(id); - } - - public Member getMember(final Long id) { - return memberRepository.findById(id) - .orElseThrow(NoSuchMemberException::new); - } + List categoryIds = categoryRepository.findByMemberId(id) + .stream() + .map(Category::getId) + .collect(Collectors.toList()); - public Member getByEmail(final String email) { - return memberRepository.findByEmail(email) - .orElseThrow(NoSuchMemberException::new); - } - - public boolean existsByEmail(final String email) { - return memberRepository.existsByEmail(email); - } + scheduleRepository.deleteByCategoryIdIn(categoryIds); + subscriptionRepository.deleteByCategoryIdIn(categoryIds); + categoryRepository.deleteByMemberId(id); - public void validateExistsMember(final Long id) { - if (!memberRepository.existsById(id)) { - throw new NoSuchMemberException("존재하지 않는 회원입니다."); - } + oAuthTokenRepository.deleteByMemberId(id); + memberRepository.deleteById(id); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 52762016..0225713a 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -29,8 +29,9 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.domain.auth.domain.OAuthToken; -import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +import com.allog.dallog.common.fixtures.AuthFixtures; +import com.allog.dallog.domain.auth.application.AuthService; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; @@ -56,13 +57,13 @@ class CalendarServiceTest extends ServiceTest { private CalendarService calendarService; @Autowired - private MemberService memberService; + private AuthService authService; @Autowired - private CategoryService categoryService; + private MemberService memberService; @Autowired - private OAuthTokenRepository oAuthTokenRepository; + private CategoryService categoryService; @Autowired private ExternalCategoryDetailRepository externalCategoryDetailRepository; @@ -77,52 +78,52 @@ class CalendarServiceTest extends ServiceTest { @Test void 시작일시와_종료일시로_유저의_캘린더를_일정_유형에_따라_분류하고_정렬하여_반환한다() { // given - MemberResponse 후디 = memberService.save(후디()); - oAuthTokenRepository.save(new OAuthToken(memberService.getMember(후디.getId()), "wegwefaasdasdasda")); + TokenResponse tokenResponse = authService.generateToken(AuthFixtures.MEMBER_인증_코드_토큰_요청()); + Long memberId = authService.extractMemberId(tokenResponse.getAccessToken()); - CategoryResponse BE_일정_응답 = categoryService.save(후디.getId(), BE_일정_생성_요청); + CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); Category BE_일정 = categoryService.getCategory(BE_일정_응답.getId()); - subscriptionService.save(후디.getId(), BE_일정.getId()); + subscriptionService.save(memberId, BE_일정.getId()); /* 장기간 일정 */ - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 첫번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 두번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 세번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 네번째", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_15일_16시_0분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 다섯번째", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); /* 종일 일정 */ - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); /* 몇시간 일정 */ - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("몇시간 첫번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("몇시간 두번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("몇시간 세번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(후디.getId(), BE_일정.getId(), + scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("몇시간 네번째", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(후디.getId(), 우아한테크코스_외부_일정_생성_요청); + CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(memberId, 우아한테크코스_외부_일정_생성_요청); Category 우아한테크코스 = categoryService.getCategory(우아한테크코스_외부_일정_응답.getId()); externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); - subscriptionService.save(후디.getId(), 우아한테크코스.getId()); + subscriptionService.save(memberId, 우아한테크코스.getId()); // when - MemberScheduleResponses memberScheduleResponses = calendarService.findSchedulesByMemberId(후디.getId(), + MemberScheduleResponses memberScheduleResponses = calendarService.findSchedulesByMemberId(memberId, new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); // then diff --git a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java index 7dcea0a5..e5e33a09 100644 --- a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java @@ -1,15 +1,12 @@ package com.allog.dallog.domain.member.application; -import static com.allog.dallog.common.fixtures.MemberFixtures.리버_이메일; import static com.allog.dallog.common.fixtures.MemberFixtures.매트; import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑_이메일; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; import com.allog.dallog.domain.member.exception.NoSuchMemberException; @@ -32,19 +29,6 @@ class MemberServiceTest extends ServiceTest { assertThat(파랑).isNotNull(); } - @DisplayName("이메일로 회원을 찾는다.") - @Test - void 이메일로_회원을_찾는다() { - // given - MemberResponse 파랑 = memberService.save(파랑()); - - // when - Member actual = memberService.getByEmail(파랑_이메일); - - // then - assertThat(actual.getId()).isEqualTo(파랑.getId()); - } - @DisplayName("id를 통해 회원을 단건 조회한다.") @Test void id를_통해_회원을_단건_조회한다() { @@ -87,41 +71,4 @@ class MemberServiceTest extends ServiceTest { assertThatThrownBy(() -> memberService.findById(후디.getId())) .isInstanceOf(NoSuchMemberException.class); } - - @DisplayName("주어진 이메일로 가입된 회원이 있으면 true를 반환한다.") - @Test - void 주어진_이메일로_가입된_회원이_있으면_true를_반환한다() { - // given - memberService.save(파랑()); - - // when - boolean actual = memberService.existsByEmail(파랑_이메일); - - // then - assertThat(actual).isTrue(); - } - - @DisplayName("주어진 이메일로 가입된 회원이 없으면 false를 반환한다.") - @Test - void 주어진_이메일로_가입된_회원이_없으면_false를_반환한다() { - // given - memberService.save(파랑()); - - // when - boolean actual = memberService.existsByEmail(리버_이메일); - - // then - assertThat(actual).isFalse(); - } - - @DisplayName("회원이 존재하지 않으면 예외를 던진다.") - @Test - void 회원이_존재하지_않으면_예외를_던진다() { - // given - Long id = 0L; - - // when & then - assertThatThrownBy(() -> memberService.validateExistsMember(id)) - .isInstanceOf(NoSuchMemberException.class); - } } From 5b43dc53265202a91bc1eaffc5653e18260fe57b Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:32:53 +0900 Subject: [PATCH 008/148] =?UTF-8?q?feat:=20AuthService=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/allog/dallog/presentation/MemberController.java | 7 ++----- .../allog/dallog/presentation/MemberControllerTest.java | 8 ++------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/presentation/MemberController.java b/backend/src/main/java/com/allog/dallog/presentation/MemberController.java index d07b7ba9..009d3597 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/MemberController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/MemberController.java @@ -1,7 +1,6 @@ package com.allog.dallog.presentation; import com.allog.dallog.domain.auth.dto.LoginMember; -import com.allog.dallog.domain.composition.application.RegisterService; import com.allog.dallog.domain.member.application.MemberService; import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; @@ -19,11 +18,9 @@ public class MemberController { private final MemberService memberService; - private final RegisterService registerService; - public MemberController(final MemberService memberService, final RegisterService registerService) { + public MemberController(final MemberService memberService) { this.memberService = memberService; - this.registerService = registerService; } @GetMapping("/me") @@ -41,7 +38,7 @@ public ResponseEntity update(@AuthenticationPrincipal LoginMember loginMem @DeleteMapping("/me") public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember) { - registerService.deleteByMemberId(loginMember.getId()); + memberService.deleteById(loginMember.getId()); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java index d060262d..7d28205c 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java @@ -21,7 +21,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.allog.dallog.domain.auth.application.AuthService; -import com.allog.dallog.domain.composition.application.RegisterService; import com.allog.dallog.domain.member.application.MemberService; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; import com.allog.dallog.domain.member.exception.NoSuchMemberException; @@ -44,9 +43,6 @@ class MemberControllerTest extends ControllerTest { @MockBean private MemberService memberService; - @MockBean - private RegisterService registerService; - @DisplayName("자신의 회원 정보를 조회한다.") @Test void 자신의_회원_정보를_조회한다() throws Exception { @@ -128,8 +124,8 @@ class MemberControllerTest extends ControllerTest { void 등록된_회원이_회원탈퇴_한다() throws Exception { // given willDoNothing() - .given(registerService) - .deleteByMemberId(any()); + .given(memberService) + .deleteById(any()); // when & then mockMvc.perform(delete("/api/members/me") From 2020abf00c14dd4d6703d894ba95dbd38e54227e Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 13 Sep 2022 16:33:14 +0900 Subject: [PATCH 009/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/RegisterService.java | 58 -------------- .../application/RegisterServiceTest.java | 77 ------------------- 2 files changed, 135 deletions(-) delete mode 100644 backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java delete mode 100644 backend/src/test/java/com/allog/dallog/domain/composition/application/RegisterServiceTest.java diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java deleted file mode 100644 index 74b83626..00000000 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.allog.dallog.domain.composition.application; - -import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; - -import com.allog.dallog.domain.auth.dto.OAuthMember; -import com.allog.dallog.domain.category.application.CategoryService; -import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; -import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.domain.Member; -import com.allog.dallog.domain.member.domain.SocialType; -import com.allog.dallog.domain.member.dto.MemberResponse; -import com.allog.dallog.domain.subscription.application.SubscriptionService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Transactional(readOnly = true) -@Service -public class RegisterService { - - private static final String PERSONAL_CATEGORY_NAME = "내 일정"; - - private final MemberService memberService; - private final CategoryService categoryService; - private final SubscriptionService subscriptionService; - - public RegisterService(final MemberService memberService, final CategoryService categoryService, - final SubscriptionService subscriptionService) { - this.memberService = memberService; - this.categoryService = categoryService; - this.subscriptionService = subscriptionService; - } - - @Transactional - public MemberResponse register(final OAuthMember oAuthMember) { - MemberResponse memberResponse = createMember(oAuthMember); - Long categoryId = createPersonalCategory(memberResponse.getId()).getId(); - subscriptionService.save(memberResponse.getId(), categoryId); - return memberResponse; - } - - private MemberResponse createMember(final OAuthMember oAuthMember) { - Member member = new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), - oAuthMember.getProfileImageUrl(), SocialType.GOOGLE); - return memberService.save(member); - } - - private CategoryResponse createPersonalCategory(final Long memberId) { - CategoryCreateRequest categoryCreateRequest = new CategoryCreateRequest(PERSONAL_CATEGORY_NAME, PERSONAL); - return categoryService.save(memberId, categoryCreateRequest); - } - - @Transactional - public void deleteByMemberId(final Long memberId) { - categoryService.deleteByMemberId(memberId); - memberService.deleteById(memberId); - } -} diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/RegisterServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/RegisterServiceTest.java deleted file mode 100644 index 90200807..00000000 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/RegisterServiceTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.allog.dallog.domain.composition.application; - -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_MEMBER; -import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertAll; - -import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.domain.auth.dto.OAuthMember; -import com.allog.dallog.domain.category.application.CategoryService; -import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; -import com.allog.dallog.domain.member.exception.NoSuchMemberException; -import com.allog.dallog.domain.subscription.application.SubscriptionService; -import com.allog.dallog.domain.subscription.domain.Subscription; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -class RegisterServiceTest extends ServiceTest { - - @Autowired - private RegisterService registerService; - - @Autowired - private MemberService memberService; - - @Autowired - private CategoryService categoryService; - - @Autowired - private SubscriptionService subscriptionService; - - @DisplayName("유저 생성 시 개인 카테고리를 자동으로 생성하고 구독한다.") - @Test - void 유저_생성_시_개인_카테고리를_자동으로_생성하고_구독한다() { - // given & when - OAuthMember member = STUB_OAUTH_MEMBER(); - MemberResponse memberResponse = registerService.register(member); - - List subscriptions = subscriptionService.getAllByMemberId(memberResponse.getId()); - - // then - assertAll(() -> { - assertThat(memberResponse.getEmail()).isEqualTo(member.getEmail()); - assertThat(subscriptions).hasSize(1); - }); - } - - @DisplayName("유저 삭제 시 연관된 구독과 카테고리를 순차적으로 삭제한다.") - @Test - void 유저_삭제_시_연관된_구독과_카테고리를_순차적으로_삭제한다() { - // given - OAuthMember member = STUB_OAUTH_MEMBER(); - MemberResponse memberResponse = registerService.register(member); - Long memberId = memberResponse.getId(); - - CategoryResponse categoryResponse = categoryService.save(memberId, 공통_일정_생성_요청); - subscriptionService.save(memberId, categoryResponse.getId()); - - // when - registerService.deleteByMemberId(memberId); - - // then - assertAll(() -> { - assertThatThrownBy(() -> memberService.getMember(memberId)) - .isInstanceOf(NoSuchMemberException.class); - assertThatThrownBy(() -> categoryService.getCategory(memberId)) - .isInstanceOf(NoSuchCategoryException.class); - assertThat(subscriptionService.getAllByMemberId(memberId)).hasSize(0); - }); - } -} From 5a729890e4c5497ca144f23290373f83034459f3 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 00:49:12 +0900 Subject: [PATCH 010/148] =?UTF-8?q?test:=20Auth=20=EB=B0=8F=20OAuth=20fixt?= =?UTF-8?q?ure=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/common/fixtures/AuthFixtures.java | 33 +++++-- .../dallog/common/fixtures/OAuthFixtures.java | 96 +++++++++++++++++++ .../oauth/client/StubOAuthClient.java | 9 +- 3 files changed, 124 insertions(+), 14 deletions(-) create mode 100644 backend/src/test/java/com/allog/dallog/common/fixtures/OAuthFixtures.java diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java index 3b166c3f..6e17ff71 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java @@ -1,6 +1,11 @@ package com.allog.dallog.common.fixtures; -import com.allog.dallog.domain.auth.dto.OAuthMember; +import static com.allog.dallog.common.fixtures.OAuthFixtures.관리자; +import static com.allog.dallog.common.fixtures.OAuthFixtures.리버; +import static com.allog.dallog.common.fixtures.OAuthFixtures.매트; +import static com.allog.dallog.common.fixtures.OAuthFixtures.파랑; +import static com.allog.dallog.common.fixtures.OAuthFixtures.후디; + import com.allog.dallog.domain.auth.dto.request.TokenRequest; import com.allog.dallog.domain.auth.dto.response.TokenResponse; @@ -33,6 +38,26 @@ public class AuthFixtures { public static final String STUB_OAUTH_SCOPE = "openid"; public static final String STUB_OAUTH_TOKEN_TYPE = "Bearer"; + public static TokenRequest 관리자_인증_코드_토큰_요청() { + return new TokenRequest(관리자.getCode(), "https://dallog.me/oauth"); + } + + public static TokenRequest 파랑_인증_코드_토큰_요청() { + return new TokenRequest(파랑.getCode(), "https://dallog.me/oauth"); + } + + public static TokenRequest 리버_인증_코드_토큰_요청() { + return new TokenRequest(리버.getCode(), "https://dallog.me/oauth"); + } + + public static TokenRequest 후디_인증_코드_토큰_요청() { + return new TokenRequest(후디.getCode(), "https://dallog.me/oauth"); + } + + public static TokenRequest 매트_인증_코드_토큰_요청() { + return new TokenRequest(매트.getCode(), "https://dallog.me/oauth"); + } + public static TokenRequest MEMBER_인증_코드_토큰_요청() { return new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); } @@ -41,11 +66,5 @@ public class AuthFixtures { return new TokenResponse(STUB_MEMBER_인증_코드); } - public static OAuthMember STUB_OAUTH_MEMBER() { - return new OAuthMember(MEMBER_이메일, MEMBER_이름, MEMBER_프로필, MEMBER_REFRESH_TOKEN); - } - public static OAuthMember STUB_OAUTH_CREATOR() { - return new OAuthMember(CREATOR_이메일, CREATOR_이름, CREATOR_프로필, CREATOR_REFRESH_TOKEN); - } } diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthFixtures.java new file mode 100644 index 00000000..ea0907fa --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthFixtures.java @@ -0,0 +1,96 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.auth.dto.OAuthMember; +import java.util.Arrays; +import java.util.NoSuchElementException; + +public enum OAuthFixtures { + + 관리자("관리자", 관리자()), + 파랑("파랑", 파랑()), + 리버("리버", 리버()), + 후디("후디", 후디()), + 매트("매트", 매트()), + MEMBER("member authorization code", MEMBER()), + CREATOR("creator authorization code", CREATOR()); + + private String code; + private OAuthMember oAuthMember; + + OAuthFixtures(final String code, final OAuthMember oAuthMember) { + this.code = code; + this.oAuthMember = oAuthMember; + } + + public static OAuthMember parseOAuthMember(final String code) { + OAuthFixtures oAuthFixtures = Arrays.stream(values()) + .filter(value -> value.code.equals(code)) + .findFirst() + .orElseThrow(NoSuchElementException::new); + return oAuthFixtures.oAuthMember; + } + + private static OAuthMember 관리자() { + String 관리자_이메일 = "dallog.admin@gmail.com"; + String 관리자_이름 = "관리자"; + String 관리자_프로필 = "/admin.png"; + String 관리자_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; + return new OAuthMember(관리자_이메일, 관리자_이름, 관리자_프로필, 관리자_REFRESH_TOKEN); + } + + private static OAuthMember 파랑() { + String 파랑_이메일 = "parang@email.com"; + String 파랑_이름 = "파랑"; + String 파랑_프로필 = "/parang.png"; + String 파랑_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; + return new OAuthMember(파랑_이메일, 파랑_이름, 파랑_프로필, 파랑_REFRESH_TOKEN); + } + + private static OAuthMember 리버() { + String 리버_이메일 = "leaver@email.com"; + String 리버_이름 = "리버"; + String 리버_프로필 = "/leaver.png"; + String 리버_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; + return new OAuthMember(리버_이메일, 리버_이름, 리버_프로필, 리버_REFRESH_TOKEN); + } + + private static OAuthMember 후디() { + String 후디_이메일 = "devhudi@gmail.com"; + String 후디_이름 = "후디"; + String 후디_프로필 = "/hudi.png"; + String 후디_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; + return new OAuthMember(후디_이메일, 후디_이름, 후디_프로필, 후디_REFRESH_TOKEN); + } + + private static OAuthMember 매트() { + String 매트_이메일 = "dev.hyeonic@gmail.com"; + String 매트_이름 = "매트"; + String 매트_프로필 = "/mat.png"; + String 매트_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; + return new OAuthMember(매트_이메일, 매트_이름, 매트_프로필, 매트_REFRESH_TOKEN); + } + + private static OAuthMember MEMBER() { + String MEMBER_이메일 = "member@email.com"; + String MEMBER_이름 = "member"; + String MEMBER_프로필 = "/member.png"; + String MEMBER_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.ccccccccc"; + return new OAuthMember(MEMBER_이메일, MEMBER_이름, MEMBER_프로필, MEMBER_REFRESH_TOKEN); + } + + private static OAuthMember CREATOR() { + String CREATOR_이메일 = "creator@email.com"; + String CREATOR_이름 = "creator"; + String CREATOR_프로필 = "/creator.png"; + String CREATOR_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.ccccccccc"; + return new OAuthMember(CREATOR_이메일, CREATOR_이름, CREATOR_프로필, CREATOR_REFRESH_TOKEN); + } + + public String getCode() { + return code; + } + + public OAuthMember getOAuthMember() { + return oAuthMember; + } +} diff --git a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java index 0b8654a5..d3bd4403 100644 --- a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java +++ b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java @@ -1,13 +1,11 @@ package com.allog.dallog.infrastructure.oauth.client; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_ACCESS_TOKEN; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_CREATOR; import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_EXPIRES_IN; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_MEMBER; import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_SCOPE; import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_TOKEN_TYPE; +import com.allog.dallog.common.fixtures.OAuthFixtures; import com.allog.dallog.domain.auth.application.OAuthClient; import com.allog.dallog.domain.auth.dto.OAuthMember; import com.allog.dallog.domain.auth.dto.response.OAuthAccessTokenResponse; @@ -16,10 +14,7 @@ public class StubOAuthClient implements OAuthClient { @Override public OAuthMember getOAuthMember(final String code, final String redirectUri) { - if (code.equals(STUB_MEMBER_인증_코드)) { - return STUB_OAUTH_MEMBER(); - } - return STUB_OAUTH_CREATOR(); + return OAuthFixtures.parseOAuthMember(code); } @Override From bc5cec93c7db8688858fb932dc328dbecd55215b Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 00:49:48 +0900 Subject: [PATCH 011/148] =?UTF-8?q?test:=20=EA=B8=B0=EC=A1=B4=20MemberServ?= =?UTF-8?q?ice=20save=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/common/annotation/ServiceTest.java | 11 ++ .../application/CategoryServiceTest.java | 19 +-- .../application/CalendarServiceTest.java | 75 +++++------- .../CategorySubscriptionServiceTest.java | 23 ++-- .../application/SchedulerServiceTest.java | 111 +++++++++-------- .../member/application/MemberServiceTest.java | 33 ++--- .../application/ScheduleServiceTest.java | 115 +++++++++--------- .../application/SubscriptionServiceTest.java | 115 +++++++++--------- 8 files changed, 238 insertions(+), 264 deletions(-) diff --git a/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java b/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java index 5d24814d..529b758d 100644 --- a/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java @@ -2,6 +2,9 @@ import com.allog.dallog.common.DatabaseCleaner; import com.allog.dallog.common.config.ExternalApiConfig; +import com.allog.dallog.domain.auth.application.AuthService; +import com.allog.dallog.domain.auth.dto.request.TokenRequest; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @@ -9,6 +12,9 @@ @SpringBootTest(classes = ExternalApiConfig.class) public class ServiceTest { + @Autowired + private AuthService authService; + @Autowired private DatabaseCleaner databaseCleaner; @@ -16,4 +22,9 @@ public class ServiceTest { void setUp() { databaseCleaner.execute(); } + + protected Long parseMemberId(final TokenRequest tokenRequest) { + TokenResponse tokenResponse = authService.generateToken(tokenRequest); + return authService.extractMemberId(tokenResponse.getAccessToken()); + } } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 36e4380a..b49100b5 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -1,5 +1,7 @@ package com.allog.dallog.domain.category.application; +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_이름; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; @@ -14,7 +16,6 @@ import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_생성_요청; import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_이름; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; -import static com.allog.dallog.common.fixtures.MemberFixtures.리버; import static com.allog.dallog.common.fixtures.MemberFixtures.매트; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_생성_요청; @@ -27,6 +28,7 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.common.fixtures.CategoryFixtures; +import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; @@ -40,7 +42,6 @@ import com.allog.dallog.domain.member.application.MemberService; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; -import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; @@ -59,9 +60,6 @@ class CategoryServiceTest extends ServiceTest { @Autowired private CategoryService categoryService; - @Autowired - private CategoryRepository categoryRepository; - @Autowired private SubscriptionService subscriptionService; @@ -71,6 +69,12 @@ class CategoryServiceTest extends ServiceTest { @Autowired private MemberService memberService; + @Autowired + private AuthService authService; + + @Autowired + private CategoryRepository categoryRepository; + @Autowired private MemberRepository memberRepository; @@ -176,9 +180,8 @@ class CategoryServiceTest extends ServiceTest { @Test void 개인_카테고리는_전체_조회_대상에서_제외된다() { // given - MemberResponse 후디 = memberService.save(후디()); // 후디의 개인 카테고리가 생성된다 - MemberResponse 리버 = memberService.save(리버()); // 리버의 개인 카테고리가 생성된다 - categoryService.save(후디.getId(), 내_일정_생성_요청); + authService.generateToken(후디_인증_코드_토큰_요청()); + authService.generateToken(리버_인증_코드_토큰_요청()); // when CategoriesResponse response = categoryService.findNormalByName("", PageRequest.of(0, 10)); diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 0225713a..0f5dafc0 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -1,14 +1,15 @@ package com.allog.dallog.domain.composition.application; +import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.파랑_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_외부_일정_생성_요청; -import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; -import static com.allog.dallog.common.fixtures.MemberFixtures.리버; -import static com.allog.dallog.common.fixtures.MemberFixtures.매트; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; @@ -29,17 +30,12 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.common.fixtures.AuthFixtures; -import com.allog.dallog.domain.auth.application.AuthService; -import com.allog.dallog.domain.auth.dto.response.TokenResponse; import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; @@ -56,12 +52,6 @@ class CalendarServiceTest extends ServiceTest { @Autowired private CalendarService calendarService; - @Autowired - private AuthService authService; - - @Autowired - private MemberService memberService; - @Autowired private CategoryService categoryService; @@ -78,8 +68,7 @@ class CalendarServiceTest extends ServiceTest { @Test void 시작일시와_종료일시로_유저의_캘린더를_일정_유형에_따라_분류하고_정렬하여_반환한다() { // given - TokenResponse tokenResponse = authService.generateToken(AuthFixtures.MEMBER_인증_코드_토큰_요청()); - Long memberId = authService.extractMemberId(tokenResponse.getAccessToken()); + Long memberId = parseMemberId(MEMBER_인증_코드_토큰_요청()); CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); Category BE_일정 = categoryService.getCategory(BE_일정_응답.getId()); @@ -142,48 +131,48 @@ class CalendarServiceTest extends ServiceTest { @Test void 카테고리를_구독하는_유저들의_모든_내부_일정을_가져온다() { // given - MemberResponse 관리자 = memberService.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); /* 카테고리에 일정 추가 */ - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 2", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); /* 카테고리 구독 */ - MemberResponse 후디 = memberService.save(후디()); - MemberResponse 파랑 = memberService.save(파랑()); - MemberResponse 매트 = memberService.save(매트()); - MemberResponse 리버 = memberService.save(리버()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); - subscriptionService.save(후디.getId(), 공통_일정.getId()); - subscriptionService.save(파랑.getId(), 공통_일정.getId()); - subscriptionService.save(매트.getId(), 공통_일정.getId()); - subscriptionService.save(리버.getId(), 공통_일정.getId()); + subscriptionService.save(후디_id, 공통_일정.getId()); + subscriptionService.save(파랑_id, 공통_일정.getId()); + subscriptionService.save(매트_id, 공통_일정.getId()); + subscriptionService.save(리버_id, 공통_일정.getId()); - subscriptionService.save(매트.getId(), BE_일정.getId()); - subscriptionService.save(매트.getId(), FE_일정.getId()); - subscriptionService.save(리버.getId(), FE_일정.getId()); + subscriptionService.save(매트_id, BE_일정.getId()); + subscriptionService.save(매트_id, FE_일정.getId()); + subscriptionService.save(리버_id, FE_일정.getId()); // when List actual = calendarService.getSchedulesOfSubscriberIds( - List.of(후디.getId(), 파랑.getId(), 매트.getId(), 리버.getId()), - new DateRangeRequest("2022-07-07T16:00", "2022-08-15T14:00")); + List.of(후디_id, 파랑_id, 매트_id, 리버_id), new DateRangeRequest("2022-07-07T16:00", "2022-08-15T14:00")); // then assertThat(actual.stream().map(IntegrationSchedule::getTitle)) diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java index 975e3fee..fbe868db 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java @@ -2,12 +2,10 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_생성_요청; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; import static org.assertj.core.api.Assertions.assertThat; import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; +import com.allog.dallog.common.fixtures.AuthFixtures; import com.allog.dallog.domain.subscription.application.SubscriptionService; import com.allog.dallog.domain.subscription.domain.Subscription; import java.util.List; @@ -20,9 +18,6 @@ class CategorySubscriptionServiceTest extends ServiceTest { @Autowired private CategorySubscriptionService categorySubscriptionService; - @Autowired - private MemberService memberService; - @Autowired private SubscriptionService subscriptionService; @@ -30,29 +25,29 @@ class CategorySubscriptionServiceTest extends ServiceTest { @Test void 카테고리_생성_시_자동으로_구독한다() { // given - MemberResponse 파랑 = memberService.save(파랑()); + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); // when - categorySubscriptionService.save(파랑.getId(), 공통_일정_생성_요청); + categorySubscriptionService.save(파랑_id, 공통_일정_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑.getId()); + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); // then - assertThat(subscriptions).hasSize(1); + assertThat(subscriptions).hasSize(2); } @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") @Test void 외부_카테고리_생성_시_자동으로_구독한다() { // given - MemberResponse 파랑 = memberService.save(파랑()); + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); // when - categorySubscriptionService.save(파랑.getId(), 대한민국_공휴일_생성_요청); + categorySubscriptionService.save(파랑_id, 대한민국_공휴일_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑.getId()); + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); // then - assertThat(subscriptions).hasSize(1); + assertThat(subscriptions).hasSize(2); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java index f8294715..886f2234 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java @@ -1,13 +1,13 @@ package com.allog.dallog.domain.composition.application; +import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.파랑_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; -import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; -import static com.allog.dallog.common.fixtures.MemberFixtures.리버; -import static com.allog.dallog.common.fixtures.MemberFixtures.매트; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; @@ -30,7 +30,6 @@ import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; @@ -67,43 +66,43 @@ class SchedulerServiceTest extends ServiceTest { void 비어있는_기간_목록을_반환한다() { // given /* 관리자 및 카테고리 생성 */ - MemberResponse 관리자 = memberService.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); /* 카테고리에 일정 추가 */ - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 2", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); /* 카테고리 구독 */ - MemberResponse 후디 = memberService.save(후디()); - MemberResponse 파랑 = memberService.save(파랑()); - MemberResponse 매트 = memberService.save(매트()); - MemberResponse 리버 = memberService.save(리버()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); - subscriptionService.save(후디.getId(), 공통_일정.getId()); - subscriptionService.save(파랑.getId(), 공통_일정.getId()); - subscriptionService.save(매트.getId(), 공통_일정.getId()); - subscriptionService.save(리버.getId(), 공통_일정.getId()); + subscriptionService.save(후디_id, 공통_일정.getId()); + subscriptionService.save(파랑_id, 공통_일정.getId()); + subscriptionService.save(매트_id, 공통_일정.getId()); + subscriptionService.save(리버_id, 공통_일정.getId()); - subscriptionService.save(매트.getId(), BE_일정.getId()); - subscriptionService.save(매트.getId(), FE_일정.getId()); - subscriptionService.save(리버.getId(), FE_일정.getId()); + subscriptionService.save(매트_id, BE_일정.getId()); + subscriptionService.save(매트_id, FE_일정.getId()); + subscriptionService.save(리버_id, FE_일정.getId()); // when List actual = schedulerService.getAvailablePeriods(공통_일정.getId(), @@ -126,46 +125,46 @@ class SchedulerServiceTest extends ServiceTest { void 체크하지_않은_구독은_일정_산정_대상에서_제외된다() { // given /* 관리자 및 카테고리 생성 */ - MemberResponse 관리자 = memberService.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); /* 카테고리에 일정 추가 */ - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); - scheduleService.save(관리자.getId(), 공통_일정.getId(), + scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); - scheduleService.save(관리자.getId(), BE_일정.getId(), + scheduleService.save(관리자_id, BE_일정.getId(), new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleService.save(관리자.getId(), FE_일정.getId(), + scheduleService.save(관리자_id, FE_일정.getId(), new ScheduleCreateRequest("프론트엔드 일정 2", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); /* 카테고리 구독 */ - MemberResponse 후디 = memberService.save(후디()); - MemberResponse 파랑 = memberService.save(파랑()); - MemberResponse 매트 = memberService.save(매트()); - MemberResponse 리버 = memberService.save(리버()); - - subscriptionService.save(후디.getId(), 공통_일정.getId()); - subscriptionService.save(파랑.getId(), 공통_일정.getId()); - subscriptionService.save(매트.getId(), 공통_일정.getId()); - subscriptionService.save(리버.getId(), 공통_일정.getId()); - subscriptionService.save(매트.getId(), BE_일정.getId()); - - SubscriptionResponse 매트_FE_일정_구독 = subscriptionService.save(매트.getId(), FE_일정.getId()); - SubscriptionResponse 리버_FE_일정_구독 = subscriptionService.save(리버.getId(), FE_일정.getId()); - subscriptionService.update(매트_FE_일정_구독.getId(), 매트.getId(), + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + + subscriptionService.save(후디_id, 공통_일정.getId()); + subscriptionService.save(파랑_id, 공통_일정.getId()); + subscriptionService.save(매트_id, 공통_일정.getId()); + subscriptionService.save(리버_id, 공통_일정.getId()); + subscriptionService.save(매트_id, BE_일정.getId()); + + SubscriptionResponse 매트_FE_일정_구독 = subscriptionService.save(매트_id, FE_일정.getId()); + SubscriptionResponse 리버_FE_일정_구독 = subscriptionService.save(리버_id, FE_일정.getId()); + subscriptionService.update(매트_FE_일정_구독.getId(), 매트_id, new SubscriptionUpdateRequest(Color.COLOR_1, false)); - subscriptionService.update(리버_FE_일정_구독.getId(), 리버.getId(), + subscriptionService.update(리버_FE_일정_구독.getId(), 리버_id, new SubscriptionUpdateRequest(Color.COLOR_1, false)); // when diff --git a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java index e5e33a09..c5cc4bbe 100644 --- a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java @@ -1,12 +1,10 @@ package com.allog.dallog.domain.member.application; -import static com.allog.dallog.common.fixtures.MemberFixtures.매트; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.common.fixtures.AuthFixtures; import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; import com.allog.dallog.domain.member.exception.NoSuchMemberException; @@ -19,42 +17,31 @@ class MemberServiceTest extends ServiceTest { @Autowired private MemberService memberService; - @DisplayName("회원을 저장한다.") - @Test - void 회원을_저장한다() { - // given & when - MemberResponse 파랑 = memberService.save(파랑()); - - // then - assertThat(파랑).isNotNull(); - } - @DisplayName("id를 통해 회원을 단건 조회한다.") @Test void id를_통해_회원을_단건_조회한다() { // given - MemberResponse 파랑 = memberService.save(파랑()); + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); // when & then - assertThat(memberService.findById(파랑.getId())) - .usingRecursiveComparison() - .isEqualTo(파랑); + assertThat(memberService.findById(파랑_id).getId()) + .isEqualTo(파랑_id); } @DisplayName("회원의 이름을 수정한다.") @Test void 회원의_이름을_수정한다() { // given - MemberResponse 매트 = memberService.save(매트()); + Long 매트_id = parseMemberId(AuthFixtures.매트_인증_코드_토큰_요청()); String 패트_이름 = "패트"; MemberUpdateRequest 매트_수정_요청 = new MemberUpdateRequest(패트_이름); // when - memberService.update(매트.getId(), 매트_수정_요청); + memberService.update(매트_id, 매트_수정_요청); // then - MemberResponse actual = memberService.findById(매트.getId()); + MemberResponse actual = memberService.findById(매트_id); assertThat(actual.getDisplayName()).isEqualTo(패트_이름); } @@ -62,13 +49,13 @@ class MemberServiceTest extends ServiceTest { @Test void 회원을_제거한다() { // given - MemberResponse 후디 = memberService.save(후디()); + Long 후디_id = parseMemberId(AuthFixtures.후디_인증_코드_토큰_요청()); // when - memberService.deleteById(후디.getId()); + memberService.deleteById(후디_id); // then - assertThatThrownBy(() -> memberService.findById(후디.getId())) + assertThatThrownBy(() -> memberService.findById(후디_id)) .isInstanceOf(NoSuchMemberException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index ec9653dc..eadd0dd9 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -1,9 +1,9 @@ package com.allog.dallog.domain.schedule.application; +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_생성_요청; -import static com.allog.dallog.common.fixtures.MemberFixtures.리버; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; @@ -25,8 +25,6 @@ import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; @@ -45,16 +43,13 @@ class ScheduleServiceTest extends ServiceTest { @Autowired private CategoryService categoryService; - @Autowired - private MemberService memberService; - @DisplayName("새로운 일정을 생성한다.") @Test void 새로운_일정을_생성한다() { // given & when - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // then assertThat(알록달록_회의.getTitle()).isEqualTo(알록달록_회의_제목); @@ -64,15 +59,15 @@ class ScheduleServiceTest extends ServiceTest { @Test void 새로운_일정을_생성_할_때_일정_제목의_길이가_50을_초과하는_경우_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); String 잘못된_일정_제목 = "일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일"; ScheduleCreateRequest 잘못된_일정_생성_요청 = new ScheduleCreateRequest(잘못된_일정_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 잘못된_일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(후디_id, BE_일정.getId(), 잘못된_일정_생성_요청)). isInstanceOf(InvalidScheduleException.class); } @@ -80,15 +75,15 @@ class ScheduleServiceTest extends ServiceTest { @Test void 새로운_일정을_생성_할_때_일정_메모의_길이가_255를_초과하는_경우_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); String 잘못된_일정_메모 = "1".repeat(256); ScheduleCreateRequest 잘못된_일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 잘못된_일정_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 잘못된_일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(후디_id, BE_일정.getId(), 잘못된_일정_생성_요청)). isInstanceOf(InvalidScheduleException.class); } @@ -96,15 +91,15 @@ class ScheduleServiceTest extends ServiceTest { @Test void 새로운_일정을_생성_할_때_종료일시가_시작일시_이전이라면_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; LocalDateTime 종료일시 = 날짜_2022년_7월_1일_0시_0분; ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(후디_id, BE_일정.getId(), 일정_생성_요청)). isInstanceOf(InvalidScheduleException.class); } @@ -112,16 +107,16 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_생성_요청자가_카테고리의_생성자가_아닌경우_예외를_던진다() { // given - MemberResponse 리버 = memberService.save(리버()); - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(리버.getId(), BE_일정.getId(), 일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(리버_id, BE_일정.getId(), 일정_생성_요청)). isInstanceOf(NoPermissionException.class); } @@ -129,14 +124,14 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_생성시_전달한_카테고리가_존재하지_않는다면_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(후디.getId(), 0L, 일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(후디_id, 0L, 일정_생성_요청)). isInstanceOf(NoSuchCategoryException.class); } @@ -144,15 +139,15 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_생성시_전달한_카테고리가_외부_연동_카테고리라면_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse 대한민국_공휴일 = categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse 대한민국_공휴일 = categoryService.save(후디_id, 대한민국_공휴일_생성_요청); LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); // when & then - assertThatThrownBy(() -> scheduleService.save(후디.getId(), 대한민국_공휴일.getId(), 일정_생성_요청)). + assertThatThrownBy(() -> scheduleService.save(후디_id, 대한민국_공휴일.getId(), 일정_생성_요청)). isInstanceOf(NoPermissionException.class); } @@ -160,9 +155,9 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정의_ID로_단건_일정을_조회한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when ScheduleResponse response = scheduleService.findById(알록달록_회의.getId()); @@ -181,9 +176,9 @@ class ScheduleServiceTest extends ServiceTest { @Test void 존재하지_않는_일정_ID로_단건_일정을_조회하면_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); Long 잘못된_아이디 = 0L; // when & then @@ -194,14 +189,14 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정을_수정한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); // when - scheduleService.update(기존_일정.getId(), 후디.getId(), 일정_수정_요청); + scheduleService.update(기존_일정.getId(), 후디_id, 일정_수정_요청); // then ScheduleResponse actual = scheduleService.findById(기존_일정.getId()); @@ -220,15 +215,15 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_수정_시_일정의_카테고리에_대한_권한이_없을_경우_예외가_발생한다() { // given - MemberResponse 리버 = memberService.save(리버()); - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); // when & then - assertThatThrownBy(() -> scheduleService.update(기존_일정.getId(), 리버.getId(), 일정_수정_요청)) + assertThatThrownBy(() -> scheduleService.update(기존_일정.getId(), 리버_id, 일정_수정_요청)) .isInstanceOf(NoPermissionException.class); } @@ -236,14 +231,14 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_수정_시_존재하지_않은_일정일_경우_예외가_발생한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); // when & then - assertThatThrownBy(() -> scheduleService.update(기존_일정.getId() + 1, 후디.getId(), 일정_수정_요청)) + assertThatThrownBy(() -> scheduleService.update(기존_일정.getId() + 1, 후디_id, 일정_수정_요청)) .isInstanceOf(NoSuchScheduleException.class); } @@ -251,12 +246,12 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정을_삭제한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when - scheduleService.deleteById(알록달록_회의.getId(), 후디.getId()); + scheduleService.deleteById(알록달록_회의.getId(), 후디_id); // then assertThatThrownBy(() -> scheduleService.findById(알록달록_회의.getId())) @@ -267,13 +262,13 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_삭제_시_일정의_카테고리에_대한_권한이_없을_경우_예외가_발생한다() { // given - MemberResponse 리버 = memberService.save(리버()); - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when & then - assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId(), 리버.getId())) + assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId(), 리버_id)) .isInstanceOf(NoPermissionException.class); } @@ -281,12 +276,12 @@ class ScheduleServiceTest extends ServiceTest { @Test void 일정_삭제_시_존재하지_않은_일정일_경우_예외가_발생한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when & then - assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId() + 1, 후디.getId())) + assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId() + 1, 후디_id)) .isInstanceOf(NoSuchScheduleException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index d0d815fc..53da27aa 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -1,15 +1,15 @@ package com.allog.dallog.domain.subscription.application; +import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.파랑_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_이름; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정_생성_요청; -import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; -import static com.allog.dallog.common.fixtures.MemberFixtures.매트; -import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -18,8 +18,6 @@ import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.member.application.MemberService; -import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.subscription.domain.Color; import com.allog.dallog.domain.subscription.dto.request.SubscriptionUpdateRequest; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; @@ -35,9 +33,6 @@ class SubscriptionServiceTest extends ServiceTest { - @Autowired - private MemberService memberService; - @Autowired private CategoryService categoryService; @@ -48,11 +43,11 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 새로운_구독을_생성한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); // when - SubscriptionResponse response = subscriptionService.save(후디.getId(), BE_일정.getId()); + SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); // then assertThat(response.getCategory().getName()).isEqualTo(BE_일정_이름); @@ -62,13 +57,13 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 자신이_생성하지_않은_개인_카테고리를_구독시_예외가_발생한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse 후디_개인_학습_일정 = categoryService.save(후디.getId(), 내_일정_생성_요청); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse 후디_개인_학습_일정 = categoryService.save(후디_id, 내_일정_생성_요청); - MemberResponse 매트 = memberService.save(매트()); + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); // when & then - assertThatThrownBy(() -> subscriptionService.save(매트.getId(), 후디_개인_학습_일정.getId())) + assertThatThrownBy(() -> subscriptionService.save(매트_id, 후디_개인_학습_일정.getId())) .isInstanceOf(NoPermissionException.class) .hasMessage("구독 권한이 없는 카테고리입니다."); } @@ -77,12 +72,12 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 이미_존재하는_구독_정보를_저장할_경우_예외를_던진다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - subscriptionService.save(후디.getId(), BE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + subscriptionService.save(후디_id, BE_일정.getId()); // when & then - assertThatThrownBy(() -> subscriptionService.save(후디.getId(), BE_일정.getId())) + assertThatThrownBy(() -> subscriptionService.save(후디_id, BE_일정.getId())) .isInstanceOf(ExistSubscriptionException.class); } @@ -90,9 +85,9 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 구독_id를_기반으로_단건_조회한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - SubscriptionResponse 빨간색_구독 = subscriptionService.save(후디.getId(), BE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + SubscriptionResponse 빨간색_구독 = subscriptionService.save(후디_id, BE_일정.getId()); // when SubscriptionResponse foundResponse = subscriptionService.findById(빨간색_구독.getId()); @@ -116,35 +111,35 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 회원_정보를_기반으로_구독_정보를_조회한다() { // given - MemberResponse 관리자 = memberService.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); - MemberResponse 후디 = memberService.save(후디()); - subscriptionService.save(후디.getId(), 공통_일정.getId()); - subscriptionService.save(후디.getId(), BE_일정.getId()); - subscriptionService.save(후디.getId(), FE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + subscriptionService.save(후디_id, 공통_일정.getId()); + subscriptionService.save(후디_id, BE_일정.getId()); + subscriptionService.save(후디_id, FE_일정.getId()); // when - SubscriptionsResponse subscriptionsResponse = subscriptionService.findByMemberId(후디.getId()); + SubscriptionsResponse subscriptionsResponse = subscriptionService.findByMemberId(후디_id); // then - assertThat(subscriptionsResponse.getSubscriptions()).hasSize(3); + assertThat(subscriptionsResponse.getSubscriptions()).hasSize(4); } @DisplayName("구독 정보를 수정한다.") @Test void 구독_정보를_수정한다() { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - SubscriptionResponse response = subscriptionService.save(후디.getId(), BE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); Color color = Color.COLOR_1; // when SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(color, true); - subscriptionService.update(response.getId(), 후디.getId(), request); + subscriptionService.update(response.getId(), 후디_id, request); // then assertAll(() -> { @@ -158,15 +153,15 @@ class SubscriptionServiceTest extends ServiceTest { @ValueSource(strings = {"#111", "#1111", "#11111", "123456", "#**1234", "##12345", "334172#", "#00FF00"}) void 구독_정보_수정_시_존재하지_않는_색상인_경우_예외를_던진다(final String colorCode) { // given - MemberResponse 후디 = memberService.save(후디()); - CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); - SubscriptionResponse response = subscriptionService.save(후디.getId(), BE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); // when SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(colorCode, true); // then - assertThatThrownBy(() -> subscriptionService.update(response.getId(), 후디.getId(), request)) + assertThatThrownBy(() -> subscriptionService.update(response.getId(), 후디_id, request)) .isInstanceOf(InvalidSubscriptionException.class); } @@ -174,35 +169,35 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 구독_정보를_삭제한다() { // given - MemberResponse 관리자 = memberService.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); - MemberResponse 후디 = memberService.save(후디()); - SubscriptionResponse response = subscriptionService.save(후디.getId(), 공통_일정.getId()); - subscriptionService.save(후디.getId(), BE_일정.getId()); - subscriptionService.save(후디.getId(), FE_일정.getId()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + SubscriptionResponse response = subscriptionService.save(후디_id, 공통_일정.getId()); + subscriptionService.save(후디_id, BE_일정.getId()); + subscriptionService.save(후디_id, FE_일정.getId()); // when - subscriptionService.deleteById(response.getId(), 후디.getId()); + subscriptionService.deleteById(response.getId(), 후디_id); // then - assertThat(subscriptionService.findByMemberId(후디.getId()).getSubscriptions()).hasSize(2); + assertThat(subscriptionService.findByMemberId(후디_id).getSubscriptions()).hasSize(3); } @DisplayName("자신의 구독 정보가 아닌 구독을 삭제할 경우 예외를 던진다.") @Test void 자신의_구독_정보가_아닌_구독을_삭제할_경우_예외를_던진다() { // given - MemberResponse 관리자 = memberService.save(관리자()); - MemberResponse 파랑 = memberService.save(파랑()); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - SubscriptionResponse 공통_일정_구독 = subscriptionService.save(파랑.getId(), 공통_일정.getId()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + SubscriptionResponse 공통_일정_구독 = subscriptionService.save(파랑_id, 공통_일정.getId()); // when & then - assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자.getId())) + assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자_id)) .isInstanceOf(NoPermissionException.class); } @@ -210,13 +205,13 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 자신이_만든_카테고리에_대한_구독을_삭제할_경우_예외를_던진다() { // given - MemberResponse 관리자 = memberService.save(관리자()); + Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - SubscriptionResponse 공통_일정_구독 = subscriptionService.save(관리자.getId(), 공통_일정.getId()); + CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); + SubscriptionResponse 공통_일정_구독 = subscriptionService.save(관리자_id, 공통_일정.getId()); // when & then - assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자.getId())) + assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자_id)) .isInstanceOf(NoPermissionException.class); } } From 79c9c1e8fbbb7ac29923c74276364be132aa46b5 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 00:50:04 +0900 Subject: [PATCH 012/148] =?UTF-8?q?refactor:=20Memberservice=20=EB=B6=88?= =?UTF-8?q?=ED=95=84=EC=9A=94=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/member/application/MemberService.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java index 690f0c3f..316418c7 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java @@ -37,12 +37,6 @@ public MemberService(final MemberRepository memberRepository, final CategoryRepo this.oAuthTokenRepository = oAuthTokenRepository; } - @Transactional - public MemberResponse save(final Member member) { - Member newMember = memberRepository.save(member); - return new MemberResponse(newMember); - } - public MemberResponse findById(final Long id) { return new MemberResponse(memberRepository.getById(id)); } From 4eb9f4306edc09708c851344a075faab747d28ca Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 00:51:36 +0900 Subject: [PATCH 013/148] =?UTF-8?q?refactor:=20subscription=20repository?= =?UTF-8?q?=20default=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/member/application/MemberService.java | 4 +--- .../domain/subscription/domain/SubscriptionRepository.java | 6 ++++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java index 316418c7..d4e5b3ea 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java @@ -10,7 +10,6 @@ import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; -import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -42,8 +41,7 @@ public MemberResponse findById(final Long id) { } public MemberResponse findBySubscriptionId(final Long subscriptionId) { - Subscription subscription = subscriptionRepository.findById(subscriptionId) - .orElseThrow(NoSuchSubscriptionException::new); + Subscription subscription = subscriptionRepository.getById(subscriptionId); Member member = subscription.getMember(); return new MemberResponse(member); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index f6217765..eca517cb 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.subscription.domain; +import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -14,4 +15,9 @@ public interface SubscriptionRepository extends JpaRepository categoryIds); + + default Subscription getById(final Long id) { + return findById(id) + .orElseThrow(NoSuchSubscriptionException::new); + } } From 337a6226d72d74c5668a2f78bb526e85e86817cf Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 00:53:04 +0900 Subject: [PATCH 014/148] =?UTF-8?q?refactor:=20AuthService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/auth/application/AuthService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index 48749d9e..125b600a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -75,6 +75,11 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { return new TokenResponse(accessToken); } + private Member parseMember(final OAuthMember oAuthMember) { + return new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), oAuthMember.getProfileImageUrl(), + SocialType.GOOGLE); + } + private Category saveCategory(final Member savedMember) { return categoryRepository.save(new Category(PERSONAL_CATEGORY_NAME, savedMember, PERSONAL)); } @@ -84,11 +89,6 @@ private Subscription saveSubscription(final Member savedMember, final Category s return subscriptionRepository.save(new Subscription(savedMember, savedCategory, randomColor)); } - private Member parseMember(final OAuthMember oAuthMember) { - return new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), oAuthMember.getProfileImageUrl(), - SocialType.GOOGLE); - } - private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member foundMember) { if (!oAuthTokenRepository.existsByMemberId(foundMember.getId())) { oAuthTokenRepository.save(new OAuthToken(foundMember, oAuthMember.getRefreshToken())); From a5c2124edab1b6b5e8b97ca68dd0bc8b0a23bc9f Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 12:26:20 +0900 Subject: [PATCH 015/148] =?UTF-8?q?refactor:=20AuthService=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/application/AuthService.java | 36 +++++++++++-------- .../member/domain/MemberRepository.java | 6 ++++ 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index 125b600a..b6e6ad7c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -12,7 +12,6 @@ import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.member.domain.SocialType; -import com.allog.dallog.domain.member.exception.NoSuchMemberException; import com.allog.dallog.domain.subscription.domain.Color; import com.allog.dallog.domain.subscription.domain.ColorPickerStrategy; import com.allog.dallog.domain.subscription.domain.Subscription; @@ -60,13 +59,7 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { String redirectUri = tokenRequest.getRedirectUri(); OAuthMember oAuthMember = oAuthClient.getOAuthMember(code, redirectUri); - - if (!memberRepository.existsByEmail(oAuthMember.getEmail())) { - Member savedMember = memberRepository.save(parseMember(oAuthMember)); - Category savedCategory = saveCategory(savedMember); - saveSubscription(savedMember, savedCategory); - } - Member foundMember = memberRepository.getByEmail(oAuthMember.getEmail()); + Member foundMember = findMember(oAuthMember); OAuthToken oAuthToken = getOAuthToken(oAuthMember, foundMember); oAuthToken.change(oAuthMember.getRefreshToken()); @@ -75,6 +68,22 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { return new TokenResponse(accessToken); } + private Member findMember(final OAuthMember oAuthMember) { + if (!memberRepository.existsByEmail(oAuthMember.getEmail())) { + return saveMember(oAuthMember); + } + + return memberRepository.getByEmail(oAuthMember.getEmail()); + } + + private Member saveMember(final OAuthMember oAuthMember) { + Member savedMember = memberRepository.save(parseMember(oAuthMember)); + Category savedCategory = saveCategory(savedMember); + saveSubscription(savedMember, savedCategory); + + return savedMember; + } + private Member parseMember(final OAuthMember oAuthMember) { return new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), oAuthMember.getProfileImageUrl(), SocialType.GOOGLE); @@ -90,20 +99,17 @@ private Subscription saveSubscription(final Member savedMember, final Category s } private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member foundMember) { - if (!oAuthTokenRepository.existsByMemberId(foundMember.getId())) { - oAuthTokenRepository.save(new OAuthToken(foundMember, oAuthMember.getRefreshToken())); + if (oAuthTokenRepository.existsByMemberId(foundMember.getId())) { + return oAuthTokenRepository.getByMemberId(foundMember.getId()); } - return oAuthTokenRepository.getByMemberId(foundMember.getId()); + return oAuthTokenRepository.save(new OAuthToken(foundMember, oAuthMember.getRefreshToken())); } public Long extractMemberId(final String accessToken) { tokenProvider.validateToken(accessToken); Long memberId = Long.valueOf(tokenProvider.getPayload(accessToken)); - if (!memberRepository.existsById(memberId)) { - throw new NoSuchMemberException(); - } - + memberRepository.validateExistsById(memberId); return memberId; } } diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java index 0c815569..4bc8dfd2 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java @@ -19,4 +19,10 @@ default Member getByEmail(final String email) { return findByEmail(email) .orElseThrow(NoSuchMemberException::new); } + + default void validateExistsById(final Long id) { + if (!existsById(id)) { + throw new NoSuchMemberException(); + } + } } From 332b227faccc1965ef67318e92495761a8405b59 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 12:39:16 +0900 Subject: [PATCH 016/148] =?UTF-8?q?refactor:=20ColorPicker=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=EC=B2=B4=20=EB=B9=88=20=EB=93=B1=EB=A1=9D=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B8=B0=EC=A1=B4=20=EC=83=81=EC=88=98=EB=A1=9C=20=EC=A0=9C?= =?UTF-8?q?=EA=B3=B5=EB=90=98=EB=A9=B4=20=EA=B5=AC=ED=98=84=EC=B2=B4=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/application/AuthService.java | 20 +++++++++---------- .../subscription/application/ColorPicker.java | 7 +++++++ .../application/RandomColorPicker.java | 16 +++++++++++++++ .../application/SubscriptionService.java | 13 +++++------- .../domain/subscription/domain/Color.java | 4 ++-- .../domain/ColorPickerStrategy.java | 7 ------- .../domain/subscription/domain/ColorTest.java | 11 +++++++--- 7 files changed, 48 insertions(+), 30 deletions(-) create mode 100644 backend/src/main/java/com/allog/dallog/domain/subscription/application/ColorPicker.java create mode 100644 backend/src/main/java/com/allog/dallog/domain/subscription/application/RandomColorPicker.java delete mode 100644 backend/src/main/java/com/allog/dallog/domain/subscription/domain/ColorPickerStrategy.java diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index b6e6ad7c..8a1bf798 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -12,11 +12,10 @@ import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.member.domain.SocialType; +import com.allog.dallog.domain.subscription.application.ColorPicker; import com.allog.dallog.domain.subscription.domain.Color; -import com.allog.dallog.domain.subscription.domain.ColorPickerStrategy; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; -import java.util.concurrent.ThreadLocalRandom; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -25,28 +24,29 @@ public class AuthService { private static final String PERSONAL_CATEGORY_NAME = "내 일정"; - private static final ColorPickerStrategy PICK_RANDOM_STRATEGY = () -> ThreadLocalRandom.current() - .nextInt(Color.values().length); private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; - private final SubscriptionRepository subscriptionRepository; private final OAuthTokenRepository oAuthTokenRepository; + private final SubscriptionRepository subscriptionRepository; private final OAuthUri oAuthUri; private final OAuthClient oAuthClient; private final TokenProvider tokenProvider; + private final ColorPicker colorPicker; public AuthService(final MemberRepository memberRepository, final CategoryRepository categoryRepository, - final SubscriptionRepository subscriptionRepository, - final OAuthTokenRepository oAuthTokenRepository, final OAuthUri oAuthUri, - final OAuthClient oAuthClient, final TokenProvider tokenProvider) { + final OAuthTokenRepository oAuthTokenRepository, + final SubscriptionRepository subscriptionRepository, final OAuthUri oAuthUri, + final OAuthClient oAuthClient, final TokenProvider tokenProvider, + final ColorPicker colorPicker) { this.memberRepository = memberRepository; this.categoryRepository = categoryRepository; - this.subscriptionRepository = subscriptionRepository; this.oAuthTokenRepository = oAuthTokenRepository; + this.subscriptionRepository = subscriptionRepository; this.oAuthUri = oAuthUri; this.oAuthClient = oAuthClient; this.tokenProvider = tokenProvider; + this.colorPicker = colorPicker; } public String generateGoogleLink(final String redirectUri) { @@ -94,7 +94,7 @@ private Category saveCategory(final Member savedMember) { } private Subscription saveSubscription(final Member savedMember, final Category savedCategory) { - Color randomColor = Color.pickAny(PICK_RANDOM_STRATEGY); + Color randomColor = Color.pick(colorPicker.pickNumber()); return subscriptionRepository.save(new Subscription(savedMember, savedCategory, randomColor)); } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/ColorPicker.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/ColorPicker.java new file mode 100644 index 00000000..1726d608 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/ColorPicker.java @@ -0,0 +1,7 @@ +package com.allog.dallog.domain.subscription.application; + +@FunctionalInterface +public interface ColorPicker { + + int pickNumber(); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/RandomColorPicker.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/RandomColorPicker.java new file mode 100644 index 00000000..4874072f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/RandomColorPicker.java @@ -0,0 +1,16 @@ +package com.allog.dallog.domain.subscription.application; + +import com.allog.dallog.domain.subscription.domain.Color; +import java.util.concurrent.ThreadLocalRandom; +import org.springframework.stereotype.Component; + +@Component +public class RandomColorPicker implements ColorPicker { + + @Override + public int pickNumber() { + ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current(); + Color[] colors = Color.values(); + return threadLocalRandom.nextInt(colors.length); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 85b3f064..27a1b65c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -8,7 +8,6 @@ import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.member.exception.NoSuchMemberException; import com.allog.dallog.domain.subscription.domain.Color; -import com.allog.dallog.domain.subscription.domain.ColorPickerStrategy; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.dto.request.SubscriptionUpdateRequest; @@ -17,7 +16,6 @@ import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; -import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -26,19 +24,18 @@ @Service public class SubscriptionService { - private static final ColorPickerStrategy PICK_RANDOM_STRATEGY - = () -> ThreadLocalRandom.current().nextInt(Color.values().length); - private final SubscriptionRepository subscriptionRepository; private final MemberRepository memberRepository; private final CategoryRepository categoryRepository; + private final ColorPicker colorPicker; public SubscriptionService(final SubscriptionRepository subscriptionRepository, - final MemberRepository memberRepository, - final CategoryRepository categoryRepository) { + final MemberRepository memberRepository, final CategoryRepository categoryRepository, + final ColorPicker colorPicker) { this.subscriptionRepository = subscriptionRepository; this.memberRepository = memberRepository; this.categoryRepository = categoryRepository; + this.colorPicker = colorPicker; } @Transactional @@ -51,7 +48,7 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { .orElseThrow(NoSuchCategoryException::new); validatePermission(memberId, category); - Color color = Color.pickAny(PICK_RANDOM_STRATEGY); + Color color = Color.pick(colorPicker.pickNumber()); Subscription subscription = subscriptionRepository.save(new Subscription(member, category, color)); return new SubscriptionResponse(subscription); } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java index 7dabec95..6a6f9f70 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java @@ -36,8 +36,8 @@ public enum Color { this.colorCode = colorCode; } - public static Color pickAny(ColorPickerStrategy strategy) { - return Color.values()[strategy.pickNumber()]; + public static Color pick(int index) { + return Color.values()[index]; } public static Color from(final String colorCode) { diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/ColorPickerStrategy.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/ColorPickerStrategy.java deleted file mode 100644 index 3365655f..00000000 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/ColorPickerStrategy.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.allog.dallog.domain.subscription.domain; - -@FunctionalInterface -public interface ColorPickerStrategy { - - int pickNumber(); -} diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/ColorTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/ColorTest.java index cf6aeae8..fd05a69f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/ColorTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/ColorTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import com.allog.dallog.domain.subscription.application.ColorPicker; import com.allog.dallog.domain.subscription.exception.InvalidSubscriptionException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -17,10 +18,14 @@ class ColorTest { @Test void 랜덤으로_색상을_가져온다() { // given - ColorPickerStrategy testStrategy = () -> 0; + ColorPicker colorPicker = () -> 0; + int randomIndex = colorPicker.pickNumber(); - // when & then - assertThat(Color.pickAny(testStrategy)).isEqualTo(Color.COLOR_1); + // when + Color actual = Color.pick(randomIndex); + + // then + assertThat(actual).isEqualTo(Color.COLOR_1); } @DisplayName("color code에 맞는 색상을 가져온다.") From 29b9c3876c9a0588b0b50e9371381f8e1f882bb9 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 12:59:20 +0900 Subject: [PATCH 017/148] =?UTF-8?q?test:=20default=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/domain/MemberRepositoryTest.java | 38 +++++++++++++------ .../domain/SubscriptionRepositoryTest.java | 13 +++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberRepositoryTest.java index e63ffef3..2cdd1a04 100644 --- a/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberRepositoryTest.java @@ -3,10 +3,10 @@ import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; import static com.allog.dallog.common.fixtures.MemberFixtures.파랑_이메일; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.allog.dallog.common.annotation.RepositoryTest; -import com.allog.dallog.domain.category.domain.CategoryRepository; -import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; +import com.allog.dallog.domain.member.exception.NoSuchMemberException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -16,11 +16,15 @@ class MemberRepositoryTest extends RepositoryTest { @Autowired private MemberRepository memberRepository; - @Autowired - private CategoryRepository categoryRepository; + @DisplayName("중복된 이메일이 존재하는 경우 true를 반환한다.") + @Test + void 중복된_이메일이_존재하는_경우_true를_반환한다() { + // given + memberRepository.save(파랑()); - @Autowired - private SubscriptionRepository subscriptionRepository; + // when & then + assertThat(memberRepository.existsByEmail(파랑_이메일)).isTrue(); + } @DisplayName("이메일을 통해 회원을 찾는다.") @Test @@ -29,19 +33,31 @@ class MemberRepositoryTest extends RepositoryTest { Member 파랑 = memberRepository.save(파랑()); // when - Member actual = memberRepository.findByEmail(파랑_이메일).get(); + Member actual = memberRepository.getByEmail(파랑_이메일); // then assertThat(actual.getId()).isEqualTo(파랑.getId()); } - @DisplayName("중복된 이메일이 존재하는 경우 true를 반환한다.") + @DisplayName("존재하지 않는 email을 조회할 경우 예외를 던진다.") @Test - void 중복된_이메일이_존재하는_경우_true를_반환한다() { + void 존재하지_않는_email을_조회할_경우_예외를_던진다() { // given - memberRepository.save(파랑()); + String email = "dev.hyeonic@gmail.com"; + + // given & when & then + assertThatThrownBy(() -> memberRepository.getByEmail(email)) + .isInstanceOf(NoSuchMemberException.class); + } + + @DisplayName("존재하지 않는 id이면 예외를 던진다.") + @Test + void 존재하지_않는_id이면_예외를_던진다() { + // given + Long id = 0L; // when & then - assertThat(memberRepository.existsByEmail(파랑_이메일)).isTrue(); + assertThatThrownBy(() -> memberRepository.validateExistsById(id)) + .isInstanceOf(NoSuchMemberException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java index 2061e292..83221971 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java @@ -11,12 +11,14 @@ import static com.allog.dallog.common.fixtures.SubscriptionFixtures.색상2_구독; import static com.allog.dallog.common.fixtures.SubscriptionFixtures.색상3_구독; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.allog.dallog.common.annotation.RepositoryTest; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; +import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -162,4 +164,15 @@ class SubscriptionRepositoryTest extends RepositoryTest { // then assertThat(subscriptionRepository.findAll()).hasSize(0); } + + @DisplayName("존재하지 않는 id인 경우 예외를 던진다.") + @Test + void 존재하지_않는_id인_경우_예외를_던진다() { + // given + Long id = 0L; + + // when & then + assertThatThrownBy(() -> subscriptionRepository.getById(id)) + .isInstanceOf(NoSuchSubscriptionException.class); + } } From 4ff4e5f1d8ede0605a9a73f311cc592985a48da5 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 16:48:37 +0900 Subject: [PATCH 018/148] =?UTF-8?q?refactor:=20=EB=A9=94=EC=8B=9C=EB=93=9C?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EC=83=9D=EC=84=B1=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=EC=97=90=20=EB=A7=9E=EC=B6=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/allog/dallog/domain/auth/application/AuthService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index 8a1bf798..3885f428 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -77,14 +77,14 @@ private Member findMember(final OAuthMember oAuthMember) { } private Member saveMember(final OAuthMember oAuthMember) { - Member savedMember = memberRepository.save(parseMember(oAuthMember)); + Member savedMember = memberRepository.save(toMember(oAuthMember)); Category savedCategory = saveCategory(savedMember); saveSubscription(savedMember, savedCategory); return savedMember; } - private Member parseMember(final OAuthMember oAuthMember) { + private Member toMember(final OAuthMember oAuthMember) { return new Member(oAuthMember.getEmail(), oAuthMember.getDisplayName(), oAuthMember.getProfileImageUrl(), SocialType.GOOGLE); } From 5f7eaaedd60e703ec19b81b11c04b485cfb26623 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Wed, 14 Sep 2022 16:49:52 +0900 Subject: [PATCH 019/148] =?UTF-8?q?refactor:=20=EA=B0=80=EB=8F=85=EC=84=B1?= =?UTF-8?q?=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EC=88=9C=EC=84=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allog/dallog/domain/auth/application/AuthService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java index 3885f428..dd68ba4d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -69,11 +69,11 @@ public TokenResponse generateToken(final TokenRequest tokenRequest) { } private Member findMember(final OAuthMember oAuthMember) { - if (!memberRepository.existsByEmail(oAuthMember.getEmail())) { - return saveMember(oAuthMember); + if (memberRepository.existsByEmail(oAuthMember.getEmail())) { + return memberRepository.getByEmail(oAuthMember.getEmail()); } - return memberRepository.getByEmail(oAuthMember.getEmail()); + return saveMember(oAuthMember); } private Member saveMember(final OAuthMember oAuthMember) { From 50079d829a0b90e1a35c48b4a051dd18770e3ccc Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 16:44:26 +0900 Subject: [PATCH 020/148] =?UTF-8?q?refactor:=20SubscriptionController?= =?UTF-8?q?=EC=9D=98=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../allog/dallog/presentation/SubscriptionController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java index 2cdc5cc2..a728c223 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java @@ -37,7 +37,7 @@ public ResponseEntity save(@AuthenticationPrincipal final } @GetMapping("/subscriptions") - public ResponseEntity findMine(@AuthenticationPrincipal final LoginMember loginMember) { + public ResponseEntity findByMemberId(@AuthenticationPrincipal final LoginMember loginMember) { SubscriptionsResponse response = subscriptionService.findByMemberId(loginMember.getId()); return ResponseEntity.ok(response); } @@ -51,8 +51,8 @@ public ResponseEntity update(@AuthenticationPrincipal final LoginMember lo } @DeleteMapping("/subscriptions/{subscriptionId}") - public ResponseEntity deleteById(@AuthenticationPrincipal final LoginMember loginMember, - @PathVariable final Long subscriptionId) { + public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long subscriptionId) { subscriptionService.deleteById(subscriptionId, loginMember.getId()); return ResponseEntity.noContent().build(); } From 4f85f1161ed01c3a46e7109c3653ac77b6996ecd Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 16:58:58 +0900 Subject: [PATCH 021/148] =?UTF-8?q?refactor:=20develop=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=EC=99=80=20=EC=B6=A9=EB=8F=8C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=B6=A9=EB=8F=8C=ED=95=B4?= =?UTF-8?q?=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SubscriptionService.java | 13 +++---------- .../subscription/domain/SubscriptionRepository.java | 7 +++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 27a1b65c..da3f9fa7 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -13,7 +13,6 @@ import com.allog.dallog.domain.subscription.dto.request.SubscriptionUpdateRequest; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; -import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import java.util.stream.Collectors; @@ -40,10 +39,10 @@ public SubscriptionService(final SubscriptionRepository subscriptionRepository, @Transactional public SubscriptionResponse save(final Long memberId, final Long categoryId) { - validateAlreadyExists(memberId, categoryId); + subscriptionRepository.validateExistsSubscription(memberId, categoryId); + + Member member = memberRepository.getById(memberId); - Member member = memberRepository.findById(memberId) - .orElseThrow(NoSuchMemberException::new); Category category = categoryRepository.findById(categoryId) .orElseThrow(NoSuchCategoryException::new); validatePermission(memberId, category); @@ -53,12 +52,6 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { return new SubscriptionResponse(subscription); } - private void validateAlreadyExists(final Long memberId, final Long categoryId) { - if (subscriptionRepository.existsByMemberIdAndCategoryId(memberId, categoryId)) { - throw new ExistSubscriptionException(); - } - } - private void validatePermission(final Long memberId, final Category category) { if (category.isPersonal() && !category.isCreator(memberId)) { throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index eca517cb..cba0fd8f 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.subscription.domain; +import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; @@ -20,4 +21,10 @@ default Subscription getById(final Long id) { return findById(id) .orElseThrow(NoSuchSubscriptionException::new); } + + default void validateExistsSubscription(final Long memberId, final Long categoryId) { + if (this.existsByMemberIdAndCategoryId(memberId, categoryId)) { + throw new ExistSubscriptionException(); + } + } } From bf3078638f343aee2d1782af099a84f6302d5381 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 17:37:08 +0900 Subject: [PATCH 022/148] =?UTF-8?q?refactor:=20SubscriptionService.save()?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/domain/Category.java | 7 +++++ .../category/domain/CategoryRepository.java | 9 +++++- .../dallog/domain/member/domain/Member.java | 30 +++++++++++++++++-- .../application/SubscriptionService.java | 18 +++-------- .../dto/response/SubscriptionResponse.java | 8 ++--- 5 files changed, 50 insertions(+), 22 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index e4cc2867..f31f6a06 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -3,6 +3,7 @@ import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.domain.Member; @@ -79,6 +80,12 @@ private void validateNameLength(final String name) { } } + public void validateCanSubscribe(final Member member) { + if (this.categoryType == PERSONAL && !this.member.isCreator(member)) { + throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); + } + } + public boolean isCreator(final Long memberId) { return Objects.equals(member.getId(), memberId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 203b2279..9c21ec47 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.category.domain; +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; @@ -11,7 +12,8 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c " + "FROM Category c " + "WHERE c.name LIKE %:name% AND c.categoryType = :categoryType") - Slice findByNameContainingAndCategoryType(final String name, final CategoryType categoryType, final Pageable pageable); + Slice findByNameContainingAndCategoryType(final String name, final CategoryType categoryType, + final Pageable pageable); @Query("SELECT c " + "FROM Category c " @@ -23,4 +25,9 @@ public interface CategoryRepository extends JpaRepository { boolean existsByIdAndMemberId(Long id, Long memberId); void deleteByMemberId(Long memberId); + + default Category getById(final Long id) { + return this.findById(id) + .orElseThrow(NoSuchCategoryException::new); + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index 17bf3518..e3810a1b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -2,6 +2,7 @@ import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.exception.InvalidMemberException; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.persistence.Column; @@ -52,6 +53,11 @@ public Member(final String email, final String displayName, final String profile this.socialType = socialType; } + public void change(final String displayName) { + validateDisplayName(displayName); + this.displayName = displayName; + } + private void validateEmail(final String email) { Matcher matcher = EMAIL_PATTERN.matcher(email); if (!matcher.matches()) { @@ -65,9 +71,8 @@ private void validateDisplayName(final String displayName) { } } - public void change(final String displayName) { - validateDisplayName(displayName); - this.displayName = displayName; + public boolean isCreator(final Member otherMember) { + return this.equals(otherMember); } public Long getId() { @@ -89,4 +94,23 @@ public String getProfileImageUrl() { public SocialType getSocialType() { return socialType; } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Member member = (Member) o; + return Objects.equals(id, member.id) && Objects.equals(email, member.email) + && Objects.equals(displayName, member.displayName) && Objects.equals(profileImageUrl, + member.profileImageUrl) && socialType == member.socialType; + } + + @Override + public int hashCode() { + return Objects.hash(id, email, displayName, profileImageUrl, socialType); + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index da3f9fa7..f1049f46 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -3,10 +3,8 @@ import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; -import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; -import com.allog.dallog.domain.member.exception.NoSuchMemberException; import com.allog.dallog.domain.subscription.domain.Color; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; @@ -42,20 +40,12 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { subscriptionRepository.validateExistsSubscription(memberId, categoryId); Member member = memberRepository.getById(memberId); - - Category category = categoryRepository.findById(categoryId) - .orElseThrow(NoSuchCategoryException::new); - validatePermission(memberId, category); + Category category = categoryRepository.getById(categoryId); + category.validateCanSubscribe(member); Color color = Color.pick(colorPicker.pickNumber()); - Subscription subscription = subscriptionRepository.save(new Subscription(member, category, color)); - return new SubscriptionResponse(subscription); - } - - private void validatePermission(final Long memberId, final Category category) { - if (category.isPersonal() && !category.isCreator(memberId)) { - throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); - } + Subscription savedSubscription = subscriptionRepository.save(new Subscription(member, category, color)); + return new SubscriptionResponse(savedSubscription); } public SubscriptionsResponse findByMemberId(final Long memberId) { diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java index 6a9fff15..48907de6 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java @@ -28,6 +28,10 @@ public SubscriptionResponse(final Long id, final CategoryResponse category, fina this.checked = checked; } + public boolean isChecked() { + return checked; + } + public Long getId() { return id; } @@ -39,8 +43,4 @@ public CategoryResponse getCategory() { public String getColorCode() { return colorCode; } - - public boolean isChecked() { - return checked; - } } From a568c651202e8f00cc19da547b6fa7720cf77e1c Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 17:50:45 +0900 Subject: [PATCH 023/148] =?UTF-8?q?refactor:=20SubscriptionService.findByM?= =?UTF-8?q?emberId()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SubscriptionService.java | 17 +++++------------ .../domain/SubscriptionRepository.java | 2 +- .../domain/SubscriptionRepositoryTest.java | 4 ++-- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index f1049f46..d2630fe6 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -11,7 +11,6 @@ import com.allog.dallog.domain.subscription.dto.request.SubscriptionUpdateRequest; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; -import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import java.util.stream.Collectors; import org.springframework.stereotype.Service; @@ -49,7 +48,7 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { } public SubscriptionsResponse findByMemberId(final Long memberId) { - List subscriptions = subscriptionRepository.findByMemberId(memberId); + List subscriptions = subscriptionRepository.getByMemberId(memberId); List subscriptionResponses = subscriptions.stream() .map(SubscriptionResponse::new) @@ -59,8 +58,7 @@ public SubscriptionsResponse findByMemberId(final Long memberId) { } public SubscriptionResponse findById(final Long id) { - Subscription subscription = getSubscription(id); - + Subscription subscription = subscriptionRepository.getById(id); return new SubscriptionResponse(subscription); } @@ -72,25 +70,20 @@ public List findByCategoryId(final Long categoryId) { } public List getAllByMemberId(final Long memberId) { - return subscriptionRepository.findByMemberId(memberId); + return subscriptionRepository.getByMemberId(memberId); } @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { validateSubscriptionPermission(id, memberId); - Subscription subscription = getSubscription(id); + Subscription subscription = subscriptionRepository.getById(id); subscription.change(request.getColor(), request.isChecked()); } - private Subscription getSubscription(final Long id) { - return subscriptionRepository.findById(id) - .orElseThrow(NoSuchSubscriptionException::new); - } - @Transactional public void deleteById(final Long id, final Long memberId) { - Subscription subscription = getSubscription(id); + Subscription subscription = subscriptionRepository.getById(id); validateSubscriptionPermission(id, memberId); validateCategoryCreator(subscription.getCategory(), memberId); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index cba0fd8f..63a40bf0 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -9,7 +9,7 @@ public interface SubscriptionRepository extends JpaRepository findByMemberId(final Long memberId); + List getByMemberId(final Long memberId); List findByCategoryId(final Long categoryId); diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java index 83221971..2dc32726 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java @@ -83,7 +83,7 @@ class SubscriptionRepositoryTest extends RepositoryTest { subscriptionRepository.save(색상3_구독(후디, FE_일정)); // when - List subscriptions = subscriptionRepository.findByMemberId(후디.getId()); + List subscriptions = subscriptionRepository.getByMemberId(후디.getId()); // then assertThat(subscriptions).hasSize(3); @@ -96,7 +96,7 @@ class SubscriptionRepositoryTest extends RepositoryTest { Member 관리자 = memberRepository.save(관리자()); // when - List subscriptions = subscriptionRepository.findByMemberId(관리자.getId()); + List subscriptions = subscriptionRepository.getByMemberId(관리자.getId()); // then assertThat(subscriptions).isEmpty(); From 7166f4391ade0b694b6d5ff77de2aef6db102f22 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 17:52:44 +0900 Subject: [PATCH 024/148] =?UTF-8?q?refactor:=20SubscriptionService.findByC?= =?UTF-8?q?ategoryId()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=EC=A1=B0?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/subscription/application/SubscriptionService.java | 2 +- .../domain/subscription/domain/SubscriptionRepository.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index d2630fe6..3b913a2a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -63,7 +63,7 @@ public SubscriptionResponse findById(final Long id) { } public List findByCategoryId(final Long categoryId) { - return subscriptionRepository.findByCategoryId(categoryId) + return subscriptionRepository.getByCategoryId(categoryId) .stream() .map(SubscriptionResponse::new) .collect(Collectors.toList()); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index 63a40bf0..f2a98915 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -11,7 +11,7 @@ public interface SubscriptionRepository extends JpaRepository getByMemberId(final Long memberId); - List findByCategoryId(final Long categoryId); + List getByCategoryId(final Long categoryId); boolean existsByIdAndMemberId(final Long id, final Long memberId); From 17f07dbfa71e026a77ef94225e60827a17ab39ac Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 17:54:30 +0900 Subject: [PATCH 025/148] =?UTF-8?q?refactor:=20SubscriptionService.getAllB?= =?UTF-8?q?yMemberId()=EC=97=90=20=EB=8C=80=ED=95=B4=EC=84=9C=20todo=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/subscription/application/SubscriptionService.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 3b913a2a..60a3ddbc 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -69,6 +69,7 @@ public List findByCategoryId(final Long categoryId) { .collect(Collectors.toList()); } + // TODO: 상위 Service인 CalanderService에서만 사용하는 메서드입니다. 삭제 예정 public List getAllByMemberId(final Long memberId) { return subscriptionRepository.getByMemberId(memberId); } From e2803091ed7f1443c9159f2adcde7609b3963f4a Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 18:10:00 +0900 Subject: [PATCH 026/148] =?UTF-8?q?refactor:=20SubscriptionService.save()?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/domain/Category.java | 6 ------ .../dallog/domain/member/domain/Member.java | 8 ++++++-- .../application/SubscriptionService.java | 19 ++++++++++--------- .../subscription/domain/Subscription.java | 4 ++++ .../domain/SubscriptionRepository.java | 8 ++++---- 5 files changed, 24 insertions(+), 21 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index f31f6a06..c4314b0c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -80,12 +80,6 @@ private void validateNameLength(final String name) { } } - public void validateCanSubscribe(final Member member) { - if (this.categoryType == PERSONAL && !this.member.isCreator(member)) { - throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); - } - } - public boolean isCreator(final Long memberId) { return Objects.equals(member.getId(), memberId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index e3810a1b..2d75d5d4 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -1,5 +1,7 @@ package com.allog.dallog.domain.member.domain; +import com.allog.dallog.domain.auth.exception.NoPermissionException; +import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.exception.InvalidMemberException; import java.util.Objects; @@ -71,8 +73,10 @@ private void validateDisplayName(final String displayName) { } } - public boolean isCreator(final Member otherMember) { - return this.equals(otherMember); + public void validateCanSubscribe(final Category category) { + if (category.isPersonal() && !this.equals(category.getMember())) { + throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); + } } public Long getId() { diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 60a3ddbc..6fb95732 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -40,7 +40,7 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { Member member = memberRepository.getById(memberId); Category category = categoryRepository.getById(categoryId); - category.validateCanSubscribe(member); + member.validateCanSubscribe(category); Color color = Color.pick(colorPicker.pickNumber()); Subscription savedSubscription = subscriptionRepository.save(new Subscription(member, category, color)); @@ -76,12 +76,19 @@ public List getAllByMemberId(final Long memberId) { @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { - validateSubscriptionPermission(id, memberId); - Subscription subscription = subscriptionRepository.getById(id); + Member member = memberRepository.getById(memberId); + subscription.validateCreator(member); + subscription.change(request.getColor(), request.isChecked()); } + private void validateSubscriptionPermission(final Long id, final Long memberId) { + if (!subscriptionRepository.existsByIdAndMemberId(id, memberId)) { + throw new NoPermissionException(); + } + } + @Transactional public void deleteById(final Long id, final Long memberId) { Subscription subscription = subscriptionRepository.getById(id); @@ -92,12 +99,6 @@ public void deleteById(final Long id, final Long memberId) { subscriptionRepository.deleteById(id); } - private void validateSubscriptionPermission(final Long id, final Long memberId) { - if (!subscriptionRepository.existsByIdAndMemberId(id, memberId)) { - throw new NoPermissionException(); - } - } - private void validateCategoryCreator(final Category category, final Long memberId) { if (category.isCreator(memberId)) { throw new NoPermissionException("내가 만든 카테고리는 구독 취소 할 수 없습니다."); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java index 6379880d..906d94cc 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -54,6 +54,10 @@ public void change(final Color color, final boolean checked) { this.checked = checked; } + public void validateCreator(final Member member) { + + } + public Long getId() { return id; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index f2a98915..c030ed83 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -9,14 +9,14 @@ public interface SubscriptionRepository extends JpaRepository getByMemberId(final Long memberId); - - List getByCategoryId(final Long categoryId); - boolean existsByIdAndMemberId(final Long id, final Long memberId); void deleteByCategoryIdIn(final List categoryIds); + List getByMemberId(final Long memberId); + + List getByCategoryId(final Long categoryId); + default Subscription getById(final Long id) { return findById(id) .orElseThrow(NoSuchSubscriptionException::new); From 862b301fccfc4bce0834c9e985093177f1510ac8 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 21:25:00 +0900 Subject: [PATCH 027/148] =?UTF-8?q?refactor:=20SubscriptionService.update(?= =?UTF-8?q?),=20SubscriptionService.delete()=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 3 ++- .../domain/category/domain/Category.java | 5 ++-- .../dallog/domain/member/domain/Member.java | 20 +--------------- .../application/SubscriptionService.java | 24 ++++--------------- .../subscription/domain/Subscription.java | 11 ++++++++- .../domain/SubscriptionRepository.java | 2 +- .../presentation/SubscriptionController.java | 2 +- .../domain/category/domain/CategoryTest.java | 4 +++- .../application/SubscriptionServiceTest.java | 6 ++--- .../SubscriptionControllerTest.java | 4 ++-- 10 files changed, 29 insertions(+), 52 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index 898ca2aa..144ca55c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -157,7 +157,8 @@ public void deleteByMemberId(final Long memberId) { } public void validateCreatorBy(final Long memberId, final Category category) { - if (!category.isCreator(memberId)) { + Member member = memberRepository.getById(memberId); + if (!category.isCreator(member)) { throw new NoPermissionException(); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index c4314b0c..e5f819fa 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -3,7 +3,6 @@ import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; -import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.domain.Member; @@ -80,8 +79,8 @@ private void validateNameLength(final String name) { } } - public boolean isCreator(final Long memberId) { - return Objects.equals(member.getId(), memberId); + public boolean isCreator(final Member creator) { + return this.member.equals(creator); } public boolean isPersonal() { diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index 2d75d5d4..b436b6d5 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -4,6 +4,7 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.exception.InvalidMemberException; +import com.allog.dallog.domain.subscription.domain.Subscription; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -98,23 +99,4 @@ public String getProfileImageUrl() { public SocialType getSocialType() { return socialType; } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - Member member = (Member) o; - return Objects.equals(id, member.id) && Objects.equals(email, member.email) - && Objects.equals(displayName, member.displayName) && Objects.equals(profileImageUrl, - member.profileImageUrl) && socialType == member.socialType; - } - - @Override - public int hashCode() { - return Objects.hash(id, email, displayName, profileImageUrl, socialType); - } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 6fb95732..fa6151ca 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -1,6 +1,5 @@ package com.allog.dallog.domain.subscription.application; -import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.member.domain.Member; @@ -78,30 +77,15 @@ public List getAllByMemberId(final Long memberId) { public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { Subscription subscription = subscriptionRepository.getById(id); Member member = memberRepository.getById(memberId); - subscription.validateCreator(member); - + subscription.validateCanEditBy(member); subscription.change(request.getColor(), request.isChecked()); } - private void validateSubscriptionPermission(final Long id, final Long memberId) { - if (!subscriptionRepository.existsByIdAndMemberId(id, memberId)) { - throw new NoPermissionException(); - } - } - @Transactional - public void deleteById(final Long id, final Long memberId) { + public void delete(final Long id, final Long memberId) { Subscription subscription = subscriptionRepository.getById(id); - - validateSubscriptionPermission(id, memberId); - validateCategoryCreator(subscription.getCategory(), memberId); - + Member member = memberRepository.getById(memberId); + subscription.validateCanDeleteBy(member); subscriptionRepository.deleteById(id); } - - private void validateCategoryCreator(final Category category, final Long memberId) { - if (category.isCreator(memberId)) { - throw new NoPermissionException("내가 만든 카테고리는 구독 취소 할 수 없습니다."); - } - } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java index 906d94cc..63941dbd 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.subscription.domain; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.domain.Member; @@ -54,8 +55,16 @@ public void change(final Color color, final boolean checked) { this.checked = checked; } - public void validateCreator(final Member member) { + public void validateCanEditBy(final Member member) { + if (!this.member.equals(member)) { + throw new NoPermissionException(); + } + } + public void validateCanDeleteBy(final Member member) { + if (category.isCreator(member)) { + throw new NoPermissionException("내가 만든 카테고리는 구독 취소 할 수 없습니다."); + } } public Long getId() { diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index c030ed83..dfcb644e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -23,7 +23,7 @@ default Subscription getById(final Long id) { } default void validateExistsSubscription(final Long memberId, final Long categoryId) { - if (this.existsByMemberIdAndCategoryId(memberId, categoryId)) { + if (existsByMemberIdAndCategoryId(memberId, categoryId)) { throw new ExistSubscriptionException(); } } diff --git a/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java index a728c223..dd0e9506 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java @@ -53,7 +53,7 @@ public ResponseEntity update(@AuthenticationPrincipal final LoginMember lo @DeleteMapping("/subscriptions/{subscriptionId}") public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, @PathVariable final Long subscriptionId) { - subscriptionService.deleteById(subscriptionId, loginMember.getId()); + subscriptionService.delete(subscriptionId, loginMember.getId()); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java index 815935db..6880bd66 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.allog.dallog.domain.category.exception.InvalidCategoryException; +import com.allog.dallog.domain.member.domain.Member; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -63,10 +64,11 @@ class CategoryTest { @Test void 제공된_멤버의_ID와_카테고리를_생성한_멤버의_ID가_일치하지_않으면_false를_반환한다() { // given + Member 관리자 = 관리자(); Category BE_일정 = BE_일정(관리자()); // when - boolean actual = BE_일정.isCreator(999L); + boolean actual = BE_일정.isCreator(관리자); // then assertThat(actual).isFalse(); diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index 53da27aa..5235d5e8 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -180,7 +180,7 @@ class SubscriptionServiceTest extends ServiceTest { subscriptionService.save(후디_id, FE_일정.getId()); // when - subscriptionService.deleteById(response.getId(), 후디_id); + subscriptionService.delete(response.getId(), 후디_id); // then assertThat(subscriptionService.findByMemberId(후디_id).getSubscriptions()).hasSize(3); @@ -197,7 +197,7 @@ class SubscriptionServiceTest extends ServiceTest { SubscriptionResponse 공통_일정_구독 = subscriptionService.save(파랑_id, 공통_일정.getId()); // when & then - assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자_id)) + assertThatThrownBy(() -> subscriptionService.delete(공통_일정_구독.getId(), 관리자_id)) .isInstanceOf(NoPermissionException.class); } @@ -211,7 +211,7 @@ class SubscriptionServiceTest extends ServiceTest { SubscriptionResponse 공통_일정_구독 = subscriptionService.save(관리자_id, 공통_일정.getId()); // when & then - assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자_id)) + assertThatThrownBy(() -> subscriptionService.delete(공통_일정_구독.getId(), 관리자_id)) .isInstanceOf(NoPermissionException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java index 06bb1753..8f07d045 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java @@ -234,7 +234,7 @@ class SubscriptionControllerTest extends ControllerTest { given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); willDoNothing().given(subscriptionService) - .deleteById(색상1_구독_응답.getId(), 매트_응답.getId()); + .delete(색상1_구독_응답.getId(), 매트_응답.getId()); // when & then mockMvc.perform(delete("/api/members/me/subscriptions/{subscriptionId}", 색상1_구독_응답.getId()) @@ -261,7 +261,7 @@ class SubscriptionControllerTest extends ControllerTest { willThrow(new NoPermissionException()) .willDoNothing() .given(subscriptionService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(delete("/api/members/me/subscriptions/{subscriptionId}", 1L) From 412e8895c06975bc524b4bc88a8331adf07775e4 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 23:46:53 +0900 Subject: [PATCH 028/148] =?UTF-8?q?refactor:=20Subscription=20=EB=82=B4?= =?UTF-8?q?=EB=B6=80=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/allog/dallog/domain/category/domain/Category.java | 8 +++++++- .../com/allog/dallog/domain/member/domain/Member.java | 6 ------ .../subscription/application/SubscriptionService.java | 6 +++--- .../dallog/domain/subscription/domain/Subscription.java | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index e5f819fa..b879012d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -3,10 +3,10 @@ import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.domain.Member; -import java.util.Objects; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.EnumType; @@ -79,6 +79,12 @@ private void validateNameLength(final String name) { } } + public void validateSubscriptionPossible(final Member member) { + if (this.categoryType == PERSONAL && !isCreator(member)) { + throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); + } + } + public boolean isCreator(final Member creator) { return this.member.equals(creator); } diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index b436b6d5..8cc4480a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -74,12 +74,6 @@ private void validateDisplayName(final String displayName) { } } - public void validateCanSubscribe(final Category category) { - if (category.isPersonal() && !this.equals(category.getMember())) { - throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); - } - } - public Long getId() { return id; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index fa6151ca..6a613aa0 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -39,7 +39,7 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { Member member = memberRepository.getById(memberId); Category category = categoryRepository.getById(categoryId); - member.validateCanSubscribe(category); + category.validateSubscriptionPossible(member); Color color = Color.pick(colorPicker.pickNumber()); Subscription savedSubscription = subscriptionRepository.save(new Subscription(member, category, color)); @@ -77,7 +77,7 @@ public List getAllByMemberId(final Long memberId) { public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { Subscription subscription = subscriptionRepository.getById(id); Member member = memberRepository.getById(memberId); - subscription.validateCanEditBy(member); + subscription.validateUpdatePossible(member); subscription.change(request.getColor(), request.isChecked()); } @@ -85,7 +85,7 @@ public void update(final Long id, final Long memberId, final SubscriptionUpdateR public void delete(final Long id, final Long memberId) { Subscription subscription = subscriptionRepository.getById(id); Member member = memberRepository.getById(memberId); - subscription.validateCanDeleteBy(member); + subscription.validateDeletePossible(member); subscriptionRepository.deleteById(id); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java index 63941dbd..87ab3a11 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -55,13 +55,13 @@ public void change(final Color color, final boolean checked) { this.checked = checked; } - public void validateCanEditBy(final Member member) { + public void validateUpdatePossible(final Member member) { if (!this.member.equals(member)) { throw new NoPermissionException(); } } - public void validateCanDeleteBy(final Member member) { + public void validateDeletePossible(final Member member) { if (category.isCreator(member)) { throw new NoPermissionException("내가 만든 카테고리는 구독 취소 할 수 없습니다."); } From 97fffde2b6a63bbd8b7089c8b13945dd52474519 Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 14:59:03 +0900 Subject: [PATCH 029/148] =?UTF-8?q?refactor:=20Category=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=9D=98=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20thi?= =?UTF-8?q?s=EB=AC=B8=20=EC=83=9D=EB=9E=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/allog/dallog/domain/category/domain/Category.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index b879012d..18d2b05c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -86,7 +86,7 @@ public void validateSubscriptionPossible(final Member member) { } public boolean isCreator(final Member creator) { - return this.member.equals(creator); + return member.equals(creator); } public boolean isPersonal() { From 163a46bb0735a8348b7ae41e3a4ff825595e5850 Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 16:11:38 +0900 Subject: [PATCH 030/148] =?UTF-8?q?refactor:=20Subscription=EC=9D=98=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/application/CategoryService.java | 3 +-- .../allog/dallog/domain/category/domain/Category.java | 6 +++--- .../com/allog/dallog/domain/member/domain/Member.java | 7 ++++--- .../subscription/application/SubscriptionService.java | 8 +++----- .../domain/subscription/domain/Subscription.java | 10 ++-------- .../subscription/domain/SubscriptionRepository.java | 9 ++++++++- .../dallog/domain/category/domain/CategoryTest.java | 4 ++-- 7 files changed, 23 insertions(+), 24 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index 144ca55c..23885434 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -157,8 +157,7 @@ public void deleteByMemberId(final Long memberId) { } public void validateCreatorBy(final Long memberId, final Category category) { - Member member = memberRepository.getById(memberId); - if (!category.isCreator(member)) { + if (!category.isCreatorId(memberId)) { throw new NoPermissionException(); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index 18d2b05c..b3d58448 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -80,13 +80,13 @@ private void validateNameLength(final String name) { } public void validateSubscriptionPossible(final Member member) { - if (this.categoryType == PERSONAL && !isCreator(member)) { + if (this.categoryType == PERSONAL && !isCreatorId(member.getId())) { throw new NoPermissionException("구독 권한이 없는 카테고리입니다."); } } - public boolean isCreator(final Member creator) { - return member.equals(creator); + public boolean isCreatorId(final Long creatorId) { + return member.hasSameId(creatorId); } public boolean isPersonal() { diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index 8cc4480a..51bf52ec 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -1,10 +1,7 @@ package com.allog.dallog.domain.member.domain; -import com.allog.dallog.domain.auth.exception.NoPermissionException; -import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; import com.allog.dallog.domain.member.exception.InvalidMemberException; -import com.allog.dallog.domain.subscription.domain.Subscription; import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -74,6 +71,10 @@ private void validateDisplayName(final String displayName) { } } + public boolean hasSameId(final Long memberId) { + return Objects.equals(this.id, memberId); + } + public Long getId() { return id; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 6a613aa0..6c1aad93 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -35,7 +35,7 @@ public SubscriptionService(final SubscriptionRepository subscriptionRepository, @Transactional public SubscriptionResponse save(final Long memberId, final Long categoryId) { - subscriptionRepository.validateExistsSubscription(memberId, categoryId); + subscriptionRepository.validateNotExistsByMemberIdAndCategoryId(memberId, categoryId); Member member = memberRepository.getById(memberId); Category category = categoryRepository.getById(categoryId); @@ -75,17 +75,15 @@ public List getAllByMemberId(final Long memberId) { @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { + subscriptionRepository.validateExistsByIdAndMemberId(id, memberId); Subscription subscription = subscriptionRepository.getById(id); - Member member = memberRepository.getById(memberId); - subscription.validateUpdatePossible(member); subscription.change(request.getColor(), request.isChecked()); } @Transactional public void delete(final Long id, final Long memberId) { Subscription subscription = subscriptionRepository.getById(id); - Member member = memberRepository.getById(memberId); - subscription.validateDeletePossible(member); + subscription.validateDeletePossible(memberId); subscriptionRepository.deleteById(id); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java index 87ab3a11..b067c78d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -55,14 +55,8 @@ public void change(final Color color, final boolean checked) { this.checked = checked; } - public void validateUpdatePossible(final Member member) { - if (!this.member.equals(member)) { - throw new NoPermissionException(); - } - } - - public void validateDeletePossible(final Member member) { - if (category.isCreator(member)) { + public void validateDeletePossible(final Long memberId) { + if (category.isCreatorId(memberId)) { throw new NoPermissionException("내가 만든 카테고리는 구독 취소 할 수 없습니다."); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index dfcb644e..758b1b88 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.subscription.domain; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; @@ -22,9 +23,15 @@ default Subscription getById(final Long id) { .orElseThrow(NoSuchSubscriptionException::new); } - default void validateExistsSubscription(final Long memberId, final Long categoryId) { + default void validateNotExistsByMemberIdAndCategoryId(final Long memberId, final Long categoryId) { if (existsByMemberIdAndCategoryId(memberId, categoryId)) { throw new ExistSubscriptionException(); } } + + default void validateExistsByIdAndMemberId(final Long id, final Long memberId) { + if (!existsByIdAndMemberId(id, memberId)) { + throw new NoPermissionException(); + } + } } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java index 6880bd66..72520ee2 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java @@ -4,6 +4,7 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; +import static com.allog.dallog.common.fixtures.MemberFixtures.리버; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; @@ -64,11 +65,10 @@ class CategoryTest { @Test void 제공된_멤버의_ID와_카테고리를_생성한_멤버의_ID가_일치하지_않으면_false를_반환한다() { // given - Member 관리자 = 관리자(); Category BE_일정 = BE_일정(관리자()); // when - boolean actual = BE_일정.isCreator(관리자); + boolean actual = BE_일정.isCreatorId(999L); // then assertThat(actual).isFalse(); From d93cbfa67ee89b658d852f005e5eee4bbabcf22d Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 21:47:28 +0900 Subject: [PATCH 031/148] =?UTF-8?q?refactor:=20ScheduleController=EC=9D=98?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/allog/dallog/presentation/ScheduleController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java index 4d95e609..cf37fd68 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java @@ -43,7 +43,7 @@ public ResponseEntity save(@AuthenticationPrincipal final Logi } @GetMapping("/members/me/schedules") - public ResponseEntity findSchedulesByMemberId( + public ResponseEntity findByMemberId( @AuthenticationPrincipal final LoginMember loginMember, @ModelAttribute DateRangeRequest request) { MemberScheduleResponses response = calendarService.findSchedulesByMemberId(loginMember.getId(), request); return ResponseEntity.ok(response); From 0638c81400cfc0eca397d06f25a9d06259dc25c4 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 23:38:14 +0900 Subject: [PATCH 032/148] =?UTF-8?q?refactor:=20ScheduleService=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=84=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/category/domain/Category.java | 8 ++++ .../dallog/domain/member/domain/Member.java | 10 ++-- .../schedule/application/ScheduleService.java | 47 +++++++------------ .../domain/schedule/domain/Schedule.java | 20 ++++++++ .../schedule/domain/ScheduleRepository.java | 6 +++ .../dto/response/SubscriptionResponse.java | 8 ++-- .../presentation/ScheduleController.java | 2 +- .../application/ScheduleServiceTest.java | 6 +-- .../presentation/ScheduleControllerTest.java | 6 +-- 9 files changed, 68 insertions(+), 45 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index b3d58448..947077a3 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -85,6 +85,14 @@ public void validateSubscriptionPossible(final Member member) { } } + public void validateCanAddSchedule(final Member member) { + if (categoryType == GOOGLE) { + throw new NoPermissionException("외부 연동 카테고리에는 일정을 추가할 수 없습니다."); + } + if (!this.member.equals(member)) { + throw new NoPermissionException(); + } + } public boolean isCreatorId(final Long creatorId) { return member.hasSameId(creatorId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java index 51bf52ec..050f19f7 100644 --- a/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -53,11 +53,6 @@ public Member(final String email, final String displayName, final String profile this.socialType = socialType; } - public void change(final String displayName) { - validateDisplayName(displayName); - this.displayName = displayName; - } - private void validateEmail(final String email) { Matcher matcher = EMAIL_PATTERN.matcher(email); if (!matcher.matches()) { @@ -71,6 +66,11 @@ private void validateDisplayName(final String displayName) { } } + public void change(final String displayName) { + validateDisplayName(displayName); + this.displayName = displayName; + } + public boolean hasSameId(final Long memberId) { return Objects.equals(this.id, memberId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index e4771bc5..3ca4e1c3 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -1,14 +1,14 @@ package com.allog.dallog.domain.schedule.application; -import com.allog.dallog.domain.auth.exception.NoPermissionException; -import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.schedule.domain.Schedule; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; -import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -17,52 +17,41 @@ public class ScheduleService { private final ScheduleRepository scheduleRepository; - private final CategoryService categoryService; + private final CategoryRepository categoryRepository; + private final MemberRepository memberRepository; - public ScheduleService(final ScheduleRepository scheduleRepository, final CategoryService categoryService) { + public ScheduleService(final ScheduleRepository scheduleRepository, final CategoryRepository categoryRepository, + final MemberRepository memberRepository) { this.scheduleRepository = scheduleRepository; - this.categoryService = categoryService; + this.categoryRepository = categoryRepository; + this.memberRepository = memberRepository; } @Transactional public ScheduleResponse save(final Long memberId, final Long categoryId, final ScheduleCreateRequest request) { - Category category = categoryService.getCategory(categoryId); - categoryService.validateCreatorBy(memberId, category); - validateCategoryType(category); - + Category category = categoryRepository.getById(categoryId); + Member member = memberRepository.getById(memberId); + category.validateCanAddSchedule(member); Schedule schedule = scheduleRepository.save(request.toEntity(category)); return new ScheduleResponse(schedule); } - private static void validateCategoryType(final Category category) { - if (category.isExternal()) { - throw new NoPermissionException("외부 연동 카테고리에는 일정을 추가할 수 없습니다."); - } - } - public ScheduleResponse findById(final Long id) { - Schedule schedule = getSchedule(id); - + Schedule schedule = scheduleRepository.getById(id); return new ScheduleResponse(schedule); } - private Schedule getSchedule(Long id) { - return scheduleRepository.findById(id) - .orElseThrow(NoSuchScheduleException::new); - } - @Transactional public void update(final Long id, final Long memberId, final ScheduleUpdateRequest request) { - Schedule schedule = getSchedule(id); - categoryService.validateCreatorBy(memberId, schedule.getCategory()); + Schedule schedule = scheduleRepository.getById(id); + schedule.validateUpdatePossible(memberId); schedule.change(request.getTitle(), request.getStartDateTime(), request.getEndDateTime(), request.getMemo()); } @Transactional - public void deleteById(final Long id, final Long memberId) { - Schedule schedule = getSchedule(id); - - categoryService.validateCreatorBy(memberId, schedule.getCategory()); + public void delete(final Long id, final Long memberId) { + Schedule schedule = scheduleRepository.getById(id); + schedule.validateDeletePossible(memberId); scheduleRepository.deleteById(id); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index 1f8da026..6327cac1 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -1,7 +1,9 @@ package com.allog.dallog.domain.schedule.domain; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; +import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; import java.time.LocalDateTime; import javax.persistence.Column; @@ -86,6 +88,24 @@ private void validateMemoLength(final String memo) { } } + public void validateUpdatePossible(final Long memberId) { + if (this.category.isExternal()) { + throw new NoPermissionException("외부 연동 카테고리에는 일정을 변경할 수 없습니다."); + } + if (!this.category.isCreatorId(memberId)) { + throw new NoPermissionException(); + } + } + + public void validateDeletePossible(final Long memberId) { + if (this.category.isExternal()) { + throw new NoPermissionException("외부 연동 카테고리에는 일정을 삭제할 수 없습니다."); + } + if (!this.category.isCreatorId(memberId)) { + throw new NoPermissionException(); + } + } + public Long getId() { return id; } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java index a45e93bf..c7ec86d0 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -1,9 +1,15 @@ package com.allog.dallog.domain.schedule.domain; +import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface ScheduleRepository extends JpaRepository { void deleteByCategoryIdIn(final List categoryIds); + + default Schedule getById(final Long id) { + return this.findById(id) + .orElseThrow(NoSuchScheduleException::new); + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java index 48907de6..6a9fff15 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java @@ -28,10 +28,6 @@ public SubscriptionResponse(final Long id, final CategoryResponse category, fina this.checked = checked; } - public boolean isChecked() { - return checked; - } - public Long getId() { return id; } @@ -43,4 +39,8 @@ public CategoryResponse getCategory() { public String getColorCode() { return colorCode; } + + public boolean isChecked() { + return checked; + } } diff --git a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java index cf37fd68..ce474055 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java @@ -66,7 +66,7 @@ public ResponseEntity update(@AuthenticationPrincipal final LoginMember lo @DeleteMapping("/schedules/{scheduleId}") public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, @PathVariable final Long scheduleId) { - scheduleService.deleteById(scheduleId, loginMember.getId()); + scheduleService.delete(scheduleId, loginMember.getId()); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index eadd0dd9..9158b058 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -251,7 +251,7 @@ class ScheduleServiceTest extends ServiceTest { ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when - scheduleService.deleteById(알록달록_회의.getId(), 후디_id); + scheduleService.delete(알록달록_회의.getId(), 후디_id); // then assertThatThrownBy(() -> scheduleService.findById(알록달록_회의.getId())) @@ -268,7 +268,7 @@ class ScheduleServiceTest extends ServiceTest { ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when & then - assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId(), 리버_id)) + assertThatThrownBy(() -> scheduleService.delete(알록달록_회의.getId(), 리버_id)) .isInstanceOf(NoPermissionException.class); } @@ -281,7 +281,7 @@ class ScheduleServiceTest extends ServiceTest { ScheduleResponse 알록달록_회의 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); // when & then - assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId() + 1, 후디_id)) + assertThatThrownBy(() -> scheduleService.delete(알록달록_회의.getId() + 1, 후디_id)) .isInstanceOf(NoSuchScheduleException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index 07e224ef..8c7db5bb 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -263,7 +263,7 @@ class ScheduleControllerTest extends ControllerTest { Long scheduleId = 1L; willDoNothing() .given(scheduleService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/schedules/{scheduleId}", scheduleId) @@ -286,7 +286,7 @@ class ScheduleControllerTest extends ControllerTest { Long scheduleId = 1L; willThrow(new NoPermissionException()) .given(scheduleService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) @@ -306,7 +306,7 @@ class ScheduleControllerTest extends ControllerTest { Long scheduleId = 1L; willThrow(new NoSuchScheduleException()) .given(scheduleService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) From 9fb5ae403102d6e289f2d848157c768d7c0bc626 Mon Sep 17 00:00:00 2001 From: devHudi Date: Sun, 11 Sep 2022 15:56:27 +0900 Subject: [PATCH 033/148] =?UTF-8?q?refactor:=20identifier=20=ED=98=95?= =?UTF-8?q?=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/AuthControllerTest.java | 4 +-- .../presentation/CategoryControllerTest.java | 26 +++++++++---------- .../ExternalCalendarControllerTest.java | 8 +++--- .../presentation/MemberControllerTest.java | 8 +++--- .../presentation/ScheduleControllerTest.java | 24 ++++++++--------- .../presentation/SchedulerControllerTest.java | 2 +- .../SubscriptionControllerTest.java | 10 +++---- 7 files changed, 41 insertions(+), 41 deletions(-) diff --git a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java index c253c6ee..3f874087 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java @@ -47,7 +47,7 @@ class AuthControllerTest extends ControllerTest { mockMvc.perform(get("/api/auth/{oauthProvider}/oauth-uri?redirectUri={redirectUri}", OAUTH_PROVIDER, "https://dallog.me/oauth")) .andDo(print()) - .andDo(document("auth/link", + .andDo(document("auth/generateLink", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -76,7 +76,7 @@ class AuthControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(MEMBER_인증_코드_토큰_요청()))) .andDo(print()) - .andDo(document("auth/token", + .andDo(document("auth/generateToken", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index 780bf34f..67ec9291 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -83,7 +83,7 @@ class CategoryControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(BE_일정_생성_요청)) ) .andDo(print()) - .andDo(document("categories/save", + .andDo(document("category/save", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -111,7 +111,7 @@ class CategoryControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(잘못된_카테고리_생성_요청)) ) .andDo(print()) - .andDo(document("categories/save/badRequest", + .andDo(document("category/save/failByInvalidNameFormat", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -138,7 +138,7 @@ class CategoryControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("categories/findAll", + .andDo(document("categories/findAllByName/allByNoName", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( @@ -167,7 +167,7 @@ class CategoryControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("categories/findAllLikeName", + .andDo(document("category/findAllByName/fileterByName", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( @@ -198,7 +198,7 @@ class CategoryControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) ) .andDo(print()) - .andDo(document("categories/findMine", + .andDo(document("category/findMineByName/allByNoName", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( @@ -229,7 +229,7 @@ class CategoryControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) ) .andDo(print()) - .andDo(document("categories/findMineLikeName", + .andDo(document("category/findMineByName/fileterByName", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( @@ -256,7 +256,7 @@ class CategoryControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("categories/findById", + .andDo(document("category/findById", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -281,7 +281,7 @@ class CategoryControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("categories/findById/notFound", + .andDo(document("category/findById/failByNoCategory", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -310,7 +310,7 @@ class CategoryControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(카테고리_수정_요청)) ) .andDo(print()) - .andDo(document("categories/update", + .andDo(document("category/update", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -340,7 +340,7 @@ class CategoryControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(카테고리_수정_요청)) ) .andDo(print()) - .andDo(document("categories/update/notFound", + .andDo(document("category/update/failByNoCategory", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -370,7 +370,7 @@ class CategoryControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(카테고리_수정_요청)) ) .andDo(print()) - .andDo(document("categories/update/badRequest", + .andDo(document("category/update/failByInvalidNameFormat", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -397,7 +397,7 @@ class CategoryControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) ) .andDo(print()) - .andDo(document("categories/delete", + .andDo(document("category/delete", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -425,7 +425,7 @@ class CategoryControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) ) .andDo(print()) - .andDo(document("categories/delete/notFound", + .andDo(document("category/delete/failByNoCategory", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java index 14fde50a..29840f01 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -65,7 +65,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .accept(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("external-calendars/get", + .andDo(document("externalCalendar/getExternalCalendar", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -90,7 +90,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) ) .andDo(print()) - .andDo(document("external-calendars/save", + .andDo(document("externalCalendar/save", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -103,7 +103,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .andExpect(status().isCreated()); } - @DisplayName("외부 캘린더를 카테고리로 저장하면 상태코드 201을 반환한다.") + @DisplayName("외부 캘린더를 중복하여 저장하면 상태코드 201을 반환한다.") @Test void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { // given @@ -119,7 +119,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) ) .andDo(print()) - .andDo(document("external-calendars/duplicated-save", + .andDo(document("externalCalendar/save/successOnDuplicate", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( diff --git a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java index 7d28205c..76362607 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java @@ -57,7 +57,7 @@ class MemberControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("members/me", + .andDo(document("member/findMe", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -80,7 +80,7 @@ class MemberControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("members/exception/notfound", + .andDo(document("member/findMe/failNoMember", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -107,7 +107,7 @@ class MemberControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(회원_수정_요청)) ) .andDo(print()) - .andDo(document("members/update", + .andDo(document("member/update", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -133,7 +133,7 @@ class MemberControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) ) .andDo(print()) - .andDo(document("members/delete", + .andDo(document("member/delete", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index 8c7db5bb..f148fee0 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -90,7 +90,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) - .andDo(document("schedules/save", + .andDo(document("schedule/save", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -113,7 +113,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) - .andDo(document("schedules/save/forbidden", + .andDo(document("schedule/save/failByNoPermission", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -136,7 +136,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request))) .andDo(print()) - .andDo(document("schedules/save/notfound", + .andDo(document("schedule/save/failByNoCategory", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -156,7 +156,7 @@ class ScheduleControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("schedules/findone", + .andDo(document("schedule/findById", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -176,7 +176,7 @@ class ScheduleControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("schedules/findone/notfound", + .andDo(document("schedule/findById/failByNoSchedule", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -200,7 +200,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(수정_요청))) .andDo(print()) - .andDo(document("schedules/update", + .andDo(document("schedule/update", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -226,7 +226,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(수정_요청))) .andDo(print()) - .andDo(document("schedules/update/forbidden", + .andDo(document("schedule/update/failByNoPermission", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -249,7 +249,7 @@ class ScheduleControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(수정_요청))) .andDo(print()) - .andDo(document("schedules/update/notfound", + .andDo(document("schedule/update/failByNoSchedule", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -269,7 +269,7 @@ class ScheduleControllerTest extends ControllerTest { mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/schedules/{scheduleId}", scheduleId) .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) .andDo(print()) - .andDo(document("schedules/delete", + .andDo(document("schedule/delete", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -292,7 +292,7 @@ class ScheduleControllerTest extends ControllerTest { mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) .andDo(print()) - .andDo(document("schedules/delete/forbidden", + .andDo(document("schedule/delete/failByNoPermission", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -312,7 +312,7 @@ class ScheduleControllerTest extends ControllerTest { mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) .andDo(print()) - .andDo(document("schedules/delete/notfound", + .andDo(document("schedule/delete/failByNoSchedule", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()) )) @@ -355,7 +355,7 @@ class ScheduleControllerTest extends ControllerTest { get("/api/members/me/schedules?startDateTime={startDate}&endDateTime={endDate}", startDate, endDate) .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) .andDo(print()) - .andDo(document("schedules/findAllByMember", + .andDo(document("schedule/findSchedulesByMemberId", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( diff --git a/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java index 85c174a9..d261232a 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java @@ -64,7 +64,7 @@ class SchedulerControllerTest extends ControllerTest { 1L, startDateTime, endDateTime) .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) .andDo(print()) - .andDo(document("scheduler/category/available-periods", + .andDo(document("scheduler/scheduleByCategory", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( diff --git a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java index 8f07d045..1d514f13 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java @@ -122,7 +122,7 @@ class SubscriptionControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("subscription/exist", + .andDo(document("subscription/save/failByAlreadyExisting", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -147,7 +147,7 @@ class SubscriptionControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .accept(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("subscription/private-category", + .andDo(document("subscription/save/failBySubscribingPrivateCategoryOfOther", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( @@ -253,9 +253,9 @@ class SubscriptionControllerTest extends ControllerTest { .andExpect(status().isNoContent()); } - @DisplayName("자신이 가지고 있지 않은 구독 정보인 경우 예외를 던진다.") + @DisplayName("구독 제거시 자신이 가지고 있지 않은 구독 정보인 경우 예외를 던진다.") @Test - void 자신이_가지고_있지_않은_구독_정보인_경우_예외를_던진다() throws Exception { + void 구독_제거시_자신이_가지고_있지_않은_구독_정보인_경우_예외를_던진다() throws Exception { // given given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); willThrow(new NoPermissionException()) @@ -268,7 +268,7 @@ class SubscriptionControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("subscription/permission", + .andDo(document("subscription/delete/failByNotMine", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( From 64e5ad68b66c221badaa9bef91b4a5e6bfaa1e64 Mon Sep 17 00:00:00 2001 From: devHudi Date: Sun, 11 Sep 2022 16:04:22 +0900 Subject: [PATCH 034/148] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/allog/dallog/presentation/AuthControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java index 3f874087..d6a5feb4 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java @@ -67,7 +67,6 @@ class AuthControllerTest extends ControllerTest { @Test void OAuth_로그인을_하면_token과_상태코드_200을_반환한다() throws Exception { // given -// TokenRequest tokenRequest = new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); given(authService.generateToken(any())).willReturn(MEMBER_인증_코드_토큰_응답()); // when & then From 053f04351b9a9cd98f4603c59f5651ff70a072dd Mon Sep 17 00:00:00 2001 From: devHudi Date: Sun, 11 Sep 2022 16:25:09 +0900 Subject: [PATCH 035/148] =?UTF-8?q?refactor:=20identifier=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=95=88=EB=90=9C=20=EB=B6=80=EB=B6=84=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/allog/dallog/presentation/AuthControllerTest.java | 2 +- .../com/allog/dallog/presentation/CategoryControllerTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java index d6a5feb4..c4e08d6a 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java @@ -102,7 +102,7 @@ class AuthControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(MEMBER_인증_코드_토큰_요청()))) .andDo(print()) - .andDo(document("auth/exception/token", + .andDo(document("auth/generateToken/failByResourceServerError", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index 67ec9291..d239aba7 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -138,7 +138,7 @@ class CategoryControllerTest extends ControllerTest { .contentType(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("categories/findAllByName/allByNoName", + .andDo(document("category/findAllByName/allByNoName", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestParameters( From 830f0f5d7c6db1bc87e0e4a5955accaa7734da7f Mon Sep 17 00:00:00 2001 From: devHudi Date: Sun, 11 Sep 2022 21:19:33 +0900 Subject: [PATCH 036/148] =?UTF-8?q?docs:=20adoc=20=EB=AC=B8=EC=84=9C=20?= =?UTF-8?q?=ED=98=95=EC=8B=9D=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/docs/asciidoc/auth.adoc | 50 +++---- backend/src/docs/asciidoc/category.adoc | 126 ++++++++---------- .../src/docs/asciidoc/external-calendar.adoc | 40 +++--- backend/src/docs/asciidoc/member.adoc | 46 +++---- backend/src/docs/asciidoc/schedule.adoc | 116 ++++++++-------- backend/src/docs/asciidoc/subscription.adoc | 86 +++--------- .../presentation/AuthControllerTest.java | 8 +- .../presentation/CategoryControllerTest.java | 22 ++- .../ExternalCalendarControllerTest.java | 4 +- .../presentation/MemberControllerTest.java | 8 ++ .../SubscriptionControllerTest.java | 28 +--- 11 files changed, 224 insertions(+), 310 deletions(-) diff --git a/backend/src/docs/asciidoc/auth.adoc b/backend/src/docs/asciidoc/auth.adoc index c785188a..c5dc3979 100644 --- a/backend/src/docs/asciidoc/auth.adoc +++ b/backend/src/docs/asciidoc/auth.adoc @@ -2,58 +2,50 @@ === OAuth 로그인 링크 생성 -==== Request +==== HTTP Request -include::{snippets}/auth/link/http-request.adoc[] +include::{snippets}/auth/generateLink/http-request.adoc[] ==== Path Parameters -include::{snippets}/auth/link/path-parameters.adoc[] +include::{snippets}/auth/generateLink/path-parameters.adoc[] ==== Request Parameters -include::{snippets}/auth/link/request-parameters.adoc[] +include::{snippets}/auth/generateLink/request-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/auth/link/http-response.adoc[] +include::{snippets}/auth/generateLink/http-response.adoc[] -==== ResponseFields +==== Response Fields -include::{snippets}/auth/link/response-fields.adoc[] +include::{snippets}/auth/generateLink/response-fields.adoc[] === OAuth 로그인 -==== Request +==== HTTP Request -include::{snippets}/auth/token/http-request.adoc[] +include::{snippets}/auth/generateToken/http-request.adoc[] -==== PathParameters - -include::{snippets}/auth/token/path-parameters.adoc[] - -==== RequestFields - -include::{snippets}/auth/token/request-fields.adoc[] - -==== Response +==== Path Parameters -include::{snippets}/auth/token/http-response.adoc[] +include::{snippets}/auth/generateToken/path-parameters.adoc[] -=== OAuth 로그인 : Resource Server 에러 +==== Request Fields -==== Request +include::{snippets}/auth/generateToken/request-fields.adoc[] -include::{snippets}/auth/exception/token/http-request.adoc[] +==== HTTP Response -==== PathParameters +include::{snippets}/auth/generateToken/http-response.adoc[] -include::{snippets}/auth/exception/token/path-parameters.adoc[] +==== Response Fields -==== RequestFields +include::{snippets}/auth/generateToken/response-fields.adoc[] -include::{snippets}/auth/exception/token/request-fields.adoc[] +=== OAuth 로그인 (Resource Server 에러) -==== Response +==== HTTP Response -include::{snippets}/auth/exception/token/http-response.adoc[] +include::{snippets}/auth/generateToken/failByResourceServerError/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/category.adoc b/backend/src/docs/asciidoc/category.adoc index 91f9ceed..42d9b585 100644 --- a/backend/src/docs/asciidoc/category.adoc +++ b/backend/src/docs/asciidoc/category.adoc @@ -2,144 +2,132 @@ === 카테고리 생성 -==== Request +==== HTTP Request -include::{snippets}/categories/save/http-request.adoc[] +include::{snippets}/category/save/http-request.adoc[] -==== Response +==== Request Fields -include::{snippets}/categories/save/http-response.adoc[] +include::{snippets}/category/save/request-fields.adoc[] -=== 카테고리 생성 (유효하지 않은 카테고리 이름) +==== HTTP Response -==== Request +include::{snippets}/category/save/http-response.adoc[] -include::{snippets}/categories/save/badRequest/http-request.adoc[] +==== Response Fields + +include::{snippets}/category/save/response-fields.adoc[] + +=== 카테고리 생성 (유효하지 않은 카테고리 이름) -==== Response +==== HTTP Response -include::{snippets}/categories/save/badRequest/http-response.adoc[] +include::{snippets}/category/save/failByInvalidNameFormat/http-response.adoc[] === 전체 카테고리 조회 ==== Request -include::{snippets}/categories/findAll/http-request.adoc[] +include::{snippets}/category/findAllByName/allByNoName/http-request.adoc[] -==== RequestParameters +==== Request Parameters -include::{snippets}/categories/findAll/request-parameters.adoc[] +include::{snippets}/category/findAllByName/allByNoName/request-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/findAll/http-response.adoc[] +include::{snippets}/category/findAllByName/allByNoName/http-response.adoc[] -=== 카테고리 제목 조회 +=== 전체 카테고리 목록 이름으로 필터링 -==== Request +==== HTTP Request -include::{snippets}/categories/findAllLikeName/http-request.adoc[] +include::{snippets}/category/findAllByName/fileterByName/http-request.adoc[] -==== RequestParameters +==== Request Parameters -include::{snippets}/categories/findAllLikeName/request-parameters.adoc[] +include::{snippets}/category/findAllByName/fileterByName/request-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/findAllLikeName/http-response.adoc[] +include::{snippets}/category/findAllByName/fileterByName/http-response.adoc[] === 자신이 생성한 카테고리 조회 -==== Request +==== HTTP Request -include::{snippets}/categories/findMine/http-request.adoc[] +include::{snippets}/category/findMineByName/allByNoName/http-request.adoc[] -==== RequestParameters +==== Request Parameters -include::{snippets}/categories/findMine/request-parameters.adoc[] +include::{snippets}/category/findMineByName/allByNoName/request-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/findMine/response-body.adoc[] +include::{snippets}/category/findMineByName/allByNoName/http-response.adoc[] === ID를 통한 카테고리 단건 조회 -==== Request +==== HTTP Request -include::{snippets}/categories/findById/http-request.adoc[] +include::{snippets}/category/findById/http-request.adoc[] -==== PathParameters +==== Path Parameters -include::{snippets}/categories/findById/path-parameters.adoc[] +include::{snippets}/category/findById/path-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/findById/http-response.adoc[] +include::{snippets}/category/findById/http-response.adoc[] === ID를 통한 카테고리 단건 조회 (존재하지 않는 경우) -==== Request - -include::{snippets}/categories/findById/notFound/http-request.adoc[] - -==== Response +==== HTTP Response -include::{snippets}/categories/findById/notFound/http-response.adoc[] +include::{snippets}/category/findById/failByNoCategory/http-response.adoc[] === 카테고리 수정 -==== Request +==== HTTP Request -include::{snippets}/categories/update/http-request.adoc[] +include::{snippets}/category/update/http-request.adoc[] -==== PathParameters +==== Path Parameters -include::{snippets}/categories/update/path-parameters.adoc[] +include::{snippets}/category/update/path-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/update/http-response.adoc[] +include::{snippets}/category/update/http-response.adoc[] === 카테고리 수정 (존재하지 않는 경우) -==== Request - -include::{snippets}/categories/update/notFound/http-request.adoc[] +==== HTTP Response -==== Response - -include::{snippets}/categories/update/notFound/http-response.adoc[] +include::{snippets}/category/update/failByNoCategory/http-response.adoc[] === 카테고리 수정 (유효하지 않은 카테고리 이름) -==== Request - -include::{snippets}/categories/update/badRequest/http-request.adoc[] +==== HTTP Response -==== Response - -include::{snippets}/categories/update/badRequest/http-response.adoc[] +include::{snippets}/category/update/failByInvalidNameFormat/http-response.adoc[] === 카테고리 삭제 -==== Request +==== HTTP Request -include::{snippets}/categories/delete/http-request.adoc[] +include::{snippets}/category/delete/http-request.adoc[] -==== PathParameters +==== Path Parameters -include::{snippets}/categories/delete/path-parameters.adoc[] +include::{snippets}/category/delete/path-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/categories/delete/http-response.adoc[] +include::{snippets}/category/delete/http-response.adoc[] === 카테고리 삭제 (존재하지 않는 경우) -==== Request - -include::{snippets}/categories/delete/notFound/http-request.adoc[] - -==== Response +==== HTTP Response -include::{snippets}/categories/delete/notFound/http-response.adoc[] +include::{snippets}/category/delete/failByNoCategory/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/external-calendar.adoc b/backend/src/docs/asciidoc/external-calendar.adoc index 72e3bc0b..d7c0cabf 100644 --- a/backend/src/docs/asciidoc/external-calendar.adoc +++ b/backend/src/docs/asciidoc/external-calendar.adoc @@ -2,46 +2,38 @@ === 자신의 외부 캘린더 조회 -==== Request +==== HTTP Request -include::{snippets}/external-calendars/get/http-request.adoc[] +include::{snippets}/externalCalendar/getExternalCalendar/http-request.adoc[] -==== Response +==== HTTP Response -include::{snippets}/external-calendars/get/http-response.adoc[] +include::{snippets}/externalCalendar/getExternalCalendar/http-response.adoc[] === 자신의 외부 캘린더 저장 -==== Request +==== HTTP Request -include::{snippets}/external-calendars/save/http-request.adoc[] +include::{snippets}/externalCalendar/save/http-request.adoc[] -==== Request Headers +==== Request Fields -include::{snippets}/external-calendars/save/request-headers.adoc[] +include::{snippets}/externalCalendar/save/request-fields.adoc[] -==== Request Body +==== HTTP Response -include::{snippets}/external-calendars/save/request-fields.adoc[] - -==== Response - -include::{snippets}/external-calendars/save/http-response.adoc[] +include::{snippets}/externalCalendar/save/http-response.adoc[] === 자신의 외부 캘린더 중복 저장 시 예외 발생 -==== Request - -include::{snippets}/external-calendars/duplicated-save/http-request.adoc[] - -==== Request Headers +==== HTTP Request -include::{snippets}/external-calendars/duplicated-save/request-headers.adoc[] +include::{snippets}/externalCalendar/save/failByDuplicate/http-request.adoc[] -==== Request Body +==== Request Fields -include::{snippets}/external-calendars/duplicated-save/request-fields.adoc[] +include::{snippets}/externalCalendar/save/failByDuplicate/request-fields.adoc[] -==== Response +==== HTTP Response -include::{snippets}/external-calendars/duplicated-save/http-response.adoc[] +include::{snippets}/externalCalendar/save/failByDuplicate/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/member.adoc b/backend/src/docs/asciidoc/member.adoc index ece68ebb..33be114f 100644 --- a/backend/src/docs/asciidoc/member.adoc +++ b/backend/src/docs/asciidoc/member.adoc @@ -2,52 +2,44 @@ === 내 정보 조회 -==== Request +==== HTTP Request -include::{snippets}/members/me/http-request.adoc[] +include::{snippets}/member/findMe/http-request.adoc[] -==== RequestHeaders +==== HTTP Response -include::{snippets}/members/me/request-headers.adoc[] +include::{snippets}/member/findMe/http-response.adoc[] -==== Response +==== Response Fields -include::{snippets}/members/me/http-response.adoc[] +include::{snippets}/member/findMe/response-fields.adoc[] -=== 삭제된 회원 정보 조회 +=== 내 정보 조회 (존재하지 않는 회원 조회 시) -==== Request +==== HTTP Response -include::{snippets}/members/exception/notfound/http-request.adoc[] - -==== Response - -include::{snippets}/members/exception/notfound/http-response.adoc[] +include::{snippets}/member/findMe/failNoMember/http-response.adoc[] === 내 정보 수정 -==== Request - -include::{snippets}/members/update/http-request.adoc[] - -==== RequestHeaders +==== HTTP Request -include::{snippets}/members/update/request-headers.adoc[] +include::{snippets}/member/update/http-request.adoc[] -==== Request Parameters +==== Request Fields -include::{snippets}/members/update/request-body.adoc[] +include::{snippets}/member/update/request-fields.adoc[] -==== Response +==== HTTP Response -include::{snippets}/members/update/http-response.adoc[] +include::{snippets}/member/update/http-response.adoc[] === 회원 탈퇴 -==== Request +==== HTTP Request -include::{snippets}/members/delete/http-request.adoc[] +include::{snippets}/member/delete/http-request.adoc[] -==== Response +==== HTTP Response -include::{snippets}/members/delete/http-response.adoc[] +include::{snippets}/member/delete/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/schedule.adoc b/backend/src/docs/asciidoc/schedule.adoc index 46ea60b8..7ba1924c 100644 --- a/backend/src/docs/asciidoc/schedule.adoc +++ b/backend/src/docs/asciidoc/schedule.adoc @@ -2,121 +2,107 @@ === 회원 일정 목록 조회 -==== Request - -include::{snippets}/schedules/findAllByMember/http-request.adoc[] - -==== RequestParameters - -include::{snippets}/schedules/findAllByMember/request-parameters.adoc[] - -==== Response - -include::{snippets}/schedules/findAllByMember/http-response.adoc[] +==== HTTP Request -=== 회원 일정 및 외부 일정 목록 조회 +include::{snippets}/schedule/findSchedulesByMemberId/http-request.adoc[] -==== Request - -include::{snippets}/schedules/findAllByMemberWithExternalSchedule/http-request.adoc[] +==== Request Parameters -==== RequestParameters +include::{snippets}/schedule/findSchedulesByMemberId/request-parameters.adoc[] -include::{snippets}/schedules/findAllByMemberWithExternalSchedule/request-parameters.adoc[] - -==== Response +==== HTTP Response -include::{snippets}/schedules/findAllByMemberWithExternalSchedule/http-response.adoc[] +include::{snippets}/schedule/findSchedulesByMemberId/http-response.adoc[] === 일정 등록 -==== Request +==== HTTP Request -include::{snippets}/schedules/save/http-request.adoc[] +include::{snippets}/schedule/save/http-request.adoc[] -==== Response +==== HTTP Response -include::{snippets}/schedules/save/http-response.adoc[] +include::{snippets}/schedule/save/http-response.adoc[] -=== 일정 생성 (카테고리 권한 없음) +=== 일정 등록 (카테고리 권한이 없을 때) -==== Response +==== HTTP Response -include::{snippets}/schedules/save/forbidden/http-response.adoc[] +include::{snippets}/schedule/save/failByNoPermission/http-response.adoc[] === 일정 생성 (카테고리가 존재하지 않음) -==== Response +==== HTTP Response -include::{snippets}/schedules/save/notfound/http-response.adoc[] +include::{snippets}/schedule/save/failByNoCategory/http-response.adoc[] === 일정 단건 조회 -==== Request +==== HTTP Request -include::{snippets}/schedules/findone/http-request.adoc[] +include::{snippets}/schedule/findById/http-request.adoc[] -==== Response +==== HTTP Response -include::{snippets}/schedules/findone/http-response.adoc[] +include::{snippets}/schedule/findById/http-response.adoc[] -=== 일정 단건 조회 (일정이 존재하지 않음) +=== 일정 단건 조회 (일정이 존재하지 않을 때) -==== Response +==== HTTP Response -include::{snippets}/schedules/findone/notfound/http-response.adoc[] +include::{snippets}/schedule/findById/failByNoSchedule/http-response.adoc[] === 일정 수정 -==== Request +==== HTTP Request -include::{snippets}/schedules/update/http-request.adoc[] +include::{snippets}/schedule/update/http-request.adoc[] -==== Path Variable +==== Path Parameters -include::{snippets}/schedules/update/path-parameters.adoc[] +include::{snippets}/schedule/update/path-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/schedules/update/http-response.adoc[] +include::{snippets}/schedule/update/http-response.adoc[] -=== 일정 수정 (카테고리 권한 없음) +=== 일정 수정 (카테고리 권한이 없을 때) -==== Response +==== HTTP Response -include::{snippets}/schedules/update/forbidden/http-response.adoc[] +include::{snippets}/schedule/update/failByNoPermission/http-response.adoc[] -=== 일정 수정 (카테고리가 존재하지 않음) +=== 일정 수정 (카테고리가 존재하지 않을 때) -==== Response +==== HTTP Response -include::{snippets}/schedules/update/notfound/http-response.adoc[] +include::{snippets}/schedule/update/failByNoSchedule/http-response.adoc[] === 일정 제거 -==== Request +==== HTTP Request -include::{snippets}/schedules/delete/http-request.adoc[] +include::{snippets}/schedule/delete/http-request.adoc[] -==== Path Variable +==== Path Parameters -include::{snippets}/schedules/delete/path-parameters.adoc[] +include::{snippets}/schedule/delete/path-parameters.adoc[] -==== Response +==== HTTP Response -include::{snippets}/schedules/delete/http-response.adoc[] +include::{snippets}/schedule/delete/http-response.adoc[] -=== 일정 제거 (카테고리 권한 없음) +=== 일정 제거 (카테고리 권한이 없을 때) -==== Response +==== HTTP Response -include::{snippets}/schedules/delete/forbidden/http-response.adoc[] +include::{snippets}/schedule/delete/failByNoPermission/http-response.adoc[] === 일정 제거 (카테고리가 존재하지 않음) -==== Response +==== HTTP Response -include::{snippets}/schedules/delete/notfound/http-response.adoc[] +include::{snippets}/schedule/delete/failByNoSchedule/http-response.adoc[] === 일정 조율 @@ -124,14 +110,16 @@ include::{snippets}/schedules/delete/notfound/http-response.adoc[] ==== Request -include::{snippets}/scheduler/category/available-periods/http-request.adoc[] +include::{snippets}/scheduler/scheduleByCategory/http-request.adoc[] + +==== Path Parameters -==== Parameters +include::{snippets}/scheduler/scheduleByCategory/path-parameters.adoc[] -include::{snippets}/scheduler/category/available-periods/path-parameters.adoc[] +==== Request Parameters -include::{snippets}/scheduler/category/available-periods/request-parameters.adoc[] +include::{snippets}/scheduler/scheduleByCategory/request-parameters.adoc[] ==== Response -include::{snippets}/scheduler/category/available-periods/http-response.adoc[] +include::{snippets}/scheduler/scheduleByCategory/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/subscription.adoc b/backend/src/docs/asciidoc/subscription.adoc index 93dbcedc..12273c12 100644 --- a/backend/src/docs/asciidoc/subscription.adoc +++ b/backend/src/docs/asciidoc/subscription.adoc @@ -2,7 +2,7 @@ === 구독 등록 -==== Request +==== HTTP Request include::{snippets}/subscription/save/http-request.adoc[] @@ -10,63 +10,35 @@ include::{snippets}/subscription/save/http-request.adoc[] include::{snippets}/subscription/save/path-parameters.adoc[] -==== Request Headers - -include::{snippets}/subscription/save/request-headers.adoc[] - -==== Response +==== HTTP Response include::{snippets}/subscription/save/http-response.adoc[] -=== 자신의 구독 목록 조회 - -==== Request - -include::{snippets}/subscription/findMine/http-request.adoc[] - -==== Response - -include::{snippets}/subscription/findMine/http-response.adoc[] - -=== 중복된 구독 등록 - -==== Request - -include::{snippets}/subscription/exist/http-request.adoc[] - -==== Path Parameters - -include::{snippets}/subscription/exist/path-parameters.adoc[] - -==== Request Headers +=== 구독 등록 (중복된 구독을 등록할 때) -include::{snippets}/subscription/exist/request-headers.adoc[] +==== HTTP Response -==== Response +include::{snippets}/subscription/save/failByAlreadyExisting/http-response.adoc[] -include::{snippets}/subscription/exist/http-response.adoc[] +=== 구독 등록 (3자의 개인 카테고리 구독 요청시) -==== Request +==== HTTP Response -include::{snippets}/subscription/me/http-request.adoc[] +include::{snippets}/subscription/save/failBySubscribingPrivateCategoryOfOther/http-response.adoc[] -==== Request Headers - -include::{snippets}/subscription/me/request-headers.adoc[] - -==== Response +=== 자신의 구독 목록 조회 -include::{snippets}/subscription/me/http-response.adoc[] +==== HTTP Request -=== 3자의 개인 카테고리 구독 요청시 +include::{snippets}/subscription/findMine/http-request.adoc[] -==== Response +==== HTTP Response -include::{snippets}/subscription/private-category/http-response.adoc[] +include::{snippets}/subscription/findMine/http-response.adoc[] === 내 구독 정보 수정 -==== Request +==== HTTP Request include::{snippets}/subscription/update/http-request.adoc[] @@ -82,13 +54,13 @@ include::{snippets}/subscription/update/request-headers.adoc[] include::{snippets}/subscription/update/request-body.adoc[] -==== Response +==== HTTP Response include::{snippets}/subscription/update/http-response.adoc[] -=== 내 구독 정보 삭제 +=== 구독 삭제 -==== Request +==== HTTP Request include::{snippets}/subscription/delete/http-request.adoc[] @@ -96,28 +68,12 @@ include::{snippets}/subscription/delete/http-request.adoc[] include::{snippets}/subscription/delete/path-parameters.adoc[] -==== Request Headers - -include::{snippets}/subscription/delete/request-headers.adoc[] - -==== Response +==== HTTP Response include::{snippets}/subscription/delete/http-response.adoc[] -=== 내 구독 정보가 아닌 구독 삭제 - -==== Request - -include::{snippets}/subscription/permission/http-request.adoc[] - -==== Path Parameters - -include::{snippets}/subscription/permission/path-parameters.adoc[] - -==== Request Headers - -include::{snippets}/subscription/permission/request-headers.adoc[] +=== 구독 삭제 (내 구독이 아닐 때) -==== Response +==== HTTP Response -include::{snippets}/subscription/permission/http-response.adoc[] +include::{snippets}/subscription/delete/failByNoPermission/http-response.adoc[] diff --git a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java index c4e08d6a..4c8f9128 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java @@ -1,5 +1,6 @@ package com.allog.dallog.presentation; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_인증_코드_토큰_응답; import static com.allog.dallog.common.fixtures.AuthFixtures.OAUTH_PROVIDER; @@ -44,14 +45,14 @@ class AuthControllerTest extends ControllerTest { given(authService.generateGoogleLink(any())).willReturn(OAuth_로그인_링크); // when & then - mockMvc.perform(get("/api/auth/{oauthProvider}/oauth-uri?redirectUri={redirectUri}", OAUTH_PROVIDER, + mockMvc.perform(get("/api/auth/{oauthProvider}/oauth-uri?redirectUri={redirectUri}", GOOGLE_PROVIDER, "https://dallog.me/oauth")) .andDo(print()) .andDo(document("auth/generateLink", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( - parameterWithName("oauthProvider").description("OAuth 로그인 제공자") + parameterWithName("oauthProvider").description("OAuth 로그인 제공자 (GOOGLE)") ), requestParameters( parameterWithName("redirectUri").description("OAuth Redirect URI") @@ -85,6 +86,9 @@ class AuthControllerTest extends ControllerTest { fieldWithPath("code").type(JsonFieldType.STRING).description("OAuth 로그인 인증 코드"), fieldWithPath("redirectUri").type(JsonFieldType.STRING) .description("OAuth Redirect URI") + ), + responseFields( + fieldWithPath("accessToken").type(JsonFieldType.STRING).description("달록 Access Token") ) )) .andExpect(status().isOk()); diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index d239aba7..f29ae8bc 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -25,6 +25,9 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessRequest; import static org.springframework.restdocs.operation.preprocess.Preprocessors.preprocessResponse; import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; @@ -87,7 +90,22 @@ class CategoryControllerTest extends ControllerTest { preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( - headerWithName("Authorization").description("JWT 토큰")) + headerWithName("Authorization").description("JWT 토큰")), + requestFields( + fieldWithPath("name").description("카테고리 이름 (최대 20글자)"), + fieldWithPath("categoryType").description("카테고리 타입 (NORMAL | PERSONAL | GOOGLE)") + ), + responseFields( + fieldWithPath("id").description("카테고리 ID"), + fieldWithPath("name").description("카테고리 이름"), + fieldWithPath("categoryType").description("카테고리 타입 (NORMAL | PERSONAL | GOOGLE)"), + fieldWithPath("creator.id").description("카테고리 생성자 ID"), + fieldWithPath("creator.email").description("카테고리 생성자 이메일"), + fieldWithPath("creator.displayName").description("카테고리 생성자 이름"), + fieldWithPath("creator.profileImageUrl").description("카테고리 생성자 프로필 이미지 URL"), + fieldWithPath("creator.socialType").description("카테고리 생성자의 소셜 타입"), + fieldWithPath("createdAt").description("카테고리 생성일자") + ) ) ) .andExpect(status().isCreated()); @@ -157,7 +175,7 @@ class CategoryControllerTest extends ControllerTest { int page = 0; int size = 10; - List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); + List 일정_목록 = List.of(BE_일정(관리자()), FE_일정(관리자())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java index 29840f01..85dcd11f 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -103,7 +103,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .andExpect(status().isCreated()); } - @DisplayName("외부 캘린더를 중복하여 저장하면 상태코드 201을 반환한다.") + @DisplayName("외부 캘린더를 중복하여 저장하면 상태코드 400을 반환한다.") @Test void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { // given @@ -119,7 +119,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) ) .andDo(print()) - .andDo(document("externalCalendar/save/successOnDuplicate", + .andDo(document("externalCalendar/save/failByDuplicate", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( diff --git a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java index 76362607..a0fab2db 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java @@ -15,6 +15,7 @@ import static org.springframework.restdocs.operation.preprocess.Preprocessors.prettyPrint; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; @@ -62,6 +63,13 @@ class MemberControllerTest extends ControllerTest { preprocessResponse(prettyPrint()), requestHeaders( headerWithName("Authorization").description("JWT 토큰") + ), + responseFields( + fieldWithPath("id").description("회원 ID"), + fieldWithPath("email").description("회원 이메일"), + fieldWithPath("displayName").description("회원 이름"), + fieldWithPath("profileImageUrl").description("회원 프로필 이미지 URL"), + fieldWithPath("socialType").description("회원 소셜 타입") ) )) .andExpect(status().isOk()); diff --git a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java index 1d514f13..1718082e 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java @@ -85,30 +85,6 @@ class SubscriptionControllerTest extends ControllerTest { .andExpect(status().isCreated()); } - @DisplayName("자신의 구독 목록을 가져온다.") - @Test - void 자신의_구독_목록을_가져온다() throws Exception { - // given - CategoryResponse 공통_일정_응답 = 공통_일정_응답(관리자_응답); - - SubscriptionsResponse subscriptionsResponse = new SubscriptionsResponse( - List.of(색상1_구독_응답(공통_일정_응답), 색상2_구독_응답(공통_일정_응답), 색상3_구독_응답(공통_일정_응답))); - - given(subscriptionService.findByMemberId(any())) - .willReturn(subscriptionsResponse); - - // when & then - mockMvc.perform(get("/api/members/me/subscriptions", 공통_일정_응답.getId()) - .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) - .accept(MediaType.APPLICATION_JSON)) - .andDo(print()) - .andDo(document("subscription/findMine", - preprocessRequest(prettyPrint()), - preprocessResponse(prettyPrint()) - )) - .andExpect(status().isOk()); - } - @DisplayName("회원이 이미 카테고리를 구독한 경우 예외를 던진다.") @Test void 회원이_이미_카테고리를_구독한_경우_예외를_던진다() throws Exception { @@ -182,7 +158,7 @@ class SubscriptionControllerTest extends ControllerTest { .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("subscription/me", + .andDo(document("subscription/findMine", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -268,7 +244,7 @@ class SubscriptionControllerTest extends ControllerTest { .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) .contentType(MediaType.APPLICATION_JSON)) .andDo(print()) - .andDo(document("subscription/delete/failByNotMine", + .andDo(document("subscription/delete/failByNoPermission", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), pathParameters( From 27524a10c76340705958d7139327f63210ad25d4 Mon Sep 17 00:00:00 2001 From: devHudi Date: Thu, 15 Sep 2022 17:34:48 +0900 Subject: [PATCH 037/148] =?UTF-8?q?docs:=20=EC=98=88=EC=99=B8=20=EC=83=81?= =?UTF-8?q?=ED=99=A9=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=EC=9D=98=20Request=20=EA=B4=80=EB=A0=A8=20=ED=95=AD=EB=AA=A9?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/docs/asciidoc/external-calendar.adoc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/backend/src/docs/asciidoc/external-calendar.adoc b/backend/src/docs/asciidoc/external-calendar.adoc index d7c0cabf..ca2cdeca 100644 --- a/backend/src/docs/asciidoc/external-calendar.adoc +++ b/backend/src/docs/asciidoc/external-calendar.adoc @@ -24,15 +24,7 @@ include::{snippets}/externalCalendar/save/request-fields.adoc[] include::{snippets}/externalCalendar/save/http-response.adoc[] -=== 자신의 외부 캘린더 중복 저장 시 예외 발생 - -==== HTTP Request - -include::{snippets}/externalCalendar/save/failByDuplicate/http-request.adoc[] - -==== Request Fields - -include::{snippets}/externalCalendar/save/failByDuplicate/request-fields.adoc[] +=== 자신의 외부 캘린더 저장 (중복 저장할 경우) ==== HTTP Response From 2c5a96fb929a537d9f8b473b8967adbafce8800d Mon Sep 17 00:00:00 2001 From: devHudi Date: Fri, 16 Sep 2022 13:07:39 +0900 Subject: [PATCH 038/148] =?UTF-8?q?ci:=20=EC=BB=A4=EB=B2=84=EB=A6=AC?= =?UTF-8?q?=EC=A7=80=20=ED=94=8C=EB=9F=AC=EA=B7=B8=EC=9D=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/backend-ci.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 87ba6ea3..62b4f7fa 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -37,11 +37,3 @@ jobs: if: always() with: junit_files: ${{ github.workspace }}/backend/build/test-results/**/*.xml - - - name: Add coverage to PR - id: jacoco - uses: madrapps/jacoco-report@v1.2 - with: - paths: ${{ github.workspace }}/backend/build/jacoco/index.xml - token: ${{ secrets.GITHUB_TOKEN }} - From 2e2925a06beebe6f22898d03213371e4a0772320 Mon Sep 17 00:00:00 2001 From: koo Date: Wed, 14 Sep 2022 23:38:14 +0900 Subject: [PATCH 039/148] =?UTF-8?q?refactor:=20ScheduleService=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=A0=84=EC=B2=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/allog/dallog/domain/category/domain/Category.java | 1 + .../java/com/allog/dallog/domain/schedule/domain/Schedule.java | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index 947077a3..800b9c33 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -93,6 +93,7 @@ public void validateCanAddSchedule(final Member member) { throw new NoPermissionException(); } } + public boolean isCreatorId(final Long creatorId) { return member.hasSameId(creatorId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index 6327cac1..c011f4c3 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -3,7 +3,6 @@ import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.common.BaseEntity; -import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; import java.time.LocalDateTime; import javax.persistence.Column; From c01e74c1b564acd8b40047a09b66eb831602e2b4 Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 14:03:25 +0900 Subject: [PATCH 040/148] =?UTF-8?q?refactor:=20CategoryController=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/CategoryController.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java index 7d18729f..84e91f5c 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -43,23 +43,23 @@ public ResponseEntity save(@AuthenticationPrincipal final Logi } @GetMapping - public ResponseEntity findAllByName(@RequestParam(defaultValue = "") final String name, - final Pageable pageable) { + public ResponseEntity findByName(@RequestParam(defaultValue = "") final String name, + final Pageable pageable) { return ResponseEntity.ok(categoryService.findNormalByName(name, pageable)); } - @GetMapping("/me") - public ResponseEntity findMineByName(@AuthenticationPrincipal final LoginMember loginMember, - @RequestParam(defaultValue = "") final String name, - final Pageable pageable) { - return ResponseEntity.ok(categoryService.findMineByName(loginMember.getId(), name, pageable)); - } - @GetMapping("/{categoryId}") public ResponseEntity findById(@PathVariable final Long categoryId) { return ResponseEntity.ok().body(categoryService.findById(categoryId)); } + @GetMapping("/me") + public ResponseEntity findMyCategories(@AuthenticationPrincipal final LoginMember loginMember, + @RequestParam(defaultValue = "") final String name, + final Pageable pageable) { + return ResponseEntity.ok(categoryService.findMineByName(loginMember.getId(), name, pageable)); + } + @PatchMapping("/{categoryId}") public ResponseEntity update(@AuthenticationPrincipal final LoginMember loginMember, @PathVariable final Long categoryId, From e9bf9375ef5fe2fa6360d61fd5b5181681aa206f Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 14:37:26 +0900 Subject: [PATCH 041/148] =?UTF-8?q?refactor:=20CategoryService.save()=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 31 +++++-------------- .../category/domain/CategoryRepository.java | 8 +++-- .../ExternalCategoryDetailRepository.java | 7 +++++ .../DuplicatedExternalCategoryException.java | 12 ------- .../ExistExternalCategoryException.java | 12 +++++++ .../dallog/global/error/ControllerAdvice.java | 4 +-- .../application/CategoryServiceTest.java | 4 +-- .../ExternalCalendarControllerTest.java | 4 +-- 8 files changed, 38 insertions(+), 44 deletions(-) delete mode 100644 backend/src/main/java/com/allog/dallog/domain/category/exception/DuplicatedExternalCategoryException.java create mode 100644 backend/src/main/java/com/allog/dallog/domain/category/exception/ExistExternalCategoryException.java diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index 23885434..72b19753 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -13,12 +13,11 @@ import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; import com.allog.dallog.domain.category.dto.response.CategoriesResponse; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.category.exception.DuplicatedExternalCategoryException; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; -import com.allog.dallog.domain.member.exception.NoSuchMemberException; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import java.util.List; @@ -50,38 +49,24 @@ public CategoryService(final CategoryRepository categoryRepository, @Transactional public CategoryResponse save(final Long memberId, final CategoryCreateRequest request) { - Member member = memberRepository.findById(memberId) - .orElseThrow(NoSuchMemberException::new); - Category newCategory = new Category(request.getName(), member, + Member member = memberRepository.getById(memberId); + Category category = new Category(request.getName(), member, CategoryType.valueOf(request.getCategoryType().toUpperCase())); - categoryRepository.save(newCategory); - return new CategoryResponse(newCategory); + Category savedCategory = categoryRepository.save(category); + return new CategoryResponse(savedCategory); } @Transactional public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { - List categories = categoryRepository.findByMemberId(memberId); - validateDuplicateExternalCategory(request.getExternalId(), categories); + List externalCategories = categoryRepository.findByMemberIdAndCategoryType(memberId, CategoryType.GOOGLE); + externalCategoryDetailRepository.validateExistCategory(request.getExternalId(), externalCategories); CategoryResponse response = save(memberId, new CategoryCreateRequest(request.getName(), CategoryType.GOOGLE)); - Category category = getCategory(response.getId()); - + Category category = categoryRepository.getById(response.getId()); externalCategoryDetailRepository.save(new ExternalCategoryDetail(category, request.getExternalId())); - return response; } - private void validateDuplicateExternalCategory(final String externalId, final List categories) { - List externalCategories = categories.stream() - .filter(Category::isExternal) - .collect(Collectors.toList()); - - if (!externalCategories.isEmpty() - && externalCategoryDetailRepository.existsByExternalIdAndCategoryIn(externalId, externalCategories)) { - throw new DuplicatedExternalCategoryException(); - } - } - public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { List categories = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 9c21ec47..039a091b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -20,11 +20,13 @@ Slice findByNameContainingAndCategoryType(final String name, final Cat + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") Slice findByMemberIdLikeCategoryName(final Long memberId, final String name, final Pageable pageable); - List findByMemberId(Long memberId); + List findByMemberIdAndCategoryType(final Long memberId, final CategoryType categoryType); - boolean existsByIdAndMemberId(Long id, Long memberId); + List findByMemberId(final Long memberId); - void deleteByMemberId(Long memberId); + boolean existsByIdAndMemberId(final Long id, final Long memberId); + + void deleteByMemberId(final Long memberId); default Category getById(final Long id) { return this.findById(id) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java index 767a66ff..ea18b7da 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.category.domain; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; @@ -11,4 +12,10 @@ public interface ExternalCategoryDetailRepository extends JpaRepository categories); void deleteByCategoryId(final Long categoryId); + + default void validateExistCategory(final String externalId, final List externalCategories) { + if (existsByExternalIdAndCategoryIn(externalId, externalCategories)) { + throw new ExistExternalCategoryException(); + } + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/exception/DuplicatedExternalCategoryException.java b/backend/src/main/java/com/allog/dallog/domain/category/exception/DuplicatedExternalCategoryException.java deleted file mode 100644 index 9bfe6a77..00000000 --- a/backend/src/main/java/com/allog/dallog/domain/category/exception/DuplicatedExternalCategoryException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.allog.dallog.domain.category.exception; - -public class DuplicatedExternalCategoryException extends RuntimeException { - - public DuplicatedExternalCategoryException(final String message) { - super(message); - } - - public DuplicatedExternalCategoryException() { - this("이미 저장된 연동 카테고리입니다."); - } -} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/exception/ExistExternalCategoryException.java b/backend/src/main/java/com/allog/dallog/domain/category/exception/ExistExternalCategoryException.java new file mode 100644 index 00000000..748fbaeb --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/exception/ExistExternalCategoryException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.category.exception; + +public class ExistExternalCategoryException extends RuntimeException { + + public ExistExternalCategoryException(final String message) { + super(message); + } + + public ExistExternalCategoryException() { + this("이미 저장된 연동 카테고리입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/global/error/ControllerAdvice.java b/backend/src/main/java/com/allog/dallog/global/error/ControllerAdvice.java index d023ac4e..fc557f2d 100644 --- a/backend/src/main/java/com/allog/dallog/global/error/ControllerAdvice.java +++ b/backend/src/main/java/com/allog/dallog/global/error/ControllerAdvice.java @@ -4,7 +4,7 @@ import com.allog.dallog.domain.auth.exception.InvalidTokenException; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.auth.exception.NoSuchOAuthTokenException; -import com.allog.dallog.domain.category.exception.DuplicatedExternalCategoryException; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.exception.InvalidMemberException; @@ -41,7 +41,7 @@ public class ControllerAdvice { InvalidScheduleException.class, InvalidSubscriptionException.class, ExistSubscriptionException.class, - DuplicatedExternalCategoryException.class + ExistExternalCategoryException.class }) public ResponseEntity handleInvalidData(final RuntimeException e) { ErrorResponse errorResponse = new ErrorResponse(e.getMessage()); diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index b49100b5..61d512e0 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -36,7 +36,7 @@ import com.allog.dallog.domain.category.dto.request.CategoryUpdateRequest; import com.allog.dallog.domain.category.dto.response.CategoriesResponse; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.category.exception.DuplicatedExternalCategoryException; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.application.MemberService; @@ -149,7 +149,7 @@ class CategoryServiceTest extends ServiceTest { // then assertThatThrownBy(() -> categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청)) - .isInstanceOf(DuplicatedExternalCategoryException.class); + .isInstanceOf(ExistExternalCategoryException.class); } @DisplayName("페이지와 제목을 받아 해당하는 구간의 카테고리를 가져온다.") diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java index 85dcd11f..3cf5061b 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -23,7 +23,7 @@ import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; -import com.allog.dallog.domain.category.exception.DuplicatedExternalCategoryException; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import com.allog.dallog.domain.composition.application.CategorySubscriptionService; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarService; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; @@ -107,7 +107,7 @@ class ExternalCalendarControllerTest extends ControllerTest { @Test void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { // given - willThrow(new DuplicatedExternalCategoryException()) + willThrow(new ExistExternalCategoryException()) .given(categorySubscriptionService) .save(any(), any(ExternalCategoryCreateRequest.class)); From 79d72d741056dc99cff408069474a0a813871b3c Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 14:53:08 +0900 Subject: [PATCH 042/148] =?UTF-8?q?refactor:=20CategoryService=EC=9D=98=20?= =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=A1=B0=ED=9A=8C=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 20 +++++++++-------- .../domain/category/domain/Category.java | 2 +- .../category/domain/CategoryRepository.java | 2 +- .../domain/category/domain/CategoryType.java | 2 +- .../dto/response/CategoriesResponse.java | 4 ++-- .../presentation/CategoryController.java | 8 +++---- .../common/fixtures/CategoryFixtures.java | 22 +++++++++---------- .../fixtures/IntegrationScheduleFixtures.java | 4 ++-- .../application/CategoryServiceTest.java | 12 +++++----- .../domain/CategoryRepositoryTest.java | 12 +++++----- .../domain/IntegrationScheduleTest.java | 14 ++++++------ .../domain/IntegrationSchedulesTest.java | 18 +++++++-------- .../presentation/CategoryControllerTest.java | 12 +++++----- 13 files changed, 67 insertions(+), 65 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index 72b19753..ffaa5085 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -1,6 +1,6 @@ package com.allog.dallog.domain.category.application; -import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; +import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; @@ -13,7 +13,6 @@ import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; import com.allog.dallog.domain.category.dto.response.CategoriesResponse; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.domain.Member; @@ -58,31 +57,34 @@ public CategoryResponse save(final Long memberId, final CategoryCreateRequest re @Transactional public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { - List externalCategories = categoryRepository.findByMemberIdAndCategoryType(memberId, CategoryType.GOOGLE); + List externalCategories = categoryRepository.findByMemberIdAndCategoryType(memberId, + CategoryType.GOOGLE); externalCategoryDetailRepository.validateExistCategory(request.getExternalId(), externalCategories); CategoryResponse response = save(memberId, new CategoryCreateRequest(request.getName(), CategoryType.GOOGLE)); Category category = categoryRepository.getById(response.getId()); externalCategoryDetailRepository.save(new ExternalCategoryDetail(category, request.getExternalId())); + return response; } - public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { + public CategoriesResponse findPublicByName(final String name, final Pageable pageable) { List categories - = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); + = categoryRepository.findByNameContainingAndCategoryType(name, PUBLIC, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } - public CategoriesResponse findMineByName(final Long memberId, final String name, final Pageable pageable) { - List categories = categoryRepository.findByMemberIdLikeCategoryName(memberId, name, pageable) - .getContent(); + public CategoriesResponse findMyCategories(final Long memberId, final String name, final Pageable pageable) { + List categories + = categoryRepository.findByNameContainingAndMemberId(name, memberId, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } public CategoryResponse findById(final Long id) { - return new CategoryResponse(getCategory(id)); + Category category = categoryRepository.getById(id); + return new CategoryResponse(category); } public Category getCategory(final Long id) { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index 800b9c33..58eac298 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -48,7 +48,7 @@ public Category(final String name, final Member member) { validateNameLength(name); this.name = name; this.member = member; - this.categoryType = CategoryType.NORMAL; + this.categoryType = CategoryType.PUBLIC; } public Category(final String name, final Member member, final CategoryType categoryType) { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 039a091b..b4e1060b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -18,7 +18,7 @@ Slice findByNameContainingAndCategoryType(final String name, final Cat @Query("SELECT c " + "FROM Category c " + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") - Slice findByMemberIdLikeCategoryName(final Long memberId, final String name, final Pageable pageable); + Slice findByNameContainingAndMemberId(final String name, final Long memberId, final Pageable pageable); List findByMemberIdAndCategoryType(final Long memberId, final CategoryType categoryType); diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java index 87060ada..86166563 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java @@ -4,7 +4,7 @@ public enum CategoryType { - NORMAL, PERSONAL, GOOGLE; + PUBLIC, PERSONAL, GOOGLE; public static CategoryType from(final String value) { try { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java index 71e99893..2d4451e8 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java @@ -14,10 +14,10 @@ public CategoriesResponse() { public CategoriesResponse(final int page, final List categories) { this.page = page; - this.categories = convertToResponses(categories); + this.categories = toDtos(categories); } - private List convertToResponses(final List categories) { + private List toDtos(final List categories) { return categories.stream() .map(CategoryResponse::new) .collect(Collectors.toList()); diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java index 84e91f5c..d587b071 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -43,9 +43,9 @@ public ResponseEntity save(@AuthenticationPrincipal final Logi } @GetMapping - public ResponseEntity findByName(@RequestParam(defaultValue = "") final String name, - final Pageable pageable) { - return ResponseEntity.ok(categoryService.findNormalByName(name, pageable)); + public ResponseEntity findPublicByName(@RequestParam(defaultValue = "") final String name, + final Pageable pageable) { + return ResponseEntity.ok(categoryService.findPublicByName(name, pageable)); } @GetMapping("/{categoryId}") @@ -57,7 +57,7 @@ public ResponseEntity findById(@PathVariable final Long catego public ResponseEntity findMyCategories(@AuthenticationPrincipal final LoginMember loginMember, @RequestParam(defaultValue = "") final String name, final Pageable pageable) { - return ResponseEntity.ok(categoryService.findMineByName(loginMember.getId(), name, pageable)); + return ResponseEntity.ok(categoryService.findMyCategories(loginMember.getId(), name, pageable)); } @PatchMapping("/{categoryId}") diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java index 24e71dc3..be50e9bb 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java @@ -1,7 +1,7 @@ package com.allog.dallog.common.fixtures; import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; -import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; +import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; import com.allog.dallog.domain.category.domain.Category; @@ -15,23 +15,23 @@ public class CategoryFixtures { /* 공통 일정 카테고리 */ public static final String 공통_일정_이름 = "공통 일정"; - public static final CategoryCreateRequest 공통_일정_생성_요청 = new CategoryCreateRequest(공통_일정_이름, NORMAL); + public static final CategoryCreateRequest 공통_일정_생성_요청 = new CategoryCreateRequest(공통_일정_이름, PUBLIC); /* BE 일정 카테고리 */ public static final String BE_일정_이름 = "BE 일정"; - public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, NORMAL); + public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, PUBLIC); /* FE 일정 카테고리 */ public static final String FE_일정_이름 = "FE 일정"; - public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, NORMAL); + public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, PUBLIC); /* 매트 아고라 카테고리 */ public static final String 매트_아고라_이름 = "매트 아고라"; - public static final CategoryCreateRequest 매트_아고라_생성_요청 = new CategoryCreateRequest(매트_아고라_이름, NORMAL); + public static final CategoryCreateRequest 매트_아고라_생성_요청 = new CategoryCreateRequest(매트_아고라_이름, PUBLIC); /* 후디 JPA 스터디 카테고리 */ public static final String 후디_JPA_스터디_이름 = "후디 JPA 스터디"; - public static final CategoryCreateRequest 후디_JPA_스터디_생성_요청 = new CategoryCreateRequest(후디_JPA_스터디_이름, NORMAL); + public static final CategoryCreateRequest 후디_JPA_스터디_생성_요청 = new CategoryCreateRequest(후디_JPA_스터디_이름, PUBLIC); /* 내 일정 카테고리 */ public static final String 내_일정_이름 = "내 일정"; @@ -70,22 +70,22 @@ public class CategoryFixtures { } public static CategoryResponse 공통_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(1L, 공통_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(1L, 공통_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse BE_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(2L, BE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(2L, BE_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse FE_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(3L, FE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(3L, FE_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse 매트_아고라_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(4L, 매트_아고라_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(4L, 매트_아고라_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse 후디_JPA_스터디_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(5L, 후디_JPA_스터디_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(5L, 후디_JPA_스터디_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); } } diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java index c6d7005f..4bb1820a 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java @@ -9,11 +9,11 @@ public class IntegrationScheduleFixtures { public static final IntegrationSchedule 점심_식사 = new IntegrationSchedule("1", 2L, "점심 식사", new Period(LocalDateTime.of(2022, 8, 16, 11, 00), LocalDateTime.of(2022, 8, 16, 13, 00)), "", - CategoryType.NORMAL.name()); + CategoryType.PUBLIC.name()); public static final IntegrationSchedule 달록_여행 = new IntegrationSchedule("2", 2L, "달록 여행", new Period(LocalDateTime.of(2022, 8, 24, 00, 00), LocalDateTime.of(2022, 8, 25, 23, 59)), "", - CategoryType.NORMAL.name()); + CategoryType.PUBLIC.name()); public static final IntegrationSchedule 레벨3_방학 = new IntegrationSchedule("gsgadfgqwrtqwerfgasdasdasd", 1L, "레벨3 방학", new Period(LocalDateTime.of(2022, 8, 20, 00, 00), LocalDateTime.of(2022, 8, 20, 00, 00)), "", diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 61d512e0..3515336e 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -21,7 +21,7 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_생성_요청; import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; -import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; +import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -113,7 +113,7 @@ class CategoryServiceTest extends ServiceTest { @ValueSource(strings = {"", "일이삼사오육칠팔구십일이삼사오육칠팔구십일", "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 카테고리"}) void 새로운_카테고리를_생성_할_때_이름이_공백이거나_길이가_20을_초과하는_경우_예외를_던진다(final String name) { // given - CategoryCreateRequest request = new CategoryCreateRequest(name, NORMAL); + CategoryCreateRequest request = new CategoryCreateRequest(name, PUBLIC); Member 관리자 = memberRepository.save(관리자()); // when & then @@ -167,7 +167,7 @@ class CategoryServiceTest extends ServiceTest { PageRequest request = PageRequest.of(0, 3); // when - CategoriesResponse response = categoryService.findNormalByName("일", request); + CategoriesResponse response = categoryService.findPublicByName("일", request); // then assertThat(response.getCategories()) @@ -184,7 +184,7 @@ class CategoryServiceTest extends ServiceTest { authService.generateToken(리버_인증_코드_토큰_요청()); // when - CategoriesResponse response = categoryService.findNormalByName("", PageRequest.of(0, 10)); + CategoriesResponse response = categoryService.findPublicByName("", PageRequest.of(0, 10)); // then assertThat(response.getCategories()).hasSize(0); @@ -205,7 +205,7 @@ class CategoryServiceTest extends ServiceTest { PageRequest request = PageRequest.of(1, 2); // when - CategoriesResponse response = categoryService.findMineByName(관리자_ID, "", request); + CategoriesResponse response = categoryService.findMyCategories(관리자_ID, "", request); // then assertThat(response.getCategories()) @@ -229,7 +229,7 @@ class CategoryServiceTest extends ServiceTest { PageRequest request = PageRequest.of(0, 3); // when - CategoriesResponse response = categoryService.findMineByName(관리자_ID, "일", request); + CategoriesResponse response = categoryService.findMyCategories(관리자_ID, "일", request); // then assertThat(response.getCategories()) diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java index c2be8d78..4cf0446d 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java @@ -6,13 +6,13 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_이름; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_이름; -import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.매트_아고라; +import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.후디_JPA_스터디; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; -import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; +import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -51,7 +51,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 5); // when - Slice actual = categoryRepository.findByNameContainingAndCategoryType("일", NORMAL, pageRequest); + Slice actual = categoryRepository.findByNameContainingAndCategoryType("일", PUBLIC, pageRequest); // then assertThat(actual.getContent()).hasSize(3) @@ -73,7 +73,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 5); // when - Slice actual = categoryRepository.findByNameContainingAndCategoryType("파랑", NORMAL, pageRequest); + Slice actual = categoryRepository.findByNameContainingAndCategoryType("파랑", PUBLIC, pageRequest); // then assertThat(actual.getContent()).hasSize(0); @@ -96,7 +96,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 8); // when - Slice categories = categoryRepository.findByMemberIdLikeCategoryName(관리자.getId(), "일", pageRequest); + Slice categories = categoryRepository.findByNameContainingAndMemberId("일", 관리자.getId(), pageRequest); // then assertAll(() -> { @@ -182,7 +182,7 @@ class CategoryRepositoryTest extends RepositoryTest { categoryRepository.deleteByMemberId(관리자.getId()); // then - assertThat(categoryRepository.findByMemberIdLikeCategoryName(관리자.getId(), "", pageRequest)) + assertThat(categoryRepository.findByNameContainingAndMemberId("", 관리자.getId(), pageRequest)) .hasSize(0); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java index 213af9a4..af372161 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java @@ -24,7 +24,7 @@ class IntegrationScheduleTest { // when & then assertDoesNotThrow( () -> new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모, - CategoryType.NORMAL.name())); + CategoryType.PUBLIC.name())); } @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 다르면 true를 반환한다.") @@ -35,7 +35,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isLongTerms(); @@ -52,7 +52,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isLongTerms(); @@ -69,7 +69,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isAllDays(); @@ -86,7 +86,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isAllDays(); @@ -103,7 +103,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isFewHours(); @@ -120,7 +120,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); // when boolean actual = integrationSchedule.isFewHours(); diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java index 6e0a05f1..0565c034 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java @@ -16,15 +16,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", CategoryType.PUBLIC.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 3, 0, 0), - LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", CategoryType.PUBLIC.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", CategoryType.PUBLIC.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -45,15 +45,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.PUBLIC.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", CategoryType.PUBLIC.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", CategoryType.PUBLIC.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -74,15 +74,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "가", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.PUBLIC.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "나", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", CategoryType.PUBLIC.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "다", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", CategoryType.PUBLIC.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index f29ae8bc..6a51c403 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -12,7 +12,7 @@ import static com.allog.dallog.common.fixtures.MemberFixtures.매트; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.MemberFixtures.후디_응답; -import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; +import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willDoNothing; @@ -115,7 +115,7 @@ class CategoryControllerTest extends ControllerTest { @Test void 잘못된_이름_형식으로_카테고리를_생성하면_400_Bad_Request가_발생한다() throws Exception { // given - CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, NORMAL); + CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, PUBLIC); willThrow(new InvalidCategoryException(CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE)) .given(categorySubscriptionService) @@ -148,7 +148,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자()), 후디_JPA_스터디(후디()), 매트_아고라(매트())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); + given(categoryService.findPublicByName(any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories?page={page}&size={size}", page, size) @@ -177,7 +177,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(BE_일정(관리자()), FE_일정(관리자())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); + given(categoryService.findPublicByName(any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories?name={name}&page={page}&size={size}", "E", page, size) @@ -207,7 +207,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findMineByName(any(), any(), any())).willReturn(categoriesResponse); + given(categoryService.findMyCategories(any(), any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories/me?name={name}&page={page}&size={size}", "", page, size) @@ -238,7 +238,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findMineByName(any(), any(), any())).willReturn(categoriesResponse); + given(categoryService.findMyCategories(any(), any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories/me?name={name}&page={page}&size={size}", "E", page, size) From 6a9463fa100a20c04f1a99965ae41e24a264e05d Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 17:07:50 +0900 Subject: [PATCH 043/148] =?UTF-8?q?refactor:=20CategoryService=EC=9D=98=20?= =?UTF-8?q?update(),=20delete()=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 55 ++++++------------- .../category/domain/CategoryRepository.java | 7 +++ .../ExternalCategoryDetailRepository.java | 2 +- .../application/SubscriptionService.java | 6 +- .../domain/SubscriptionRepository.java | 4 +- .../domain/SubscriptionRepositoryTest.java | 4 +- 6 files changed, 33 insertions(+), 45 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index ffaa5085..6916a8ff 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -2,7 +2,6 @@ import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; -import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.category.domain.CategoryType; @@ -57,9 +56,10 @@ public CategoryResponse save(final Long memberId, final CategoryCreateRequest re @Transactional public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { - List externalCategories = categoryRepository.findByMemberIdAndCategoryType(memberId, - CategoryType.GOOGLE); - externalCategoryDetailRepository.validateExistCategory(request.getExternalId(), externalCategories); + List externalCategories = categoryRepository + .findByMemberIdAndCategoryType(memberId, CategoryType.GOOGLE); + externalCategoryDetailRepository + .validateExistByExternalIdAndCategoryIn(request.getExternalId(), externalCategories); CategoryResponse response = save(memberId, new CategoryCreateRequest(request.getName(), CategoryType.GOOGLE)); Category category = categoryRepository.getById(response.getId()); @@ -93,41 +93,28 @@ public Category getCategory(final Long id) { } @Transactional - public void update(final Long memberId, final Long categoryId, final CategoryUpdateRequest request) { - Category category = getCategory(categoryId); - validatePermission(memberId, categoryId); - + public void update(final Long memberId, final Long id, final CategoryUpdateRequest request) { + categoryRepository.validateExistsByIdAndMemberId(id, memberId); + Category category = categoryRepository.getById(id); category.changeName(request.getName()); } @Transactional - public void deleteById(final Long memberId, final Long categoryId) { - validateCategoryExisting(categoryId); - validatePermission(memberId, categoryId); - validatePersonalCategory(categoryId); - - scheduleRepository.deleteByCategoryIdIn(List.of(categoryId)); - subscriptionRepository.deleteByCategoryIdIn(List.of(categoryId)); - externalCategoryDetailRepository.deleteByCategoryId(categoryId); - categoryRepository.deleteById(categoryId); - } + public void deleteById(final Long memberId, final Long id) { + categoryRepository.validateExistsByIdAndMemberId(id, memberId); + Category category = categoryRepository.getById(id); - private void validatePersonalCategory(final Long categoryId) { - Category category = getCategory(categoryId); - if (category.isPersonal()) { - throw new InvalidCategoryException("내 일정 카테고리는 삭제할 수 없습니다."); - } - } + validateNotPersonalCategory(category); - private void validateCategoryExisting(final Long categoryId) { - if (!categoryRepository.existsById(categoryId)) { - throw new NoSuchCategoryException("존재하지 않는 카테고리를 삭제할 수 없습니다."); - } + scheduleRepository.deleteByCategoryIdIn(List.of(id)); + subscriptionRepository.deleteByCategoryIdIn(List.of(id)); + externalCategoryDetailRepository.deleteByCategoryId(id); + categoryRepository.deleteById(id); } - private void validatePermission(final Long memberId, final Long categoryId) { - if (!categoryRepository.existsByIdAndMemberId(categoryId, memberId)) { - throw new NoPermissionException(); + private void validateNotPersonalCategory(final Category category) { + if (category.isPersonal()) { + throw new InvalidCategoryException("내 일정 카테고리는 삭제할 수 없습니다."); } } @@ -142,10 +129,4 @@ public void deleteByMemberId(final Long memberId) { subscriptionRepository.deleteByCategoryIdIn(categoryIds); categoryRepository.deleteByMemberId(memberId); } - - public void validateCreatorBy(final Long memberId, final Category category) { - if (!category.isCreatorId(memberId)) { - throw new NoPermissionException(); - } - } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index b4e1060b..d9ddfe41 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.category.domain; +import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import java.util.List; import org.springframework.data.domain.Pageable; @@ -32,4 +33,10 @@ default Category getById(final Long id) { return this.findById(id) .orElseThrow(NoSuchCategoryException::new); } + + default void validateExistsByIdAndMemberId(final Long id, final Long memberId) { + if (!existsByIdAndMemberId(id, memberId)) { + throw new NoPermissionException(); + } + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java index ea18b7da..1a97784f 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java @@ -13,7 +13,7 @@ public interface ExternalCategoryDetailRepository extends JpaRepository externalCategories) { + default void validateExistByExternalIdAndCategoryIn(final String externalId, final List externalCategories) { if (existsByExternalIdAndCategoryIn(externalId, externalCategories)) { throw new ExistExternalCategoryException(); } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 6c1aad93..1183aea7 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -47,7 +47,7 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { } public SubscriptionsResponse findByMemberId(final Long memberId) { - List subscriptions = subscriptionRepository.getByMemberId(memberId); + List subscriptions = subscriptionRepository.findByMemberId(memberId); List subscriptionResponses = subscriptions.stream() .map(SubscriptionResponse::new) @@ -62,7 +62,7 @@ public SubscriptionResponse findById(final Long id) { } public List findByCategoryId(final Long categoryId) { - return subscriptionRepository.getByCategoryId(categoryId) + return subscriptionRepository.findByCategoryId(categoryId) .stream() .map(SubscriptionResponse::new) .collect(Collectors.toList()); @@ -70,7 +70,7 @@ public List findByCategoryId(final Long categoryId) { // TODO: 상위 Service인 CalanderService에서만 사용하는 메서드입니다. 삭제 예정 public List getAllByMemberId(final Long memberId) { - return subscriptionRepository.getByMemberId(memberId); + return subscriptionRepository.findByMemberId(memberId); } @Transactional diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index 758b1b88..5707f1e4 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -14,9 +14,9 @@ public interface SubscriptionRepository extends JpaRepository categoryIds); - List getByMemberId(final Long memberId); + List findByMemberId(final Long memberId); - List getByCategoryId(final Long categoryId); + List findByCategoryId(final Long categoryId); default Subscription getById(final Long id) { return findById(id) diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java index 2dc32726..83221971 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java @@ -83,7 +83,7 @@ class SubscriptionRepositoryTest extends RepositoryTest { subscriptionRepository.save(색상3_구독(후디, FE_일정)); // when - List subscriptions = subscriptionRepository.getByMemberId(후디.getId()); + List subscriptions = subscriptionRepository.findByMemberId(후디.getId()); // then assertThat(subscriptions).hasSize(3); @@ -96,7 +96,7 @@ class SubscriptionRepositoryTest extends RepositoryTest { Member 관리자 = memberRepository.save(관리자()); // when - List subscriptions = subscriptionRepository.getByMemberId(관리자.getId()); + List subscriptions = subscriptionRepository.findByMemberId(관리자.getId()); // then assertThat(subscriptions).isEmpty(); From c431dd98e780ba45e6bf53e613d3b62065e0b82c Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 17:51:14 +0900 Subject: [PATCH 044/148] =?UTF-8?q?refactor:=20ScheduleService=EC=97=90=20?= =?UTF-8?q?=EB=8C=80=ED=95=9C=20=ED=94=BC=EB=93=9C=EB=B0=B1=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 24 +------- .../dto/request/CategoryCreateRequest.java | 6 ++ .../domain/schedule/domain/Schedule.java | 17 +++--- .../presentation/CategoryController.java | 2 +- .../application/CategoryServiceTest.java | 58 ++++--------------- .../application/CalendarServiceTest.java | 8 ++- .../presentation/CategoryControllerTest.java | 4 +- 7 files changed, 36 insertions(+), 83 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index 6916a8ff..f1aa063c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -13,13 +13,11 @@ import com.allog.dallog.domain.category.dto.response.CategoriesResponse; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.InvalidCategoryException; -import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import java.util.List; -import java.util.stream.Collectors; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -48,8 +46,7 @@ public CategoryService(final CategoryRepository categoryRepository, @Transactional public CategoryResponse save(final Long memberId, final CategoryCreateRequest request) { Member member = memberRepository.getById(memberId); - Category category = new Category(request.getName(), member, - CategoryType.valueOf(request.getCategoryType().toUpperCase())); + Category category = request.toEntity(member); Category savedCategory = categoryRepository.save(category); return new CategoryResponse(savedCategory); } @@ -87,11 +84,6 @@ public CategoryResponse findById(final Long id) { return new CategoryResponse(category); } - public Category getCategory(final Long id) { - return categoryRepository.findById(id) - .orElseThrow(NoSuchCategoryException::new); - } - @Transactional public void update(final Long memberId, final Long id, final CategoryUpdateRequest request) { categoryRepository.validateExistsByIdAndMemberId(id, memberId); @@ -100,7 +92,7 @@ public void update(final Long memberId, final Long id, final CategoryUpdateReque } @Transactional - public void deleteById(final Long memberId, final Long id) { + public void delete(final Long memberId, final Long id) { categoryRepository.validateExistsByIdAndMemberId(id, memberId); Category category = categoryRepository.getById(id); @@ -117,16 +109,4 @@ private void validateNotPersonalCategory(final Category category) { throw new InvalidCategoryException("내 일정 카테고리는 삭제할 수 없습니다."); } } - - @Transactional - public void deleteByMemberId(final Long memberId) { - List categoryIds = categoryRepository.findByMemberId(memberId) - .stream() - .map(Category::getId) - .collect(Collectors.toList()); - - scheduleRepository.deleteByCategoryIdIn(categoryIds); - subscriptionRepository.deleteByCategoryIdIn(categoryIds); - categoryRepository.deleteByMemberId(memberId); - } } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java index d6bd4467..cab8befe 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java @@ -1,6 +1,8 @@ package com.allog.dallog.domain.category.dto.request; +import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryType; +import com.allog.dallog.domain.member.domain.Member; import javax.validation.constraints.NotBlank; public class CategoryCreateRequest { @@ -19,6 +21,10 @@ public CategoryCreateRequest(final String name, final CategoryType categoryType) this.categoryType = categoryType.name(); } + public Category toEntity(final Member member) { + return new Category(name, member, CategoryType.valueOf(categoryType.toUpperCase())); + } + public String getName() { return name; } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index c011f4c3..a6063a53 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -88,19 +88,18 @@ private void validateMemoLength(final String memo) { } public void validateUpdatePossible(final Long memberId) { - if (this.category.isExternal()) { - throw new NoPermissionException("외부 연동 카테고리에는 일정을 변경할 수 없습니다."); - } - if (!this.category.isCreatorId(memberId)) { - throw new NoPermissionException(); - } + validateModifyPossible(memberId); } public void validateDeletePossible(final Long memberId) { - if (this.category.isExternal()) { - throw new NoPermissionException("외부 연동 카테고리에는 일정을 삭제할 수 없습니다."); + validateModifyPossible(memberId); + } + + private void validateModifyPossible(final Long memberId) { + if (category.isExternal()) { + throw new NoPermissionException("외부 연동 카테고리에는 일정을 변경할 수 없습니다."); } - if (!this.category.isCreatorId(memberId)) { + if (!category.isCreatorId(memberId)) { throw new NoPermissionException(); } } diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java index d587b071..01b6cf46 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -71,7 +71,7 @@ public ResponseEntity update(@AuthenticationPrincipal final LoginMember lo @DeleteMapping("/{categoryId}") public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, @PathVariable final Long categoryId) { - categoryService.deleteById(loginMember.getId(), categoryId); + categoryService.delete(loginMember.getId(), categoryId); return ResponseEntity.noContent().build(); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 3515336e..70968a08 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -278,7 +278,7 @@ class CategoryServiceTest extends ServiceTest { // when categoryService.update(관리자.getId(), 공통_일정.getId(), categoryUpdateRequest); - Category category = categoryService.getCategory(공통_일정.getId()); + Category category = categoryRepository.getById(공통_일정.getId()); //then assertThat(category.getName()).isEqualTo(우테코_공통_일정_이름); @@ -294,7 +294,7 @@ class CategoryServiceTest extends ServiceTest { // when & then assertThatThrownBy(() -> categoryService.update(관리자.getId(), 공통_일정.getId() + 1, categoryUpdateRequest)) - .isInstanceOf(NoSuchCategoryException.class); + .isInstanceOf(NoPermissionException.class); } @DisplayName("자신이 만들지 않은 카테고리를 수정할 경우 예외를 던진다.") @@ -320,10 +320,10 @@ class CategoryServiceTest extends ServiceTest { CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); // when - categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + categoryService.delete(관리자.getId(), 공통_일정.getId()); //then - assertThatThrownBy(() -> categoryService.getCategory(공통_일정.getId())) + assertThatThrownBy(() -> categoryRepository.getById(공통_일정.getId())) .isInstanceOf(NoSuchCategoryException.class); } @@ -335,8 +335,8 @@ class CategoryServiceTest extends ServiceTest { CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); // when & then - assertThatThrownBy(() -> categoryService.deleteById(관리자.getId(), 공통_일정.getId() + 1)) - .isInstanceOf(NoSuchCategoryException.class); + assertThatThrownBy(() -> categoryService.delete(관리자.getId(), 공통_일정.getId() + 1)) + .isInstanceOf(NoPermissionException.class); } @DisplayName("자신이 만들지 않은 카테고리를 삭제할 경우 예외를 던진다.") @@ -349,7 +349,7 @@ class CategoryServiceTest extends ServiceTest { // when & then assertThatThrownBy( - () -> categoryService.deleteById(매트.getId(), 공통_일정.getId())) + () -> categoryService.delete(매트.getId(), 공통_일정.getId())) .isInstanceOf(NoPermissionException.class); } @@ -363,7 +363,7 @@ class CategoryServiceTest extends ServiceTest { ScheduleResponse 레벨_인터뷰 = scheduleService.save(관리자.getId(), 공통_일정.getId(), 레벨_인터뷰_생성_요청); // when - categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + categoryService.delete(관리자.getId(), 공통_일정.getId()); // then assertAll(() -> { @@ -385,7 +385,7 @@ class CategoryServiceTest extends ServiceTest { SubscriptionResponse 구독 = subscriptionService.save(후디.getId(), 공통_일정.getId()); // when - categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + categoryService.delete(관리자.getId(), 공통_일정.getId()); // then assertThatThrownBy(() -> subscriptionService.findById(구독.getId())) @@ -401,46 +401,10 @@ class CategoryServiceTest extends ServiceTest { subscriptionService.save(관리자.getId(), 내_일정.getId()); // when & then - assertThatThrownBy(() -> categoryService.deleteById(관리자.getId(), 내_일정.getId())) + assertThatThrownBy(() -> categoryService.delete(관리자.getId(), 내_일정.getId())) .isInstanceOf(InvalidCategoryException.class); } - @DisplayName("특정 회원의 카테고리를 전부 삭제한다.") - @Test - void 특정_회원의_카테고리를_전부_삭제한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - - // when - categoryService.deleteByMemberId(관리자.getId()); - - //then - assertThatThrownBy(() -> categoryService.getCategory(공통_일정.getId())) - .isInstanceOf(NoSuchCategoryException.class); - } - - @DisplayName("특정 회원의 카테고리를 삭제할 때 연관된 일정도 삭제한다.") - @Test - void 특정_회원의_카테고리를_삭제할_때_연관된_일정도_삭제한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); - ScheduleResponse 알록달록_회식 = scheduleService.save(관리자.getId(), 공통_일정.getId(), 알록달록_회식_생성_요청); - ScheduleResponse 레벨_인터뷰 = scheduleService.save(관리자.getId(), 공통_일정.getId(), 레벨_인터뷰_생성_요청); - - // when - categoryService.deleteByMemberId(관리자.getId()); - - // then - assertAll(() -> { - assertThatThrownBy(() -> scheduleService.findById(알록달록_회식.getId())) - .isInstanceOf(NoSuchScheduleException.class); - assertThatThrownBy(() -> scheduleService.findById(레벨_인터뷰.getId())) - .isInstanceOf(NoSuchScheduleException.class); - }); - } - @DisplayName("외부 캘린더의 카테고리를 삭제한다.") @Test void 외부_캘린더의_카테고리를_삭제한다() { @@ -449,7 +413,7 @@ class CategoryServiceTest extends ServiceTest { CategoryResponse 우아한테크코스_외부_일정 = categoryService.save(관리자.getId(), 우아한테크코스_외부_일정_생성_요청); // when - categoryService.deleteById(관리자.getId(), 우아한테크코스_외부_일정.getId()); + categoryService.delete(관리자.getId(), 우아한테크코스_외부_일정.getId()); // then assertThatThrownBy(() -> categoryService.findById(우아한테크코스_외부_일정.getId())) diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 0f5dafc0..395ee47f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -32,6 +32,7 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; @@ -64,6 +65,9 @@ class CalendarServiceTest extends ServiceTest { @Autowired private ScheduleService scheduleService; + @Autowired + private CategoryRepository categoryRepository; + @DisplayName("시작일시와 종료일시로 유저의 캘린더를 일정 유형에 따라 분류하고 정렬하여 반환한다.") @Test void 시작일시와_종료일시로_유저의_캘린더를_일정_유형에_따라_분류하고_정렬하여_반환한다() { @@ -71,7 +75,7 @@ class CalendarServiceTest extends ServiceTest { Long memberId = parseMemberId(MEMBER_인증_코드_토큰_요청()); CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); - Category BE_일정 = categoryService.getCategory(BE_일정_응답.getId()); + Category BE_일정 = categoryRepository.getById(BE_일정_응답.getId()); subscriptionService.save(memberId, BE_일정.getId()); @@ -106,7 +110,7 @@ class CalendarServiceTest extends ServiceTest { new ScheduleCreateRequest("몇시간 네번째", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_18시_0분, "")); CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(memberId, 우아한테크코스_외부_일정_생성_요청); - Category 우아한테크코스 = categoryService.getCategory(우아한테크코스_외부_일정_응답.getId()); + Category 우아한테크코스 = categoryRepository.getById(우아한테크코스_외부_일정_응답.getId()); externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); subscriptionService.save(memberId, 우아한테크코스.getId()); diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index 6a51c403..ef012a09 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -406,7 +406,7 @@ class CategoryControllerTest extends ControllerTest { Long categoryId = 1L; willDoNothing() .given(categoryService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(delete("/api/categories/{categoryId}", categoryId) @@ -434,7 +434,7 @@ class CategoryControllerTest extends ControllerTest { willThrow(new NoSuchCategoryException("존재하지 않는 카테고리를 삭제할 수 없습니다.")) .willDoNothing() .given(categoryService) - .deleteById(any(), any()); + .delete(any(), any()); // when & then mockMvc.perform(delete("/api/categories/{categoryId}", categoryId) From fabe13a06dbc71283b3770ed6e247f931da017c0 Mon Sep 17 00:00:00 2001 From: koo Date: Fri, 16 Sep 2022 17:32:34 +0900 Subject: [PATCH 045/148] =?UTF-8?q?refactor:=20Category=EC=99=80=20Service?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=A0=84=EC=B2=B4=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 8 +++---- .../domain/category/domain/Category.java | 2 +- .../category/domain/CategoryRepository.java | 2 +- .../domain/category/domain/CategoryType.java | 2 +- .../dto/request/CategoryCreateRequest.java | 2 +- .../dto/response/CategoriesResponse.java | 4 ++-- .../schedule/application/ScheduleService.java | 4 ++-- .../domain/schedule/domain/Schedule.java | 12 ++-------- .../presentation/CategoryController.java | 2 +- .../common/fixtures/CategoryFixtures.java | 22 +++++++++---------- .../fixtures/IntegrationScheduleFixtures.java | 4 ++-- .../application/CategoryServiceTest.java | 8 +++---- .../domain/CategoryRepositoryTest.java | 10 ++++----- .../domain/IntegrationScheduleTest.java | 14 ++++++------ .../domain/IntegrationSchedulesTest.java | 18 +++++++-------- .../presentation/CategoryControllerTest.java | 8 +++---- 16 files changed, 57 insertions(+), 65 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index f1aa063c..c05a261a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -1,6 +1,6 @@ package com.allog.dallog.domain.category.application; -import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; @@ -65,16 +65,16 @@ public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRe return response; } - public CategoriesResponse findPublicByName(final String name, final Pageable pageable) { + public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { List categories - = categoryRepository.findByNameContainingAndCategoryType(name, PUBLIC, pageable).getContent(); + = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } public CategoriesResponse findMyCategories(final Long memberId, final String name, final Pageable pageable) { List categories - = categoryRepository.findByNameContainingAndMemberId(name, memberId, pageable).getContent(); + = categoryRepository.findByMemberIdAndNameContaining(name, memberId, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java index 58eac298..800b9c33 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -48,7 +48,7 @@ public Category(final String name, final Member member) { validateNameLength(name); this.name = name; this.member = member; - this.categoryType = CategoryType.PUBLIC; + this.categoryType = CategoryType.NORMAL; } public Category(final String name, final Member member, final CategoryType categoryType) { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index d9ddfe41..8312585b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -19,7 +19,7 @@ Slice findByNameContainingAndCategoryType(final String name, final Cat @Query("SELECT c " + "FROM Category c " + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") - Slice findByNameContainingAndMemberId(final String name, final Long memberId, final Pageable pageable); + Slice findByMemberIdAndNameContaining(final String name, final Long memberId, final Pageable pageable); List findByMemberIdAndCategoryType(final Long memberId, final CategoryType categoryType); diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java index 86166563..87060ada 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java @@ -4,7 +4,7 @@ public enum CategoryType { - PUBLIC, PERSONAL, GOOGLE; + NORMAL, PERSONAL, GOOGLE; public static CategoryType from(final String value) { try { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java index cab8befe..6ce4a11c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java @@ -22,7 +22,7 @@ public CategoryCreateRequest(final String name, final CategoryType categoryType) } public Category toEntity(final Member member) { - return new Category(name, member, CategoryType.valueOf(categoryType.toUpperCase())); + return new Category(name, member, CategoryType.from(categoryType)); } public String getName() { diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java index 2d4451e8..e8162830 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java @@ -14,10 +14,10 @@ public CategoriesResponse() { public CategoriesResponse(final int page, final List categories) { this.page = page; - this.categories = toDtos(categories); + this.categories = toResponses(categories); } - private List toDtos(final List categories) { + private List toResponses(final List categories) { return categories.stream() .map(CategoryResponse::new) .collect(Collectors.toList()); diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index 3ca4e1c3..8c784381 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -44,14 +44,14 @@ public ScheduleResponse findById(final Long id) { @Transactional public void update(final Long id, final Long memberId, final ScheduleUpdateRequest request) { Schedule schedule = scheduleRepository.getById(id); - schedule.validateUpdatePossible(memberId); + schedule.validateEditablePossible(memberId); schedule.change(request.getTitle(), request.getStartDateTime(), request.getEndDateTime(), request.getMemo()); } @Transactional public void delete(final Long id, final Long memberId) { Schedule schedule = scheduleRepository.getById(id); - schedule.validateDeletePossible(memberId); + schedule.validateEditablePossible(memberId); scheduleRepository.deleteById(id); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index a6063a53..62207712 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -87,17 +87,9 @@ private void validateMemoLength(final String memo) { } } - public void validateUpdatePossible(final Long memberId) { - validateModifyPossible(memberId); - } - - public void validateDeletePossible(final Long memberId) { - validateModifyPossible(memberId); - } - - private void validateModifyPossible(final Long memberId) { + public void validateEditablePossible(final Long memberId) { if (category.isExternal()) { - throw new NoPermissionException("외부 연동 카테고리에는 일정을 변경할 수 없습니다."); + throw new NoPermissionException("외부 연동 카테고리의 일정은 변경할 수 없습니다."); } if (!category.isCreatorId(memberId)) { throw new NoPermissionException(); diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java index 01b6cf46..d504d3a0 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -45,7 +45,7 @@ public ResponseEntity save(@AuthenticationPrincipal final Logi @GetMapping public ResponseEntity findPublicByName(@RequestParam(defaultValue = "") final String name, final Pageable pageable) { - return ResponseEntity.ok(categoryService.findPublicByName(name, pageable)); + return ResponseEntity.ok(categoryService.findNormalByName(name, pageable)); } @GetMapping("/{categoryId}") diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java index be50e9bb..24e71dc3 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java @@ -1,7 +1,7 @@ package com.allog.dallog.common.fixtures; import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; -import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static com.allog.dallog.domain.category.domain.CategoryType.PERSONAL; import com.allog.dallog.domain.category.domain.Category; @@ -15,23 +15,23 @@ public class CategoryFixtures { /* 공통 일정 카테고리 */ public static final String 공통_일정_이름 = "공통 일정"; - public static final CategoryCreateRequest 공통_일정_생성_요청 = new CategoryCreateRequest(공통_일정_이름, PUBLIC); + public static final CategoryCreateRequest 공통_일정_생성_요청 = new CategoryCreateRequest(공통_일정_이름, NORMAL); /* BE 일정 카테고리 */ public static final String BE_일정_이름 = "BE 일정"; - public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, PUBLIC); + public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, NORMAL); /* FE 일정 카테고리 */ public static final String FE_일정_이름 = "FE 일정"; - public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, PUBLIC); + public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, NORMAL); /* 매트 아고라 카테고리 */ public static final String 매트_아고라_이름 = "매트 아고라"; - public static final CategoryCreateRequest 매트_아고라_생성_요청 = new CategoryCreateRequest(매트_아고라_이름, PUBLIC); + public static final CategoryCreateRequest 매트_아고라_생성_요청 = new CategoryCreateRequest(매트_아고라_이름, NORMAL); /* 후디 JPA 스터디 카테고리 */ public static final String 후디_JPA_스터디_이름 = "후디 JPA 스터디"; - public static final CategoryCreateRequest 후디_JPA_스터디_생성_요청 = new CategoryCreateRequest(후디_JPA_스터디_이름, PUBLIC); + public static final CategoryCreateRequest 후디_JPA_스터디_생성_요청 = new CategoryCreateRequest(후디_JPA_스터디_이름, NORMAL); /* 내 일정 카테고리 */ public static final String 내_일정_이름 = "내 일정"; @@ -70,22 +70,22 @@ public class CategoryFixtures { } public static CategoryResponse 공통_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(1L, 공통_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(1L, 공통_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse BE_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(2L, BE_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(2L, BE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse FE_일정_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(3L, FE_일정_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(3L, FE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse 매트_아고라_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(4L, 매트_아고라_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(4L, 매트_아고라_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } public static CategoryResponse 후디_JPA_스터디_응답(final MemberResponse creatorResponse) { - return new CategoryResponse(5L, 후디_JPA_스터디_이름, PUBLIC.name(), creatorResponse, LocalDateTime.now()); + return new CategoryResponse(5L, 후디_JPA_스터디_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } } diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java index 4bb1820a..c6d7005f 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java @@ -9,11 +9,11 @@ public class IntegrationScheduleFixtures { public static final IntegrationSchedule 점심_식사 = new IntegrationSchedule("1", 2L, "점심 식사", new Period(LocalDateTime.of(2022, 8, 16, 11, 00), LocalDateTime.of(2022, 8, 16, 13, 00)), "", - CategoryType.PUBLIC.name()); + CategoryType.NORMAL.name()); public static final IntegrationSchedule 달록_여행 = new IntegrationSchedule("2", 2L, "달록 여행", new Period(LocalDateTime.of(2022, 8, 24, 00, 00), LocalDateTime.of(2022, 8, 25, 23, 59)), "", - CategoryType.PUBLIC.name()); + CategoryType.NORMAL.name()); public static final IntegrationSchedule 레벨3_방학 = new IntegrationSchedule("gsgadfgqwrtqwerfgasdasdasd", 1L, "레벨3 방학", new Period(LocalDateTime.of(2022, 8, 20, 00, 00), LocalDateTime.of(2022, 8, 20, 00, 00)), "", diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 70968a08..dca0fd1c 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -21,7 +21,7 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_생성_요청; import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; -import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -113,7 +113,7 @@ class CategoryServiceTest extends ServiceTest { @ValueSource(strings = {"", "일이삼사오육칠팔구십일이삼사오육칠팔구십일", "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 카테고리"}) void 새로운_카테고리를_생성_할_때_이름이_공백이거나_길이가_20을_초과하는_경우_예외를_던진다(final String name) { // given - CategoryCreateRequest request = new CategoryCreateRequest(name, PUBLIC); + CategoryCreateRequest request = new CategoryCreateRequest(name, NORMAL); Member 관리자 = memberRepository.save(관리자()); // when & then @@ -167,7 +167,7 @@ class CategoryServiceTest extends ServiceTest { PageRequest request = PageRequest.of(0, 3); // when - CategoriesResponse response = categoryService.findPublicByName("일", request); + CategoriesResponse response = categoryService.findNormalByName("일", request); // then assertThat(response.getCategories()) @@ -184,7 +184,7 @@ class CategoryServiceTest extends ServiceTest { authService.generateToken(리버_인증_코드_토큰_요청()); // when - CategoriesResponse response = categoryService.findPublicByName("", PageRequest.of(0, 10)); + CategoriesResponse response = categoryService.findNormalByName("", PageRequest.of(0, 10)); // then assertThat(response.getCategories()).hasSize(0); diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java index 4cf0446d..aeccfab8 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java @@ -12,7 +12,7 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.후디_JPA_스터디; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; -import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -51,7 +51,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 5); // when - Slice actual = categoryRepository.findByNameContainingAndCategoryType("일", PUBLIC, pageRequest); + Slice actual = categoryRepository.findByNameContainingAndCategoryType("일", NORMAL, pageRequest); // then assertThat(actual.getContent()).hasSize(3) @@ -73,7 +73,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 5); // when - Slice actual = categoryRepository.findByNameContainingAndCategoryType("파랑", PUBLIC, pageRequest); + Slice actual = categoryRepository.findByNameContainingAndCategoryType("파랑", NORMAL, pageRequest); // then assertThat(actual.getContent()).hasSize(0); @@ -96,7 +96,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 8); // when - Slice categories = categoryRepository.findByNameContainingAndMemberId("일", 관리자.getId(), pageRequest); + Slice categories = categoryRepository.findByMemberIdAndNameContaining("일", 관리자.getId(), pageRequest); // then assertAll(() -> { @@ -182,7 +182,7 @@ class CategoryRepositoryTest extends RepositoryTest { categoryRepository.deleteByMemberId(관리자.getId()); // then - assertThat(categoryRepository.findByNameContainingAndMemberId("", 관리자.getId(), pageRequest)) + assertThat(categoryRepository.findByMemberIdAndNameContaining("", 관리자.getId(), pageRequest)) .hasSize(0); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java index af372161..213af9a4 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java @@ -24,7 +24,7 @@ class IntegrationScheduleTest { // when & then assertDoesNotThrow( () -> new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모, - CategoryType.PUBLIC.name())); + CategoryType.NORMAL.name())); } @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 다르면 true를 반환한다.") @@ -35,7 +35,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isLongTerms(); @@ -52,7 +52,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isLongTerms(); @@ -69,7 +69,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isAllDays(); @@ -86,7 +86,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isAllDays(); @@ -103,7 +103,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isFewHours(); @@ -120,7 +120,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); // when boolean actual = integrationSchedule.isFewHours(); diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java index 0565c034..6e0a05f1 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java @@ -16,15 +16,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", CategoryType.NORMAL.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 3, 0, 0), - LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", CategoryType.NORMAL.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", CategoryType.NORMAL.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -45,15 +45,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", CategoryType.NORMAL.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", CategoryType.NORMAL.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -74,15 +74,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "가", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "나", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", CategoryType.NORMAL.name()); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "다", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", CategoryType.PUBLIC.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", CategoryType.NORMAL.name()); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index ef012a09..60c944ed 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -12,7 +12,7 @@ import static com.allog.dallog.common.fixtures.MemberFixtures.매트; import static com.allog.dallog.common.fixtures.MemberFixtures.후디; import static com.allog.dallog.common.fixtures.MemberFixtures.후디_응답; -import static com.allog.dallog.domain.category.domain.CategoryType.PUBLIC; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.willDoNothing; @@ -115,7 +115,7 @@ class CategoryControllerTest extends ControllerTest { @Test void 잘못된_이름_형식으로_카테고리를_생성하면_400_Bad_Request가_발생한다() throws Exception { // given - CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, PUBLIC); + CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, NORMAL); willThrow(new InvalidCategoryException(CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE)) .given(categorySubscriptionService) @@ -148,7 +148,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자()), 후디_JPA_스터디(후디()), 매트_아고라(매트())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findPublicByName(any(), any())).willReturn(categoriesResponse); + given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories?page={page}&size={size}", page, size) @@ -177,7 +177,7 @@ class CategoryControllerTest extends ControllerTest { List 일정_목록 = List.of(BE_일정(관리자()), FE_일정(관리자())); CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); - given(categoryService.findPublicByName(any(), any())).willReturn(categoriesResponse); + given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); // when & then mockMvc.perform(get("/api/categories?name={name}&page={page}&size={size}", "E", page, size) From cbea24ce526fdc171b462b6c29af118a3516118b Mon Sep 17 00:00:00 2001 From: devHudi Date: Sun, 18 Sep 2022 13:27:37 +0900 Subject: [PATCH 046/148] =?UTF-8?q?chore:=20~/Desktop/jacoco/index.xml=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/~/Desktop/jacoco/index.xml | 1 - 1 file changed, 1 deletion(-) delete mode 100644 backend/~/Desktop/jacoco/index.xml diff --git a/backend/~/Desktop/jacoco/index.xml b/backend/~/Desktop/jacoco/index.xml deleted file mode 100644 index bde0fd01..00000000 --- a/backend/~/Desktop/jacoco/index.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 9170d975b17d564b894f40606aceb90eced71a1d Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Tue, 13 Sep 2022 20:21:30 +0900 Subject: [PATCH 047/148] =?UTF-8?q?refactor:=20dto=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8A=94=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/GoogleCalendarEventResponse.java | 43 +--------- .../dto/GoogleCalendarEventsResponse.java | 53 +----------- .../oauth/dto/GoogleCalendarListResponse.java | 27 +----- .../oauth/dto/GoogleCalendarResponse.java | 84 +------------------ .../oauth/dto/GoogleDateFormat.java | 8 +- .../oauth/dto/GoogleTokenResponse.java | 27 +----- 6 files changed, 8 insertions(+), 234 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java index cecbdbb0..93a91257 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java @@ -7,36 +7,23 @@ public class GoogleCalendarEventResponse { - private String kind; - private String etag; private String id; - private String status; - private String htmlLink; private String summary = ""; private String description = ""; - private String location; private GoogleDateFormat start; private GoogleDateFormat end; - private String recurringEventId; private GoogleCalendarEventResponse() { } - public GoogleCalendarEventResponse(final String kind, final String etag, final String id, final String status, - final String htmlLink, final String summary, final String description, - final String location, final GoogleDateFormat start, final GoogleDateFormat end, - final String recurringEventId) { - this.kind = kind; - this.etag = etag; + public GoogleCalendarEventResponse(final String id, final String summary, final String description, + final GoogleDateFormat start, + final GoogleDateFormat end) { this.id = id; - this.status = status; - this.htmlLink = htmlLink; this.summary = summary; this.description = description; - this.location = location; this.start = start; this.end = end; - this.recurringEventId = recurringEventId; } public LocalDateTime getStartDateTime() { @@ -55,26 +42,10 @@ public LocalDateTime getEndDateTime() { return LocalDateTime.of(LocalDate.parse(end.getDate()), LocalTime.MIN); } - public String getKind() { - return kind; - } - - public String getEtag() { - return etag; - } - public String getId() { return id; } - public String getStatus() { - return status; - } - - public String getHtmlLink() { - return htmlLink; - } - public String getSummary() { return summary; } @@ -83,10 +54,6 @@ public String getDescription() { return description; } - public String getLocation() { - return location; - } - public GoogleDateFormat getStart() { return start; } @@ -94,8 +61,4 @@ public GoogleDateFormat getStart() { public GoogleDateFormat getEnd() { return end; } - - public String getRecurringEventId() { - return recurringEventId; - } } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventsResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventsResponse.java index ffb3fcda..5f72f9b4 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventsResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventsResponse.java @@ -4,66 +4,15 @@ public class GoogleCalendarEventsResponse { - private String kind; - private String etag; - private String summary; - private String description; - private String timeZone; - private String accessRole; - private String nextPageToken; - private String nextSyncToken; private List items; private GoogleCalendarEventsResponse() { } - public GoogleCalendarEventsResponse(final String kind, final String etag, final String summary, - final String description, final String timeZone, final String accessRole, - final String nextPageToken, final String nextSyncToken, - final List items) { - this.kind = kind; - this.etag = etag; - this.summary = summary; - this.description = description; - this.timeZone = timeZone; - this.accessRole = accessRole; - this.nextPageToken = nextPageToken; - this.nextSyncToken = nextSyncToken; + public GoogleCalendarEventsResponse(final List items) { this.items = items; } - public String getKind() { - return kind; - } - - public String getEtag() { - return etag; - } - - public String getSummary() { - return summary; - } - - public String getDescription() { - return description; - } - - public String getTimeZone() { - return timeZone; - } - - public String getAccessRole() { - return accessRole; - } - - public String getNextPageToken() { - return nextPageToken; - } - - public String getNextSyncToken() { - return nextSyncToken; - } - public List getItems() { return items; } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarListResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarListResponse.java index 61d92a56..ee89e787 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarListResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarListResponse.java @@ -4,40 +4,15 @@ public class GoogleCalendarListResponse { - private String kind; - private String etag; - private String nextPageToken; - private String nextSyncToken; private List items; private GoogleCalendarListResponse() { } - public GoogleCalendarListResponse(final String kind, final String etag, final String nextPageToken, - final String nextSyncToken, final List items) { - this.kind = kind; - this.etag = etag; - this.nextPageToken = nextPageToken; - this.nextSyncToken = nextSyncToken; + public GoogleCalendarListResponse(final List items) { this.items = items; } - public String getKind() { - return kind; - } - - public String getEtag() { - return etag; - } - - public String getNextPageToken() { - return nextPageToken; - } - - public String getNextSyncToken() { - return nextSyncToken; - } - public List getItems() { return items; } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarResponse.java index d43cf782..17dd6305 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarResponse.java @@ -2,55 +2,17 @@ public class GoogleCalendarResponse { - private String kind; - private String etag; private String id; private String summary; private String description; - private String location; - private String timeZone; - private String summaryOverride; - private String colorId; - private String backgroundColor; - private String foregroundColor; - private boolean hidden; - private boolean selected; - private String accessRole; - private boolean primary; - private boolean deleted; private GoogleCalendarResponse() { } - public GoogleCalendarResponse(final String kind, final String etag, final String id, final String summary, - final String description, final String location, final String timeZone, - final String summaryOverride, final String colorId, final String backgroundColor, - final String foregroundColor, final boolean hidden, final boolean selected, - final String accessRole, final boolean primary, final boolean deleted) { - this.kind = kind; - this.etag = etag; + public GoogleCalendarResponse(final String id, final String summary, final String description) { this.id = id; this.summary = summary; this.description = description; - this.location = location; - this.timeZone = timeZone; - this.summaryOverride = summaryOverride; - this.colorId = colorId; - this.backgroundColor = backgroundColor; - this.foregroundColor = foregroundColor; - this.hidden = hidden; - this.selected = selected; - this.accessRole = accessRole; - this.primary = primary; - this.deleted = deleted; - } - - public String getKind() { - return kind; - } - - public String getEtag() { - return etag; } public String getId() { @@ -64,48 +26,4 @@ public String getSummary() { public String getDescription() { return description; } - - public String getLocation() { - return location; - } - - public String getTimeZone() { - return timeZone; - } - - public String getSummaryOverride() { - return summaryOverride; - } - - public String getColorId() { - return colorId; - } - - public String getBackgroundColor() { - return backgroundColor; - } - - public String getForegroundColor() { - return foregroundColor; - } - - public boolean isHidden() { - return hidden; - } - - public boolean isSelected() { - return selected; - } - - public String getAccessRole() { - return accessRole; - } - - public boolean isPrimary() { - return primary; - } - - public boolean isDeleted() { - return deleted; - } } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleDateFormat.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleDateFormat.java index cd30178d..6683f3b6 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleDateFormat.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleDateFormat.java @@ -4,15 +4,13 @@ public class GoogleDateFormat { private String date; private String dateTime; - private String timeZone; private GoogleDateFormat() { } - public GoogleDateFormat(final String date, final String dateTime, final String timeZone) { + public GoogleDateFormat(final String date, final String dateTime) { this.date = date; this.dateTime = dateTime; - this.timeZone = timeZone; } public String getDate() { @@ -22,8 +20,4 @@ public String getDate() { public String getDateTime() { return dateTime; } - - public String getTimeZone() { - return timeZone; - } } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleTokenResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleTokenResponse.java index ec979965..cbae35f1 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleTokenResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleTokenResponse.java @@ -6,28 +6,15 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class GoogleTokenResponse { - private String accessToken; private String refreshToken; private String idToken; - private String expiresIn; - private String tokenType; - private String scope; private GoogleTokenResponse() { } - public GoogleTokenResponse(final String accessToken, final String refreshToken, final String idToken, - final String expiresIn, final String scope, final String tokenType) { - this.accessToken = accessToken; + public GoogleTokenResponse(final String refreshToken, final String idToken) { this.refreshToken = refreshToken; this.idToken = idToken; - this.expiresIn = expiresIn; - this.scope = scope; - this.tokenType = tokenType; - } - - public String getAccessToken() { - return accessToken; } public String getRefreshToken() { @@ -37,16 +24,4 @@ public String getRefreshToken() { public String getIdToken() { return idToken; } - - public String getExpiresIn() { - return expiresIn; - } - - public String getScope() { - return scope; - } - - public String getTokenType() { - return tokenType; - } } From 7a0eefca4eb3f09231e1eade1300b29e754e22a6 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Tue, 13 Sep 2022 21:42:43 +0900 Subject: [PATCH 048/148] =?UTF-8?q?refactor:=20=EA=B5=AC=EA=B8=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=98=A8=20=EC=9D=BC=EC=A0=95?= =?UTF-8?q?=EC=9D=B4=20=EC=A2=85=EC=9D=BC=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?-1=EB=B6=84=20=ED=95=B4=EC=A3=BC=EB=8A=94=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/GoogleExternalCalendarClient.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java index 2fefc06e..54e77f58 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -58,13 +58,13 @@ private ResponseEntity fetchGoogleCalendarList(final @Override public List getExternalCalendarSchedules(final String accessToken, final Long internalCategoryId, - final String calendarId, + final String externalCalendarId, final String startDateTime, final String endDateTime) { HttpEntity request = new HttpEntity<>(generateCalendarRequestHeaders(accessToken)); - Map uriVariables = generateEventsVariables(calendarId, startDateTime, endDateTime); - GoogleCalendarEventsResponse response = fetchGoogleCalendarEvents(uriVariables, request).getBody(); + Map uriVariables = generateEventsVariables(externalCalendarId, startDateTime, endDateTime); + GoogleCalendarEventsResponse response = fetchGoogleCalendarEvents(request, uriVariables).getBody(); return response.getItems() .stream() @@ -89,7 +89,7 @@ private Map generateEventsVariables(final String externalCalenda } private ResponseEntity fetchGoogleCalendarEvents( - final Map uriVariables, final HttpEntity request) { + final HttpEntity request, final Map uriVariables) { try { return restTemplate.exchange(CALENDAR_EVENTS_REQUEST_URI, HttpMethod.GET, request, GoogleCalendarEventsResponse.class, uriVariables); @@ -102,16 +102,8 @@ private IntegrationSchedule parseIntegrationSchedule(final Long internalCategory final GoogleCalendarEventResponse event) { LocalDateTime startDateTime = event.getStartDateTime(); LocalDateTime endDateTime = event.getEndDateTime(); - if (isAllDay(startDateTime, endDateTime)) { - endDateTime = endDateTime.minusMinutes(1); - } return new IntegrationSchedule(event.getId(), internalCategoryId, event.getSummary(), startDateTime, endDateTime, event.getDescription(), CategoryType.GOOGLE.name()); } - - private boolean isAllDay(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - return startDateTime.getHour() == 0 && startDateTime.getMinute() == 0 - && endDateTime.getHour() == 0 && endDateTime.getMinute() == 0; - } } From aac52b59633a5bc5d56ef2ac4b3e4e773e3a0d74 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Wed, 14 Sep 2022 10:00:14 +0900 Subject: [PATCH 049/148] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20=ED=95=84=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/OAuthAccessTokenResponse.java | 21 +------------------ .../dallog/common/fixtures/AuthFixtures.java | 3 --- .../oauth/client/StubOAuthClient.java | 6 +----- 3 files changed, 2 insertions(+), 28 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java index 9b8539b1..41d3ab64 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java @@ -7,34 +7,15 @@ public class OAuthAccessTokenResponse { private String accessToken; - private String expiresIn; - private String scope; - private String tokenType; private OAuthAccessTokenResponse() { } - public OAuthAccessTokenResponse(final String accessToken, final String expiresIn, final String scope, - final String tokenType) { + public OAuthAccessTokenResponse(final String accessToken) { this.accessToken = accessToken; - this.expiresIn = expiresIn; - this.scope = scope; - this.tokenType = tokenType; } public String getAccessToken() { return accessToken; } - - public String getExpiresIn() { - return expiresIn; - } - - public String getScope() { - return scope; - } - - public String getTokenType() { - return tokenType; - } } diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java index 6e17ff71..0f96cc85 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java @@ -34,9 +34,6 @@ public class AuthFixtures { public static final String 더미_시크릿_키 = "asdfasarspofjkosdfasdjkflikasndflkasndsdfjkadsnfkjasdn"; public static final String STUB_OAUTH_ACCESS_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.cccccccccc"; - public static final String STUB_OAUTH_EXPIRES_IN = "3599"; - public static final String STUB_OAUTH_SCOPE = "openid"; - public static final String STUB_OAUTH_TOKEN_TYPE = "Bearer"; public static TokenRequest 관리자_인증_코드_토큰_요청() { return new TokenRequest(관리자.getCode(), "https://dallog.me/oauth"); diff --git a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java index d3bd4403..e194aee9 100644 --- a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java +++ b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java @@ -1,9 +1,6 @@ package com.allog.dallog.infrastructure.oauth.client; import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_ACCESS_TOKEN; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_EXPIRES_IN; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_SCOPE; -import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_OAUTH_TOKEN_TYPE; import com.allog.dallog.common.fixtures.OAuthFixtures; import com.allog.dallog.domain.auth.application.OAuthClient; @@ -19,7 +16,6 @@ public OAuthMember getOAuthMember(final String code, final String redirectUri) { @Override public OAuthAccessTokenResponse getAccessToken(final String refreshToken) { - return new OAuthAccessTokenResponse(STUB_OAUTH_ACCESS_TOKEN, STUB_OAUTH_EXPIRES_IN, STUB_OAUTH_SCOPE, - STUB_OAUTH_TOKEN_TYPE); + return new OAuthAccessTokenResponse(STUB_OAUTH_ACCESS_TOKEN); } } From d3820017f6198650846f0f26ee5fabb89e5b2a6b Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Wed, 14 Sep 2022 10:05:12 +0900 Subject: [PATCH 050/148] =?UTF-8?q?refactor:=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/response/OAuthAccessTokenResponse.java | 10 +++++----- .../composition/application/CalendarService.java | 3 ++- .../application/ExternalCalendarService.java | 7 +++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java index 41d3ab64..5cb88730 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java @@ -6,16 +6,16 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class OAuthAccessTokenResponse { - private String accessToken; + private String value; private OAuthAccessTokenResponse() { } - public OAuthAccessTokenResponse(final String accessToken) { - this.accessToken = accessToken; + public OAuthAccessTokenResponse(final String value) { + this.value = value; } - public String getAccessToken() { - return accessToken; + public String getValue() { + return value; } } diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index 7da8dc06..bfb30f71 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -117,7 +117,8 @@ private ExternalCategoryDetail getExternalCategoryDetail(final Category category private List getExternalSchedules(final Long memberId, final DateRangeRequest request, final List externalCategories) { String refreshToken = getOAuthToken(memberId).getRefreshToken(); - String accessToken = oAuthClient.getAccessToken(refreshToken).getAccessToken(); + String accessToken = oAuthClient.getAccessToken(refreshToken) + .getValue(); return externalCategories.stream() .flatMap(externalCategory -> flatIntegrationSchedules(request, accessToken, externalCategory)) diff --git a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java index 1dfca38f..3f1fa44e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java @@ -3,7 +3,6 @@ import com.allog.dallog.domain.auth.application.OAuthClient; import com.allog.dallog.domain.auth.domain.OAuthToken; import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; -import com.allog.dallog.domain.auth.dto.response.OAuthAccessTokenResponse; import com.allog.dallog.domain.auth.exception.NoSuchOAuthTokenException; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; @@ -30,9 +29,9 @@ public ExternalCalendarsResponse findByMemberId(final Long memberId) { OAuthToken oAuthToken = oAuthTokenRepository.findByMemberId(memberId) .orElseThrow(NoSuchOAuthTokenException::new); - OAuthAccessTokenResponse oAuthAccessTokenResponse = oAuthClient.getAccessToken(oAuthToken.getRefreshToken()); - String oAuthAccessToken = oAuthAccessTokenResponse.getAccessToken(); - + String oAuthAccessToken = oAuthClient.getAccessToken(oAuthToken.getRefreshToken()) + .getValue(); + List externalCalendars = externalCalendarClient.getExternalCalendars(oAuthAccessToken); return new ExternalCalendarsResponse(externalCalendars); From db05c8222e69b1bb195dbe970c0a2214a8b211d0 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Wed, 14 Sep 2022 10:34:20 +0900 Subject: [PATCH 051/148] =?UTF-8?q?refactor:=20categoryType=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20=EB=AC=B8=EC=9E=90=EC=97=B4=EC=97=90?= =?UTF-8?q?=EC=84=9C=20enum=20=EA=B0=9D=EC=B2=B4=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dao/IntegrationScheduleDao.java | 3 ++- .../domain/IntegrationSchedule.java | 11 +++++----- .../dto/response/MemberScheduleResponse.java | 3 ++- .../client/GoogleExternalCalendarClient.java | 2 +- .../ExternalCalendarController.java | 1 - .../fixtures/IntegrationScheduleFixtures.java | 14 ++++++------- .../domain/IntegrationScheduleTest.java | 16 +++++++-------- .../domain/IntegrationSchedulesTest.java | 20 +++++++++---------- .../domain/scheduler/SchedulerTest.java | 17 ++++++++-------- 9 files changed, 44 insertions(+), 43 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java index 38805d4a..a05f062d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.integrationschedule.dao; +import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.integrationschedule.domain.Period; import java.time.LocalDateTime; @@ -51,7 +52,7 @@ private RowMapper generateIntegrationSchedule() { LocalDateTime startDateTime = resultSet.getTimestamp("startDateTime").toLocalDateTime(); LocalDateTime endDateTime = resultSet.getTimestamp("endDateTime").toLocalDateTime(); String memo = resultSet.getString("memo"); - String categoryType = resultSet.getString("categoryType"); + CategoryType categoryType = CategoryType.from((resultSet.getString("categoryType"))); Period period = new Period(startDateTime, endDateTime); diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java index 190c84b8..cc675383 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java @@ -1,6 +1,7 @@ package com.allog.dallog.domain.integrationschedule.domain; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.subscription.domain.Color; import com.allog.dallog.domain.subscription.domain.Subscription; @@ -19,16 +20,16 @@ public class IntegrationSchedule { private final String title; private final Period period; private final String memo; - private final String categoryType; + private final CategoryType categoryType; public IntegrationSchedule(final String id, final Long categoryId, final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, final String memo, - final String categoryType) { + final CategoryType categoryType) { this(id, categoryId, title, new Period(startDateTime, endDateTime), memo, categoryType); } public IntegrationSchedule(final String id, final Long categoryId, final String title, final Period period, - final String memo, final String categoryType) { + final String memo, final CategoryType categoryType) { this.id = id; this.categoryId = categoryId; this.title = title; @@ -93,12 +94,10 @@ public String getMemo() { return memo; } - public String getCategoryType() { + public CategoryType getCategoryType() { return categoryType; } - // 중복 일정을 판별하기 위한 equals & hashCode - @Override public boolean equals(final Object o) { if (this == o) { diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java index 82299165..fdc90341 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java @@ -18,7 +18,8 @@ public class MemberScheduleResponse { public MemberScheduleResponse(final IntegrationSchedule integrationSchedule, final Color color) { this(integrationSchedule.getId(), integrationSchedule.getTitle(), integrationSchedule.getStartDateTime(), integrationSchedule.getEndDateTime(), integrationSchedule.getMemo(), - integrationSchedule.getCategoryId(), color.getColorCode(), integrationSchedule.getCategoryType()); + integrationSchedule.getCategoryId(), color.getColorCode(), + integrationSchedule.getCategoryType().name()); } public MemberScheduleResponse(final String id, final String title, final LocalDateTime startDateTime, diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java index 54e77f58..34522b46 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -104,6 +104,6 @@ private IntegrationSchedule parseIntegrationSchedule(final Long internalCategory LocalDateTime endDateTime = event.getEndDateTime(); return new IntegrationSchedule(event.getId(), internalCategoryId, event.getSummary(), startDateTime, - endDateTime, event.getDescription(), CategoryType.GOOGLE.name()); + endDateTime, event.getDescription(), CategoryType.GOOGLE); } } diff --git a/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java index 239b0d94..14114bff 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java @@ -31,7 +31,6 @@ public ExternalCalendarController(final ExternalCalendarService externalCalendar @GetMapping public ResponseEntity getExternalCalendar( @AuthenticationPrincipal final LoginMember loginMember) { - return ResponseEntity.ok(externalCalendarService.findByMemberId(loginMember.getId())); } diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java index c6d7005f..1ef07d4d 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java @@ -1,6 +1,8 @@ package com.allog.dallog.common.fixtures; -import com.allog.dallog.domain.category.domain.CategoryType; +import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; + import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.integrationschedule.domain.Period; import java.time.LocalDateTime; @@ -8,18 +10,16 @@ public class IntegrationScheduleFixtures { public static final IntegrationSchedule 점심_식사 = new IntegrationSchedule("1", 2L, "점심 식사", - new Period(LocalDateTime.of(2022, 8, 16, 11, 00), LocalDateTime.of(2022, 8, 16, 13, 00)), "", - CategoryType.NORMAL.name()); + new Period(LocalDateTime.of(2022, 8, 16, 11, 00), LocalDateTime.of(2022, 8, 16, 13, 00)), "", NORMAL); public static final IntegrationSchedule 달록_여행 = new IntegrationSchedule("2", 2L, "달록 여행", - new Period(LocalDateTime.of(2022, 8, 24, 00, 00), LocalDateTime.of(2022, 8, 25, 23, 59)), "", - CategoryType.NORMAL.name()); + new Period(LocalDateTime.of(2022, 8, 24, 00, 00), LocalDateTime.of(2022, 8, 25, 23, 59)), "", NORMAL); public static final IntegrationSchedule 레벨3_방학 = new IntegrationSchedule("gsgadfgqwrtqwerfgasdasdasd", 1L, "레벨3 방학", new Period(LocalDateTime.of(2022, 8, 20, 00, 00), LocalDateTime.of(2022, 8, 20, 00, 00)), "", - CategoryType.GOOGLE.name()); + GOOGLE); public static final IntegrationSchedule 포수타 = new IntegrationSchedule("asgasgasfgadfgdf", 1L, "포수타", new Period(LocalDateTime.of(2022, 8, 12, 14, 00), LocalDateTime.of(2022, 8, 12, 14, 30)), "", - CategoryType.GOOGLE.name()); + GOOGLE); } diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java index 213af9a4..bc26cdda 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java @@ -4,10 +4,10 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_시작일시; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_제목; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_종료일시; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import com.allog.dallog.domain.category.domain.CategoryType; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -24,7 +24,7 @@ class IntegrationScheduleTest { // when & then assertDoesNotThrow( () -> new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모, - CategoryType.NORMAL.name())); + NORMAL)); } @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 다르면 true를 반환한다.") @@ -35,7 +35,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isLongTerms(); @@ -52,7 +52,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 1), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isLongTerms(); @@ -69,7 +69,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isAllDays(); @@ -86,7 +86,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isAllDays(); @@ -103,7 +103,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isFewHours(); @@ -120,7 +120,7 @@ class IntegrationScheduleTest { Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isFewHours(); diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java index 6e0a05f1..5b505327 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java @@ -1,8 +1,8 @@ package com.allog.dallog.domain.integrationschedule.domain; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; -import com.allog.dallog.domain.category.domain.CategoryType; import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -16,15 +16,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 2, 0, 0), "일정1", NORMAL); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 3, 0, 0), - LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 4, 0, 0), "일정2", NORMAL); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정3", NORMAL); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -45,15 +45,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "일정1", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", NORMAL); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 7, 0, 0), "일정2", NORMAL); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "일정3", LocalDateTime.of(2022, 3, 5, 0, 0), - LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 5, 0, 0), "일정3", NORMAL); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); @@ -74,15 +74,15 @@ class IntegrationSchedulesTest { Long categoryId = 1L; IntegrationSchedule 첫번째로_정렬되어야_하는_일정 = new IntegrationSchedule("1", categoryId, "가", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정1", NORMAL); IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "나", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정2", NORMAL); IntegrationSchedule 세번째로_정렬되어야_하는_일정 = new IntegrationSchedule("3", categoryId, "다", LocalDateTime.of(2022, 3, 1, 0, 0), - LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", CategoryType.NORMAL.name()); + LocalDateTime.of(2022, 3, 10, 0, 0), "일정3", NORMAL); // when IntegrationSchedules integrationSchedules = new IntegrationSchedules(); diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java index d25c0648..9f8c3021 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java @@ -17,6 +17,7 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; @@ -40,21 +41,21 @@ class SchedulerTest { String 일정_메모 = "일정 메모"; IntegrationSchedule 일정1 = new IntegrationSchedule("1", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_7일_16시_0분, - 날짜_2022년_7월_10일_0시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_10일_0시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정2 = new IntegrationSchedule("2", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_10일_11시_59분, - 날짜_2022년_7월_15일_16시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_15일_16시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정3 = new IntegrationSchedule("3", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_16시_0분, - 날짜_2022년_7월_16일_16시_1분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_16일_16시_1분, 일정_메모, NORMAL); IntegrationSchedule 일정4 = new IntegrationSchedule("4", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_18시_0분, - 날짜_2022년_7월_16일_20시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_16일_20시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정5 = new IntegrationSchedule("5", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_20시_0분, - 날짜_2022년_7월_20일_0시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_20일_0시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정6 = new IntegrationSchedule("6", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_20일_11시_59분, - 날짜_2022년_7월_27일_0시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_27일_0시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정7 = new IntegrationSchedule("7", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_27일_11시_59분, - 날짜_2022년_7월_31일_0시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_7월_31일_0시_0분, 일정_메모, NORMAL); IntegrationSchedule 일정8 = new IntegrationSchedule("8", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_31일_0시_0분, - 날짜_2022년_8월_15일_14시_0분, 일정_메모, "NORMAL"); + 날짜_2022년_8월_15일_14시_0분, 일정_메모, NORMAL); List 일정_목록 = List.of(일정1, 일정2, 일정3, 일정4, 일정5, 일정6, 일정7, 일정8); From b0f4d50f6e0c5330ce2622544ebb328d9e767247 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Thu, 15 Sep 2022 10:26:23 +0900 Subject: [PATCH 052/148] =?UTF-8?q?refactor:=20Subscriptions=20=EC=9D=BC?= =?UTF-8?q?=EA=B8=89=20=EC=BB=AC=EB=A0=89=EC=85=98=20=EB=A7=8C=EB=93=A4?= =?UTF-8?q?=EA=B3=A0=20=EB=A1=9C=EC=A7=81=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CalendarService.java | 41 +++++-------------- .../dao/IntegrationScheduleDao.java | 4 +- .../integrationschedule/domain/Period.java | 21 +++++----- .../subscription/domain/Subscriptions.java | 24 +++++++++++ 4 files changed, 47 insertions(+), 43 deletions(-) create mode 100644 backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index bfb30f71..f5b9bd06 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -16,15 +16,14 @@ import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; import com.allog.dallog.domain.subscription.application.SubscriptionService; import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -// TODO: 전체 리팩토링 @Transactional(readOnly = true) @Service public class CalendarService { @@ -54,8 +53,8 @@ public CalendarService(final SubscriptionService subscriptionService, public List getSchedulesOfSubscriberIds(final List subscriberIds, final DateRangeRequest dateRange) { return subscriberIds.stream() - .flatMap(subscriberId -> getIntegrationSchedulesByMemberIdAndSubscriptions(subscriberId, - dateRange).stream()) + .flatMap(subscriberId -> + getIntegrationSchedulesByMemberIdAndSubscriptions(subscriberId, dateRange).stream()) .distinct() .collect(Collectors.toList()); } @@ -70,12 +69,11 @@ public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, private List getIntegrationSchedulesByMemberIdAndSubscriptions(final Long memberId, final DateRangeRequest dateRange) { - List subscriptions = subscriptionService.getAllByMemberId(memberId); - List internalCategoryIds = findCheckedInternalCategories(subscriptions); + Subscriptions subscriptions = new Subscriptions(subscriptionService.getAllByMemberId(memberId)); + List internalCategoryIds = subscriptions.findCheckedCategoryIdsBy(Category::isInternal); - List integrationSchedules = new ArrayList<>( - integrationScheduleDao.findByCategoryIdInAndBetween( - internalCategoryIds, dateRange.getStartDateTime(), dateRange.getEndDateTime())); + List integrationSchedules = integrationScheduleDao.findByCategoryIdInAndBetween( + internalCategoryIds, dateRange.getStartDateTime(), dateRange.getEndDateTime()); List externalCategoryDetails = findCheckedExternalCategoryDetails(subscriptions); if (!externalCategoryDetails.isEmpty()) { @@ -85,32 +83,15 @@ private List getIntegrationSchedulesByMemberIdAndSubscripti return integrationSchedules; } - private List findCheckedInternalCategories(final List subscriptions) { - return findCheckedSubscriptions(subscriptions) - .stream() - .map(Subscription::getCategory) - .filter(Category::isInternal) - .map(Category::getId) - .collect(Collectors.toList()); - } - - private List findCheckedExternalCategoryDetails(final List subscriptions) { - return findCheckedSubscriptions(subscriptions) + private List findCheckedExternalCategoryDetails(final Subscriptions subscriptions) { + return subscriptions.findCheckedCategoryIdsBy(Category::isExternal) .stream() - .map(Subscription::getCategory) - .filter(Category::isExternal) .map(this::getExternalCategoryDetail) .collect(Collectors.toList()); } - private List findCheckedSubscriptions(final List subscriptions) { - return subscriptions.stream() - .filter(Subscription::isChecked) - .collect(Collectors.toList()); - } - - private ExternalCategoryDetail getExternalCategoryDetail(final Category category) { - return externalCategoryDetailRepository.findByCategoryId(category.getId()) + private ExternalCategoryDetail getExternalCategoryDetail(final Long categoryId) { + return externalCategoryDetailRepository.findByCategoryId(categoryId) .orElseThrow(NoSuchExternalCategoryDetailException::new); } diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java index a05f062d..9eb50780 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java @@ -4,7 +4,7 @@ import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.integrationschedule.domain.Period; import java.time.LocalDateTime; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; @@ -24,7 +24,7 @@ public List findByCategoryIdInAndBetween(final List c final LocalDateTime startDateTime, final LocalDateTime endDateTime) { if (categoryIds.isEmpty()) { - return Collections.emptyList(); + return new ArrayList<>(); } String sql = "SELECT s.id as id, c.id as categoryId, s.title as title, " diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java index 769ae36c..7b93cf40 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java @@ -42,10 +42,19 @@ public List slice(final Period otherPeriod) { if (isNotOverlapped(otherPeriod)) { return List.of(this); } - return sliceByOtherPeriod(otherPeriod); } + private boolean isNotOverlapped(final Period otherPeriod) { + // other가 좌측 방향으로 멀리 떨어져 겹치지 않을때 + boolean farFromLeftSideOfBase = otherPeriod.endDateTime.isBefore(startDateTime); + + // other가 우측 방향으로 멀리 떨어져 겹치지 않을때 + boolean farFromRightSideOfBase = otherPeriod.startDateTime.isAfter(endDateTime); + + return farFromLeftSideOfBase || farFromRightSideOfBase; + } + private List sliceByOtherPeriod(final Period otherPeriod) { List periods = new ArrayList<>(); if (startDateTime.isBefore(otherPeriod.startDateTime)) { @@ -59,16 +68,6 @@ private List sliceByOtherPeriod(final Period otherPeriod) { return periods; } - private boolean isNotOverlapped(final Period otherPeriod) { - boolean farFromLeftSideOfBase = otherPeriod.endDateTime.isBefore(startDateTime); - // other가 좌측 방향으로 멀리 떨어져 겹치지 않을때 - - boolean farFromRightSideOfBase = otherPeriod.startDateTime.isAfter(endDateTime); - // other가 우측 방향으로 멀리 떨어져 겹치지 않을때 - - return farFromLeftSideOfBase || farFromRightSideOfBase; - } - public LocalDateTime getStartDateTime() { return startDateTime; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java new file mode 100644 index 00000000..e92b2232 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java @@ -0,0 +1,24 @@ +package com.allog.dallog.domain.subscription.domain; + +import com.allog.dallog.domain.category.domain.Category; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class Subscriptions { + + private final List subscriptions; + + public Subscriptions(final List subscriptions) { + this.subscriptions = subscriptions; + } + + public List findCheckedCategoryIdsBy(Predicate predicate) { + return subscriptions.stream() + .filter(Subscription::isChecked) + .map(Subscription::getCategory) + .filter(predicate) + .map(Category::getId) + .collect(Collectors.toList()); + } +} From 922146b45c64ec21a488dbeaae6d128b6d452274 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Thu, 15 Sep 2022 10:27:19 +0900 Subject: [PATCH 053/148] =?UTF-8?q?refactor:=20comparator=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/IntegrationScheduleComparator.java | 25 ++++++------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java index 70d33096..37f8bcff 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java @@ -5,38 +5,29 @@ public class IntegrationScheduleComparator implements Comparator { - private static final int BEFORE = -1; - private static final int AFTER = 1; - private static final int SAME = 0; + private static final int SAME_CONDITION = 0; @Override public int compare(final IntegrationSchedule firstSchedule, final IntegrationSchedule secondSchedule) { LocalDateTime firstScheduleStartDateTime = firstSchedule.getStartDateTime(); LocalDateTime secondScheduleStartDateTime = secondSchedule.getStartDateTime(); - if (firstScheduleStartDateTime.isBefore(secondScheduleStartDateTime)) { - return BEFORE; - } - if (firstScheduleStartDateTime.isAfter(secondScheduleStartDateTime)) { - return AFTER; - } - if (firstScheduleStartDateTime.isEqual(secondScheduleStartDateTime)) { + int cmp = firstScheduleStartDateTime.compareTo(secondScheduleStartDateTime); + if (cmp == SAME_CONDITION) { return compareEndDateTime(firstSchedule, secondSchedule); } - return SAME; + return cmp; } private int compareEndDateTime(IntegrationSchedule firstSchedule, IntegrationSchedule secondSchedule) { LocalDateTime firstScheduleEndDateTime = firstSchedule.getEndDateTime(); LocalDateTime secondScheduleEndDateTime = secondSchedule.getEndDateTime(); - if (firstScheduleEndDateTime.isBefore(secondScheduleEndDateTime)) { - return AFTER; - } - if (firstScheduleEndDateTime.isAfter(secondScheduleEndDateTime)) { - return BEFORE; + int cmp = secondScheduleEndDateTime.compareTo(firstScheduleEndDateTime); + if (cmp == SAME_CONDITION) { + return compareByTitle(firstSchedule, secondSchedule); } - return compareByTitle(firstSchedule, secondSchedule); + return cmp; } private int compareByTitle(IntegrationSchedule firstSchedule, IntegrationSchedule secondSchedule) { From 4470c81ddf04284e0d87c216db92d2de70e7bdfc Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Thu, 15 Sep 2022 10:37:38 +0900 Subject: [PATCH 054/148] =?UTF-8?q?refactor:=20=EA=B5=AC=EA=B8=80=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B0=80=EC=A0=B8=EC=98=A8=20=EC=A2=85=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20-1=EB=B6=84=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=A1=A4=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../oauth/client/GoogleExternalCalendarClient.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java index 34522b46..392cc4dc 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -103,7 +103,16 @@ private IntegrationSchedule parseIntegrationSchedule(final Long internalCategory LocalDateTime startDateTime = event.getStartDateTime(); LocalDateTime endDateTime = event.getEndDateTime(); + if (isAllDay(startDateTime, endDateTime)) { + endDateTime = endDateTime.minusMinutes(1); + } + return new IntegrationSchedule(event.getId(), internalCategoryId, event.getSummary(), startDateTime, endDateTime, event.getDescription(), CategoryType.GOOGLE); } + + private boolean isAllDay(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + return startDateTime.getHour() == 0 && startDateTime.getMinute() == 0 + && endDateTime.getHour() == 0 && endDateTime.getMinute() == 0; + } } From 4443e3877be9a6c954c5579c288bec11c2e698f4 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Thu, 15 Sep 2022 13:53:33 +0900 Subject: [PATCH 055/148] =?UTF-8?q?test:=20Subscriptions=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/fixtures/CategoryFixtures.java | 12 ++++ .../domain/SubscriptionsTest.java | 68 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java index 24e71dc3..9fbc05e7 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java @@ -9,6 +9,7 @@ import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.dto.MemberResponse; +import java.lang.reflect.Field; import java.time.LocalDateTime; public class CategoryFixtures { @@ -88,4 +89,15 @@ public class CategoryFixtures { public static CategoryResponse 후디_JPA_스터디_응답(final MemberResponse creatorResponse) { return new CategoryResponse(5L, 후디_JPA_스터디_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); } + + public static Category setId(final Category category, final Long id) { + try { + Field idField = Category.class.getDeclaredField("id"); + idField.setAccessible(true); + idField.set(category, id); + return category; + } catch (NoSuchFieldException | IllegalAccessException e) { + throw new IllegalArgumentException(e.getMessage()); + } + } } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java new file mode 100644 index 00000000..a081d993 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java @@ -0,0 +1,68 @@ +package com.allog.dallog.domain.subscription.domain; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; +import static com.allog.dallog.common.fixtures.CategoryFixtures.setId; +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; +import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정; +import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; +import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; +import static com.allog.dallog.domain.subscription.domain.Color.COLOR_1; +import static com.allog.dallog.domain.subscription.domain.Color.COLOR_2; +import static com.allog.dallog.domain.subscription.domain.Color.COLOR_3; +import static com.allog.dallog.domain.subscription.domain.Color.COLOR_4; +import static org.assertj.core.api.Assertions.assertThat; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.member.domain.Member; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SubscriptionsTest { + + @DisplayName("체크된 카테고리 중 내부 카테고리의 아이디를 찾는다.") + @Test + void 체크된_카테고리_중_내부_카테고리의_아이디를_찾는다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + Category BE_일정 = setId(BE_일정(파랑), 2L); + Category 내_일정 = setId(내_일정(파랑), 3L); + Category 우아한테크코스_일정 = setId(우아한테크코스_일정(파랑), 4L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + Subscription BE_일정_구독 = new Subscription(파랑, BE_일정, COLOR_2); + Subscription 내_일정_구독 = new Subscription(파랑, 내_일정, COLOR_3); + Subscription 우아한테크코스_일정_구독 = new Subscription(파랑, 우아한테크코스_일정, COLOR_4); + + BE_일정_구독.change(COLOR_2, false); + + Subscriptions subscriptions = + new Subscriptions(List.of(공통_일정_구독, BE_일정_구독, 내_일정_구독, 우아한테크코스_일정_구독)); + + // when & then + assertThat(subscriptions.findCheckedCategoryIdsBy(Category::isInternal)).isEqualTo(List.of(1L, 3L)); + } + + @DisplayName("체크된 카테고리 중 외부 카테고리의 아이디를 찾는다.") + @Test + void 체크된_카테고리_중_외부_카테고리의_아이디를_찾는다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + Category BE_일정 = setId(BE_일정(파랑), 2L); + Category 내_일정 = setId(내_일정(파랑), 3L); + Category 우아한테크코스_일정 = setId(우아한테크코스_일정(파랑), 4L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + Subscription BE_일정_구독 = new Subscription(파랑, BE_일정, COLOR_2); + Subscription 내_일정_구독 = new Subscription(파랑, 내_일정, COLOR_3); + Subscription 우아한테크코스_일정_구독 = new Subscription(파랑, 우아한테크코스_일정, COLOR_4); + + Subscriptions subscriptions = + new Subscriptions(List.of(공통_일정_구독, BE_일정_구독, 내_일정_구독, 우아한테크코스_일정_구독)); + + // when & then + assertThat(subscriptions.findCheckedCategoryIdsBy(Category::isExternal)).isEqualTo(List.of(4L)); + } +} From abfc5de34d6b063bea5ebc2aa266e551cbb60ad6 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Fri, 16 Sep 2022 11:59:37 +0900 Subject: [PATCH 056/148] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/integrationschedule/domain/TypedSchedules.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java index de5ad6c4..135f9031 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java @@ -12,8 +12,8 @@ public TypedSchedules(final List integrationSchedules) { initializeValues(); for (IntegrationSchedule integrationSchedule : integrationSchedules) { ScheduleType scheduleType = ScheduleType.from(integrationSchedule); - IntegrationSchedules sortedSchedules = values.get(scheduleType); - sortedSchedules.add(integrationSchedule); + IntegrationSchedules schedules = values.get(scheduleType); + schedules.add(integrationSchedule); } } From 2225e2150126d036e040e951bc45268869c46caa Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Fri, 16 Sep 2022 12:27:51 +0900 Subject: [PATCH 057/148] =?UTF-8?q?refactor:=20=EA=B5=AC=EB=8F=85=20?= =?UTF-8?q?=EC=83=89=EC=83=81=20=EC=B0=BE=EB=8A=94=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CalendarService.java | 10 ++--- .../domain/IntegrationSchedule.java | 13 +----- .../dto/response/MemberScheduleResponses.java | 9 ++-- .../subscription/domain/Subscriptions.java | 10 +++++ .../domain/SubscriptionsTest.java | 41 +++++++++++++++++++ 5 files changed, 60 insertions(+), 23 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index f5b9bd06..f6dc51a7 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -15,7 +15,6 @@ import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; import com.allog.dallog.domain.subscription.application.SubscriptionService; -import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.time.format.DateTimeFormatter; import java.util.List; @@ -59,11 +58,10 @@ public List getSchedulesOfSubscriberIds(final List su .collect(Collectors.toList()); } - public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, - final DateRangeRequest dateRange) { - List subscriptions = subscriptionService.getAllByMemberId(memberId); - List integrationSchedules = getIntegrationSchedulesByMemberIdAndSubscriptions(memberId, - dateRange); + public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, final DateRangeRequest dateRange) { + Subscriptions subscriptions = new Subscriptions(subscriptionService.getAllByMemberId(memberId)); + List integrationSchedules = + getIntegrationSchedulesByMemberIdAndSubscriptions(memberId, dateRange); return new MemberScheduleResponses(subscriptions, new TypedSchedules(integrationSchedules)); } diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java index cc675383..362914de 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java @@ -2,11 +2,8 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryType; -import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.subscription.domain.Color; import com.allog.dallog.domain.subscription.domain.Subscription; import java.time.LocalDateTime; -import java.util.List; import java.util.Objects; public class IntegrationSchedule { @@ -53,15 +50,7 @@ public boolean isFewHours() { && period.calculateDayDifference() < ONE_DAY; } - public Color findSubscriptionColor(final List subscriptions) { - return subscriptions.stream() - .filter(this::isSameCategory) - .findAny() - .orElseThrow(() -> new NoSuchCategoryException("구독하지 않은 카테고리 입니다.")) - .getColor(); - } - - private boolean isSameCategory(final Subscription subscription) { + public boolean isSameCategory(final Subscription subscription) { Category category = subscription.getCategory(); return category.getId().equals(categoryId); } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java index 0f63ec4c..16b35a1a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java @@ -2,13 +2,12 @@ import com.allog.dallog.domain.integrationschedule.domain.ScheduleType; import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; -import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.util.List; import java.util.stream.Collectors; public class MemberScheduleResponses { - // TODO: 리팩토링 private final List longTerms; private final List allDays; private final List fewHours; @@ -21,19 +20,19 @@ public MemberScheduleResponses(final List longTerms, this.fewHours = fewHours; } - public MemberScheduleResponses(final List subscriptions, final TypedSchedules typedSchedules) { + public MemberScheduleResponses(final Subscriptions subscriptions, final TypedSchedules typedSchedules) { this.longTerms = getColoredScheduleResponses(ScheduleType.LONG_TERMS, subscriptions, typedSchedules); this.allDays = getColoredScheduleResponses(ScheduleType.ALL_DAYS, subscriptions, typedSchedules); this.fewHours = getColoredScheduleResponses(ScheduleType.FEW_HOURS, subscriptions, typedSchedules); } private List getColoredScheduleResponses(final ScheduleType scheduleType, - final List subscriptions, + final Subscriptions subscriptions, final TypedSchedules typedSchedules) { return typedSchedules.getSortedSchedules(scheduleType) .getSortedValues() .stream() - .map(schedule -> new MemberScheduleResponse(schedule, schedule.findSubscriptionColor(subscriptions))) + .map(schedule -> new MemberScheduleResponse(schedule, subscriptions.findColor(schedule))) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java index e92b2232..74c08b1e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java @@ -1,6 +1,8 @@ package com.allog.dallog.domain.subscription.domain; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import java.util.List; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -21,4 +23,12 @@ public List findCheckedCategoryIdsBy(Predicate predicate) { .map(Category::getId) .collect(Collectors.toList()); } + + public Color findColor(final IntegrationSchedule schedule) { + return subscriptions.stream() + .filter(schedule::isSameCategory) + .findAny() + .orElseThrow(() -> new NoSuchCategoryException("구독하지 않은 카테고리 입니다.")) + .getColor(); + } } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java index a081d993..a768cebd 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java @@ -5,14 +5,17 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; +import static com.allog.dallog.common.fixtures.IntegrationScheduleFixtures.달록_여행; import static com.allog.dallog.common.fixtures.MemberFixtures.파랑; import static com.allog.dallog.domain.subscription.domain.Color.COLOR_1; import static com.allog.dallog.domain.subscription.domain.Color.COLOR_2; import static com.allog.dallog.domain.subscription.domain.Color.COLOR_3; import static com.allog.dallog.domain.subscription.domain.Color.COLOR_4; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.member.domain.Member; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -65,4 +68,42 @@ class SubscriptionsTest { // when & then assertThat(subscriptions.findCheckedCategoryIdsBy(Category::isExternal)).isEqualTo(List.of(4L)); } + + @DisplayName("특정 스케줄의 구독 색상을 찾는다.") + @Test + void 특정_스케줄의_구독_색상을_찾는다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + Category BE_일정 = setId(BE_일정(파랑), 2L); + Category 내_일정 = setId(내_일정(파랑), 3L); + Category 우아한테크코스_일정 = setId(우아한테크코스_일정(파랑), 4L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + Subscription BE_일정_구독 = new Subscription(파랑, BE_일정, COLOR_2); + Subscription 내_일정_구독 = new Subscription(파랑, 내_일정, COLOR_3); + Subscription 우아한테크코스_일정_구독 = new Subscription(파랑, 우아한테크코스_일정, COLOR_4); + + Subscriptions subscriptions = + new Subscriptions(List.of(공통_일정_구독, BE_일정_구독, 내_일정_구독, 우아한테크코스_일정_구독)); + + // when & then + assertThat(subscriptions.findColor(달록_여행)).isEqualTo(COLOR_2); + } + + @DisplayName("구독하지 않은 스케줄의 구독 색상을 찾는 경우 예외를 던진다") + @Test + void 구독하지_않은_스케줄의_구독_색상을_찾는_경우_예외를_던진다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + setId(BE_일정(파랑), 2L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + + Subscriptions subscriptions = new Subscriptions(List.of(공통_일정_구독)); + + // when & then + assertThatThrownBy(() -> subscriptions.findColor(달록_여행)).isInstanceOf(NoSuchCategoryException.class); + } } From 286d51e6979ce8f8a9c5d510d61c6cc24502f8bb Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Fri, 16 Sep 2022 12:51:42 +0900 Subject: [PATCH 058/148] =?UTF-8?q?refactor:=20CalendarService=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExternalCategoryDetailRepository.java | 6 +++ .../application/CalendarService.java | 43 +++++++------------ .../application/SchedulerService.java | 2 +- .../application/SubscriptionService.java | 5 --- .../application/CalendarServiceTest.java | 2 +- .../CategorySubscriptionServiceTest.java | 8 ++-- 6 files changed, 27 insertions(+), 39 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java index 1a97784f..158be786 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.category.domain; +import com.allog.dallog.domain.category.exception.NoSuchExternalCategoryDetailException; import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; import java.util.List; import java.util.Optional; @@ -13,6 +14,11 @@ public interface ExternalCategoryDetailRepository extends JpaRepository externalCategories) { if (existsByExternalIdAndCategoryIn(externalId, externalCategories)) { throw new ExistExternalCategoryException(); diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index f6dc51a7..43dced0a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -1,20 +1,17 @@ package com.allog.dallog.domain.composition.application; import com.allog.dallog.domain.auth.application.OAuthClient; -import com.allog.dallog.domain.auth.domain.OAuthToken; import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; -import com.allog.dallog.domain.auth.exception.NoSuchOAuthTokenException; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; -import com.allog.dallog.domain.category.exception.NoSuchExternalCategoryDetailException; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; -import com.allog.dallog.domain.subscription.application.SubscriptionService; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.time.format.DateTimeFormatter; import java.util.List; @@ -29,45 +26,43 @@ public class CalendarService { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; - private final SubscriptionService subscriptionService; private final IntegrationScheduleDao integrationScheduleDao; + private final SubscriptionRepository subscriptionRepository; private final ExternalCategoryDetailRepository externalCategoryDetailRepository; private final OAuthTokenRepository oAuthTokenRepository; private final OAuthClient oAuthClient; private final ExternalCalendarClient externalCalendarClient; - public CalendarService(final SubscriptionService subscriptionService, - final IntegrationScheduleDao integrationScheduleDao, + public CalendarService(final IntegrationScheduleDao integrationScheduleDao, + final SubscriptionRepository subscriptionRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final OAuthTokenRepository oAuthTokenRepository, final OAuthClient oAuthClient, final ExternalCalendarClient externalCalendarClient) { - this.subscriptionService = subscriptionService; this.integrationScheduleDao = integrationScheduleDao; + this.subscriptionRepository = subscriptionRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; this.oAuthTokenRepository = oAuthTokenRepository; this.oAuthClient = oAuthClient; this.externalCalendarClient = externalCalendarClient; } - public List getSchedulesOfSubscriberIds(final List subscriberIds, + public List getSchedulesBySubscriberIds(final List subscriberIds, final DateRangeRequest dateRange) { return subscriberIds.stream() - .flatMap(subscriberId -> - getIntegrationSchedulesByMemberIdAndSubscriptions(subscriberId, dateRange).stream()) + .flatMap(subscriberId -> getIntegrationSchedulesByMemberId(subscriberId, dateRange).stream()) .distinct() .collect(Collectors.toList()); } public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, final DateRangeRequest dateRange) { - Subscriptions subscriptions = new Subscriptions(subscriptionService.getAllByMemberId(memberId)); - List integrationSchedules = - getIntegrationSchedulesByMemberIdAndSubscriptions(memberId, dateRange); + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); + List integrationSchedules = getIntegrationSchedulesByMemberId(memberId, dateRange); return new MemberScheduleResponses(subscriptions, new TypedSchedules(integrationSchedules)); } - private List getIntegrationSchedulesByMemberIdAndSubscriptions(final Long memberId, - final DateRangeRequest dateRange) { - Subscriptions subscriptions = new Subscriptions(subscriptionService.getAllByMemberId(memberId)); + private List getIntegrationSchedulesByMemberId(final Long memberId, + final DateRangeRequest dateRange) { + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); List internalCategoryIds = subscriptions.findCheckedCategoryIdsBy(Category::isInternal); List integrationSchedules = integrationScheduleDao.findByCategoryIdInAndBetween( @@ -84,18 +79,14 @@ private List getIntegrationSchedulesByMemberIdAndSubscripti private List findCheckedExternalCategoryDetails(final Subscriptions subscriptions) { return subscriptions.findCheckedCategoryIdsBy(Category::isExternal) .stream() - .map(this::getExternalCategoryDetail) + .map(externalCategoryDetailRepository::getByCategoryId) .collect(Collectors.toList()); } - private ExternalCategoryDetail getExternalCategoryDetail(final Long categoryId) { - return externalCategoryDetailRepository.findByCategoryId(categoryId) - .orElseThrow(NoSuchExternalCategoryDetailException::new); - } - private List getExternalSchedules(final Long memberId, final DateRangeRequest request, final List externalCategories) { - String refreshToken = getOAuthToken(memberId).getRefreshToken(); + String refreshToken = oAuthTokenRepository.getByMemberId(memberId) + .getRefreshToken(); String accessToken = oAuthClient.getAccessToken(refreshToken) .getValue(); @@ -104,10 +95,6 @@ private List getExternalSchedules(final Long memberId, fina .collect(Collectors.toList()); } - private OAuthToken getOAuthToken(final Long memberId) { - return oAuthTokenRepository.findByMemberId(memberId).orElseThrow(NoSuchOAuthTokenException::new); - } - private Stream flatIntegrationSchedules(final DateRangeRequest dateRangeRequest, final String accessToken, final ExternalCategoryDetail externalCategory) { diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java index 7352f517..ad1a09b1 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java @@ -31,7 +31,7 @@ public SchedulerService(final CalendarService calendarService, final Subscriptio public List getAvailablePeriods(final Long categoryId, final DateRangeRequest dateRange) { List subscriberIds = getSubscriberIds(categoryId); - List schedules = calendarService.getSchedulesOfSubscriberIds(subscriberIds, dateRange); + List schedules = calendarService.getSchedulesBySubscriberIds(subscriberIds, dateRange); Scheduler scheduler = new Scheduler(schedules, dateRange.getStartDateTime(), dateRange.getEndDateTime()); diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 1183aea7..cb871c75 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -68,11 +68,6 @@ public List findByCategoryId(final Long categoryId) { .collect(Collectors.toList()); } - // TODO: 상위 Service인 CalanderService에서만 사용하는 메서드입니다. 삭제 예정 - public List getAllByMemberId(final Long memberId) { - return subscriptionRepository.findByMemberId(memberId); - } - @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { subscriptionRepository.validateExistsByIdAndMemberId(id, memberId); diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 395ee47f..be4cceba 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -175,7 +175,7 @@ class CalendarServiceTest extends ServiceTest { subscriptionService.save(리버_id, FE_일정.getId()); // when - List actual = calendarService.getSchedulesOfSubscriberIds( + List actual = calendarService.getSchedulesBySubscriberIds( List.of(후디_id, 파랑_id, 매트_id, 리버_id), new DateRangeRequest("2022-07-07T16:00", "2022-08-15T14:00")); // then diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java index fbe868db..6f2d7b9c 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java @@ -6,8 +6,8 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.common.fixtures.AuthFixtures; -import com.allog.dallog.domain.subscription.application.SubscriptionService; import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,7 +19,7 @@ class CategorySubscriptionServiceTest extends ServiceTest { private CategorySubscriptionService categorySubscriptionService; @Autowired - private SubscriptionService subscriptionService; + private SubscriptionRepository subscriptionRepository; @DisplayName("카테고리 생성 시 자동으로 구독한다.") @Test @@ -30,7 +30,7 @@ class CategorySubscriptionServiceTest extends ServiceTest { // when categorySubscriptionService.save(파랑_id, 공통_일정_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + List subscriptions = subscriptionRepository.findByMemberId(파랑_id); // then assertThat(subscriptions).hasSize(2); @@ -45,7 +45,7 @@ class CategorySubscriptionServiceTest extends ServiceTest { // when categorySubscriptionService.save(파랑_id, 대한민국_공휴일_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + List subscriptions = subscriptionRepository.findByMemberId(파랑_id); // then assertThat(subscriptions).hasSize(2); From 1ad0da1caa53a011e1ce3989183b4ed2370e6690 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Sat, 17 Sep 2022 18:04:32 +0900 Subject: [PATCH 059/148] =?UTF-8?q?refactor:=20=EC=BB=A8=EB=B2=A4=EC=85=98?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/integrationschedule/domain/Period.java | 6 ++++-- .../dallog/domain/subscription/domain/Subscriptions.java | 2 +- .../acceptance/fixtures/ScheduleAcceptanceFixtures.java | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java index 7b93cf40..0f233d40 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java @@ -47,10 +47,12 @@ public List slice(final Period otherPeriod) { private boolean isNotOverlapped(final Period otherPeriod) { // other가 좌측 방향으로 멀리 떨어져 겹치지 않을때 - boolean farFromLeftSideOfBase = otherPeriod.endDateTime.isBefore(startDateTime); + boolean farFromLeftSideOfBase = otherPeriod.endDateTime + .isBefore(startDateTime); // other가 우측 방향으로 멀리 떨어져 겹치지 않을때 - boolean farFromRightSideOfBase = otherPeriod.startDateTime.isAfter(endDateTime); + boolean farFromRightSideOfBase = otherPeriod.startDateTime + .isAfter(endDateTime); return farFromLeftSideOfBase || farFromRightSideOfBase; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java index 74c08b1e..022f9b21 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java @@ -15,7 +15,7 @@ public Subscriptions(final List subscriptions) { this.subscriptions = subscriptions; } - public List findCheckedCategoryIdsBy(Predicate predicate) { + public List findCheckedCategoryIdsBy(final Predicate predicate) { return subscriptions.stream() .filter(Subscription::isChecked) .map(Subscription::getCategory) diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/ScheduleAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/ScheduleAcceptanceFixtures.java index c2b49763..4990b587 100644 --- a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/ScheduleAcceptanceFixtures.java +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/ScheduleAcceptanceFixtures.java @@ -15,7 +15,7 @@ public class ScheduleAcceptanceFixtures { public static ExtractableResponse 새로운_일정을_등록한다(final String accessToken, final Long categoryId) { - Map params = new HashMap(); + Map params = new HashMap<>(); params.put("title", 알록달록_회의_제목); params.put("startDateTime", "2022-07-04T13:00"); params.put("endDateTime", "2022-07-05T16:00"); From 92c8efc346a0fe7fdd80d5db23f25834ad385faf Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Sun, 18 Sep 2022 13:36:30 +0900 Subject: [PATCH 060/148] =?UTF-8?q?test:=20repository=EC=9D=98=20default?= =?UTF-8?q?=20=EB=A9=94=EC=84=9C=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExternalCategoryDetailRepositoryTest.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java new file mode 100644 index 00000000..82b654bf --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java @@ -0,0 +1,79 @@ +package com.allog.dallog.domain.category.domain; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; +import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.allog.dallog.common.annotation.RepositoryTest; +import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; +import com.allog.dallog.domain.category.exception.NoSuchExternalCategoryDetailException; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +public class ExternalCategoryDetailRepositoryTest extends RepositoryTest { + + @Autowired + private ExternalCategoryDetailRepository externalCategoryDetailRepository; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @DisplayName("존재하지 않는 외부 카테고리 세부정보를 가져오는 경우 예외를 던진다.") + @Test + void 존재하지_않는_외부_카테고리_세부정보를_가져오는_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category 우아한테크코스_일정 = 우아한테크코스_일정(관리자); + categoryRepository.save(우아한테크코스_일정); + + externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스_일정, "externalId")); + + // when & then + assertThatThrownBy(() -> externalCategoryDetailRepository.getByCategoryId(0L)) + .isInstanceOf(NoSuchExternalCategoryDetailException.class); + } + + @DisplayName("새로운 외부 카테고리 세부정보인 경우 예외를 던지지 않는다.") + @Test + void 새로운_외부_카테고리_세부정보인_경우_예외를_던지지_않는다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category 우아한테크코스_일정 = 우아한테크코스_일정(관리자); + categoryRepository.save(우아한테크코스_일정); + + String externalId = "externalId"; + externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스_일정, externalId)); + + // when & then + assertDoesNotThrow(() -> externalCategoryDetailRepository + .validateExistByExternalIdAndCategoryIn(externalId, List.of())); + } + + @DisplayName("이미 존재하는 외부 카테고리 세부정보인 경우 예외를 던진다.") + @Test + void 이미_존재하는_외부_카테고리_세부정보인_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category 우아한테크코스_일정 = 우아한테크코스_일정(관리자); + categoryRepository.save(우아한테크코스_일정); + + String externalId = "externalId"; + externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스_일정, externalId)); + + // when & then + assertThatThrownBy(() -> externalCategoryDetailRepository + .validateExistByExternalIdAndCategoryIn(externalId, List.of(우아한테크코스_일정))) + .isInstanceOf(ExistExternalCategoryException.class); + } +} From 772546868758083856351d782ef3ae6d23b6fc50 Mon Sep 17 00:00:00 2001 From: koo Date: Thu, 15 Sep 2022 14:53:08 +0900 Subject: [PATCH 061/148] =?UTF-8?q?refactor:=20develop=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=EC=99=80=20=EC=B6=A9=EB=8F=8C=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=EC=9D=98=20=EC=B6=A9=EB=8F=8C?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 25 ++++++--- .../category/domain/CategoryRepository.java | 2 +- .../CategorySubscriptionService.java | 37 ------------- .../presentation/CategoryController.java | 10 ++-- .../ExternalCalendarController.java | 10 ++-- .../application/CategoryServiceTest.java | 34 ++++++++++++ .../domain/CategoryRepositoryTest.java | 4 +- .../CategorySubscriptionServiceTest.java | 53 ------------------- .../presentation/CategoryControllerTest.java | 8 +-- .../ExternalCalendarControllerTest.java | 8 +-- 10 files changed, 69 insertions(+), 122 deletions(-) delete mode 100644 backend/src/main/java/com/allog/dallog/domain/composition/application/CategorySubscriptionService.java delete mode 100644 backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index c05a261a..aadff403 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -16,6 +16,9 @@ import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; +import com.allog.dallog.domain.subscription.application.ColorPicker; +import com.allog.dallog.domain.subscription.domain.Color; +import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import java.util.List; import org.springframework.data.domain.Pageable; @@ -31,16 +34,18 @@ public class CategoryService { private final MemberRepository memberRepository; private final SubscriptionRepository subscriptionRepository; private final ScheduleRepository scheduleRepository; + private final ColorPicker colorPicker; public CategoryService(final CategoryRepository categoryRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, - final ScheduleRepository scheduleRepository) { + final ScheduleRepository scheduleRepository, final ColorPicker colorPicker) { this.categoryRepository = categoryRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; this.memberRepository = memberRepository; this.subscriptionRepository = subscriptionRepository; this.scheduleRepository = scheduleRepository; + this.colorPicker = colorPicker; } @Transactional @@ -48,9 +53,15 @@ public CategoryResponse save(final Long memberId, final CategoryCreateRequest re Member member = memberRepository.getById(memberId); Category category = request.toEntity(member); Category savedCategory = categoryRepository.save(category); + subscribeCategory(member, savedCategory); return new CategoryResponse(savedCategory); } + private void subscribeCategory(final Member member, final Category category) { + Color color = Color.pick(colorPicker.pickNumber()); + subscriptionRepository.save(new Subscription(member, category, color)); + } + @Transactional public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { List externalCategories = categoryRepository @@ -65,6 +76,11 @@ public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRe return response; } + public CategoryResponse findById(final Long id) { + Category category = categoryRepository.getById(id); + return new CategoryResponse(category); + } + public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { List categories = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); @@ -74,16 +90,11 @@ public CategoriesResponse findNormalByName(final String name, final Pageable pag public CategoriesResponse findMyCategories(final Long memberId, final String name, final Pageable pageable) { List categories - = categoryRepository.findByMemberIdAndNameContaining(name, memberId, pageable).getContent(); + = categoryRepository.findByMemberIdAndNameContaining(memberId, name, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } - public CategoryResponse findById(final Long id) { - Category category = categoryRepository.getById(id); - return new CategoryResponse(category); - } - @Transactional public void update(final Long memberId, final Long id, final CategoryUpdateRequest request) { categoryRepository.validateExistsByIdAndMemberId(id, memberId); diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 8312585b..7e9192f0 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -19,7 +19,7 @@ Slice findByNameContainingAndCategoryType(final String name, final Cat @Query("SELECT c " + "FROM Category c " + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") - Slice findByMemberIdAndNameContaining(final String name, final Long memberId, final Pageable pageable); + Slice findByMemberIdAndNameContaining(final Long memberId, final String name, final Pageable pageable); List findByMemberIdAndCategoryType(final Long memberId, final CategoryType categoryType); diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CategorySubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CategorySubscriptionService.java deleted file mode 100644 index 62199bdd..00000000 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CategorySubscriptionService.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.allog.dallog.domain.composition.application; - -import com.allog.dallog.domain.category.application.CategoryService; -import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; -import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; -import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.subscription.application.SubscriptionService; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Transactional(readOnly = true) -@Service -public class CategorySubscriptionService { - - private final CategoryService categoryService; - private final SubscriptionService subscriptionService; - - public CategorySubscriptionService(final CategoryService categoryService, - final SubscriptionService subscriptionService) { - this.categoryService = categoryService; - this.subscriptionService = subscriptionService; - } - - @Transactional - public CategoryResponse save(final Long memberId, final CategoryCreateRequest request) { - CategoryResponse response = categoryService.save(memberId, request); - subscriptionService.save(memberId, response.getId()); - return response; - } - - @Transactional - public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { - CategoryResponse response = categoryService.save(memberId, request); - subscriptionService.save(memberId, response.getId()); - return response; - } -} diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java index d504d3a0..192685b1 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -6,7 +6,6 @@ import com.allog.dallog.domain.category.dto.request.CategoryUpdateRequest; import com.allog.dallog.domain.category.dto.response.CategoriesResponse; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.composition.application.CategorySubscriptionService; import com.allog.dallog.presentation.auth.AuthenticationPrincipal; import java.net.URI; import javax.validation.Valid; @@ -27,23 +26,20 @@ public class CategoryController { private final CategoryService categoryService; - private final CategorySubscriptionService categorySubscriptionService; - public CategoryController(final CategoryService categoryService, - final CategorySubscriptionService categorySubscriptionService) { + public CategoryController(final CategoryService categoryService) { this.categoryService = categoryService; - this.categorySubscriptionService = categorySubscriptionService; } @PostMapping public ResponseEntity save(@AuthenticationPrincipal final LoginMember loginMember, @Valid @RequestBody final CategoryCreateRequest request) { - CategoryResponse categoryResponse = categorySubscriptionService.save(loginMember.getId(), request); + CategoryResponse categoryResponse = categoryService.save(loginMember.getId(), request); return ResponseEntity.created(URI.create("/api/categories/" + categoryResponse.getId())).body(categoryResponse); } @GetMapping - public ResponseEntity findPublicByName(@RequestParam(defaultValue = "") final String name, + public ResponseEntity findNormalByName(@RequestParam(defaultValue = "") final String name, final Pageable pageable) { return ResponseEntity.ok(categoryService.findNormalByName(name, pageable)); } diff --git a/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java index 14114bff..f43c8188 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java @@ -1,9 +1,9 @@ package com.allog.dallog.presentation; import com.allog.dallog.domain.auth.dto.LoginMember; +import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.composition.application.CategorySubscriptionService; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarService; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; import com.allog.dallog.presentation.auth.AuthenticationPrincipal; @@ -20,12 +20,12 @@ public class ExternalCalendarController { private final ExternalCalendarService externalCalendarService; - private final CategorySubscriptionService categorySubscriptionService; + private final CategoryService categoryService; public ExternalCalendarController(final ExternalCalendarService externalCalendarService, - final CategorySubscriptionService categorySubscriptionService) { + final CategoryService categoryService) { this.externalCalendarService = externalCalendarService; - this.categorySubscriptionService = categorySubscriptionService; + this.categoryService = categoryService; } @GetMapping @@ -37,7 +37,7 @@ public ResponseEntity getExternalCalendar( @PostMapping public ResponseEntity save(@AuthenticationPrincipal final LoginMember loginMember, @RequestBody final ExternalCategoryCreateRequest request) { - CategoryResponse response = categorySubscriptionService.save(loginMember.getId(), request); + CategoryResponse response = categoryService.save(loginMember.getId(), request); return ResponseEntity.created(URI.create("/api/categories/" + response.getId())).body(response); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index dca0fd1c..754bfc3f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.common.fixtures.AuthFixtures; import com.allog.dallog.common.fixtures.CategoryFixtures; import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.auth.exception.NoPermissionException; @@ -46,8 +47,10 @@ import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import com.allog.dallog.domain.subscription.application.SubscriptionService; +import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -108,6 +111,21 @@ class CategoryServiceTest extends ServiceTest { }); } + @DisplayName("카테고리 생성 시 자동으로 구독한다.") + @Test + void 카테고리_생성_시_자동으로_구독한다() { + // given + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); + + // when + categoryService.save(파랑_id, 공통_일정_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + + // then + assertThat(subscriptions).hasSize(2); + } + @DisplayName("새로운 카테고리를 생성 할 떄 이름이 공백이거나 길이가 20을 초과하는 경우 예외를 던진다.") @ParameterizedTest @ValueSource(strings = {"", "일이삼사오육칠팔구십일이삼사오육칠팔구십일", "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 카테고리"}) @@ -152,6 +170,22 @@ class CategoryServiceTest extends ServiceTest { .isInstanceOf(ExistExternalCategoryException.class); } + + @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") + @Test + void 외부_카테고리_생성_시_자동으로_구독한다() { + // given + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); + + // when + categoryService.save(파랑_id, 대한민국_공휴일_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + + // then + assertThat(subscriptions).hasSize(2); + } + @DisplayName("페이지와 제목을 받아 해당하는 구간의 카테고리를 가져온다.") @Test void 페이지와_제목을_받아_해당하는_구간의_카테고리를_가져온다() { diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java index aeccfab8..f9574883 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java @@ -96,7 +96,7 @@ class CategoryRepositoryTest extends RepositoryTest { PageRequest pageRequest = PageRequest.of(0, 8); // when - Slice categories = categoryRepository.findByMemberIdAndNameContaining("일", 관리자.getId(), pageRequest); + Slice categories = categoryRepository.findByMemberIdAndNameContaining(관리자.getId(), "일", pageRequest); // then assertAll(() -> { @@ -182,7 +182,7 @@ class CategoryRepositoryTest extends RepositoryTest { categoryRepository.deleteByMemberId(관리자.getId()); // then - assertThat(categoryRepository.findByMemberIdAndNameContaining("", 관리자.getId(), pageRequest)) + assertThat(categoryRepository.findByMemberIdAndNameContaining(관리자.getId(), "", pageRequest)) .hasSize(0); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java deleted file mode 100644 index 6f2d7b9c..00000000 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.allog.dallog.domain.composition.application; - -import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; -import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_생성_요청; -import static org.assertj.core.api.Assertions.assertThat; - -import com.allog.dallog.common.annotation.ServiceTest; -import com.allog.dallog.common.fixtures.AuthFixtures; -import com.allog.dallog.domain.subscription.domain.Subscription; -import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; - -class CategorySubscriptionServiceTest extends ServiceTest { - - @Autowired - private CategorySubscriptionService categorySubscriptionService; - - @Autowired - private SubscriptionRepository subscriptionRepository; - - @DisplayName("카테고리 생성 시 자동으로 구독한다.") - @Test - void 카테고리_생성_시_자동으로_구독한다() { - // given - Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); - - // when - categorySubscriptionService.save(파랑_id, 공통_일정_생성_요청); - - List subscriptions = subscriptionRepository.findByMemberId(파랑_id); - - // then - assertThat(subscriptions).hasSize(2); - } - - @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") - @Test - void 외부_카테고리_생성_시_자동으로_구독한다() { - // given - Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); - - // when - categorySubscriptionService.save(파랑_id, 대한민국_공휴일_생성_요청); - - List subscriptions = subscriptionRepository.findByMemberId(파랑_id); - - // then - assertThat(subscriptions).hasSize(2); - } -} diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java index 60c944ed..62e91c5f 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -45,7 +45,6 @@ import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.InvalidCategoryException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.composition.application.CategorySubscriptionService; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -68,15 +67,12 @@ class CategoryControllerTest extends ControllerTest { @MockBean private CategoryService categoryService; - @MockBean - private CategorySubscriptionService categorySubscriptionService; - @DisplayName("카테고리를 생성한다.") @Test void 카테고리를_생성한다() throws Exception { // given CategoryResponse 카테고리 = BE_일정_응답(후디_응답); - given(categorySubscriptionService.save(any(), any(CategoryCreateRequest.class))).willReturn(카테고리); + given(categoryService.save(any(), any(CategoryCreateRequest.class))).willReturn(카테고리); // when & then mockMvc.perform(post("/api/categories") @@ -118,7 +114,7 @@ class CategoryControllerTest extends ControllerTest { CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, NORMAL); willThrow(new InvalidCategoryException(CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE)) - .given(categorySubscriptionService) + .given(categoryService) .save(any(), any(CategoryCreateRequest.class)); // when & then diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java index 3cf5061b..5e5f1109 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -22,9 +22,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.allog.dallog.domain.auth.application.AuthService; +import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; -import com.allog.dallog.domain.composition.application.CategorySubscriptionService; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarService; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; @@ -49,7 +49,7 @@ class ExternalCalendarControllerTest extends ControllerTest { private ExternalCalendarService externalCalendarService; @MockBean - private CategorySubscriptionService categorySubscriptionService; + private CategoryService categoryService; @DisplayName("외부 캘린더의 일정을 조회하면 상태코드 200을 반환한다.") @Test @@ -79,7 +79,7 @@ class ExternalCalendarControllerTest extends ControllerTest { @Test void 외부_캘린더를_카테고리로_저장하면_상태코드_201을_반환한다() throws Exception { // given - given(categorySubscriptionService.save(any(), any(ExternalCategoryCreateRequest.class))).willReturn( + given(categoryService.save(any(), any(ExternalCategoryCreateRequest.class))).willReturn( 공통_일정_응답(후디_응답)); // when & then @@ -108,7 +108,7 @@ class ExternalCalendarControllerTest extends ControllerTest { void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { // given willThrow(new ExistExternalCategoryException()) - .given(categorySubscriptionService) + .given(categoryService) .save(any(), any(ExternalCategoryCreateRequest.class)); // when & then From 05ded570cc8ddae58f2899e908f771481de5202c Mon Sep 17 00:00:00 2001 From: koo Date: Fri, 16 Sep 2022 17:05:59 +0900 Subject: [PATCH 062/148] =?UTF-8?q?refactor:=20CategorySubscriptionService?= =?UTF-8?q?=EC=9D=98=20=EB=A1=9C=EC=A7=81=20CategoryService=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 19 +++---- .../application/CategoryServiceTest.java | 31 +++++++++++- .../application/CalendarServiceTest.java | 4 -- .../application/SchedulerServiceTest.java | 6 ++- .../application/SubscriptionServiceTest.java | 49 ++++++++++++++----- .../ExternalCalendarControllerTest.java | 11 ++--- 6 files changed, 86 insertions(+), 34 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index aadff403..e4f5f731 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -39,7 +39,8 @@ public class CategoryService { public CategoryService(final CategoryRepository categoryRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, - final ScheduleRepository scheduleRepository, final ColorPicker colorPicker) { + final ScheduleRepository scheduleRepository, + final ColorPicker colorPicker) { this.categoryRepository = categoryRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; this.memberRepository = memberRepository; @@ -53,7 +54,7 @@ public CategoryResponse save(final Long memberId, final CategoryCreateRequest re Member member = memberRepository.getById(memberId); Category category = request.toEntity(member); Category savedCategory = categoryRepository.save(category); - subscribeCategory(member, savedCategory); + subscribeCategory(member, category); return new CategoryResponse(savedCategory); } @@ -76,12 +77,7 @@ public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRe return response; } - public CategoryResponse findById(final Long id) { - Category category = categoryRepository.getById(id); - return new CategoryResponse(category); - } - - public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { + public CategoriesResponse findPublicByName(final String name, final Pageable pageable) { List categories = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); @@ -90,11 +86,16 @@ public CategoriesResponse findNormalByName(final String name, final Pageable pag public CategoriesResponse findMyCategories(final Long memberId, final String name, final Pageable pageable) { List categories - = categoryRepository.findByMemberIdAndNameContaining(memberId, name, pageable).getContent(); + = categoryRepository.findByNameContainingAndMemberId(name, memberId, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } + public CategoryResponse findById(final Long id) { + Category category = categoryRepository.getById(id); + return new CategoryResponse(category); + } + @Transactional public void update(final Long memberId, final Long id, final CategoryUpdateRequest request) { categoryRepository.validateExistsByIdAndMemberId(id, memberId); diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 754bfc3f..4e7d68d5 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -432,7 +432,6 @@ class CategoryServiceTest extends ServiceTest { // given Member 관리자 = memberRepository.save(관리자()); CategoryResponse 내_일정 = categoryService.save(관리자.getId(), 내_일정_생성_요청); - subscriptionService.save(관리자.getId(), 내_일정.getId()); // when & then assertThatThrownBy(() -> categoryService.delete(관리자.getId(), 내_일정.getId())) @@ -453,4 +452,34 @@ class CategoryServiceTest extends ServiceTest { assertThatThrownBy(() -> categoryService.findById(우아한테크코스_외부_일정.getId())) .isInstanceOf(NoSuchCategoryException.class); } + + @DisplayName("카테고리 생성 시 자동으로 구독한다.") + @Test + void 카테고리_생성_시_자동으로_구독한다() { + // given + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); + + // when + categoryService.save(파랑_id, 공통_일정_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + + // then + assertThat(subscriptions).hasSize(2); + } + + @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") + @Test + void 외부_카테고리_생성_시_자동으로_구독한다() { + // given + Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); + + // when + categoryService.save(파랑_id, 대한민국_공휴일_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + + // then + assertThat(subscriptions).hasSize(2); + } } diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index be4cceba..980d51b5 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -77,8 +77,6 @@ class CalendarServiceTest extends ServiceTest { CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); Category BE_일정 = categoryRepository.getById(BE_일정_응답.getId()); - subscriptionService.save(memberId, BE_일정.getId()); - /* 장기간 일정 */ scheduleService.save(memberId, BE_일정.getId(), new ScheduleCreateRequest("장기간 첫번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); @@ -113,8 +111,6 @@ class CalendarServiceTest extends ServiceTest { Category 우아한테크코스 = categoryRepository.getById(우아한테크코스_외부_일정_응답.getId()); externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); - subscriptionService.save(memberId, 우아한테크코스.getId()); - // when MemberScheduleResponses memberScheduleResponses = calendarService.findSchedulesByMemberId(memberId, new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java index 886f2234..9c1e99b9 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java @@ -162,6 +162,7 @@ class SchedulerServiceTest extends ServiceTest { SubscriptionResponse 매트_FE_일정_구독 = subscriptionService.save(매트_id, FE_일정.getId()); SubscriptionResponse 리버_FE_일정_구독 = subscriptionService.save(리버_id, FE_일정.getId()); + subscriptionService.update(매트_FE_일정_구독.getId(), 매트_id, new SubscriptionUpdateRequest(Color.COLOR_1, false)); subscriptionService.update(리버_FE_일정_구독.getId(), 리버_id, @@ -173,12 +174,13 @@ class SchedulerServiceTest extends ServiceTest { // then assertAll(() -> { - assertThat(actual).hasSize(6); + assertThat(actual).hasSize(7); assertThat(actual.stream().map(PeriodResponse::getStartDateTime)).containsExactly(날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_1분, 날짜_2022년_7월_20일_0시_0분, - 날짜_2022년_7월_27일_0시_0분); + 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_8월_15일_14시_0분); assertThat(actual.stream().map(PeriodResponse::getEndDateTime)).containsExactly(날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_20일_11시_59분, + 날짜_2022년_7월_27일_11시_59분, LocalDateTime.of(2022, 8, 31, 0, 0)); }); } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index 5235d5e8..80e7e114 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -2,14 +2,18 @@ import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.파랑_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_이름; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정_생성_요청; +import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; +import static com.allog.dallog.common.fixtures.SubscriptionFixtures.색상1_구독; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -17,8 +21,14 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.application.CategoryService; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.subscription.domain.Color; +import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.dto.request.SubscriptionUpdateRequest; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; @@ -39,6 +49,15 @@ class SubscriptionServiceTest extends ServiceTest { @Autowired private SubscriptionService subscriptionService; + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private SubscriptionRepository subscriptionRepository; + + @Autowired + private MemberRepository memberRepository; + @DisplayName("새로운 구독을 생성한다.") @Test void 새로운_구독을_생성한다() { @@ -46,8 +65,10 @@ class SubscriptionServiceTest extends ServiceTest { Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + // when - SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); + SubscriptionResponse response = subscriptionService.save(리버_id, BE_일정.getId()); // then assertThat(response.getCategory().getName()).isEqualTo(BE_일정_이름); @@ -74,7 +95,6 @@ class SubscriptionServiceTest extends ServiceTest { // given Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); - subscriptionService.save(후디_id, BE_일정.getId()); // when & then assertThatThrownBy(() -> subscriptionService.save(후디_id, BE_일정.getId())) @@ -87,7 +107,9 @@ class SubscriptionServiceTest extends ServiceTest { // given Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); - SubscriptionResponse 빨간색_구독 = subscriptionService.save(후디_id, BE_일정.getId()); + + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + SubscriptionResponse 빨간색_구독 = subscriptionService.save(리버_id, BE_일정.getId()); // when SubscriptionResponse foundResponse = subscriptionService.findById(빨간색_구독.getId()); @@ -134,12 +156,14 @@ class SubscriptionServiceTest extends ServiceTest { // given Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); - SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); + + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + SubscriptionResponse response = subscriptionService.save(리버_id, BE_일정.getId()); Color color = Color.COLOR_1; // when SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(color, true); - subscriptionService.update(response.getId(), 후디_id, request); + subscriptionService.update(response.getId(), 리버_id, request); // then assertAll(() -> { @@ -155,13 +179,15 @@ class SubscriptionServiceTest extends ServiceTest { // given Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); - SubscriptionResponse response = subscriptionService.save(후디_id, BE_일정.getId()); + + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + SubscriptionResponse response = subscriptionService.save(리버_id, BE_일정.getId()); // when SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(colorCode, true); // then - assertThatThrownBy(() -> subscriptionService.update(response.getId(), 후디_id, request)) + assertThatThrownBy(() -> subscriptionService.update(response.getId(), 리버_id, request)) .isInstanceOf(InvalidSubscriptionException.class); } @@ -205,13 +231,12 @@ class SubscriptionServiceTest extends ServiceTest { @Test void 자신이_만든_카테고리에_대한_구독을_삭제할_경우_예외를_던진다() { // given - Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); - - CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); - SubscriptionResponse 공통_일정_구독 = subscriptionService.save(관리자_id, 공통_일정.getId()); + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + Subscription 공통_일정_구독 = subscriptionRepository.save(색상1_구독(관리자, 공통_일정)); // when & then - assertThatThrownBy(() -> subscriptionService.delete(공통_일정_구독.getId(), 관리자_id)) + assertThatThrownBy(() -> subscriptionService.delete(공통_일정_구독.getId(), 관리자.getId())) .isInstanceOf(NoPermissionException.class); } } diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java index 5e5f1109..1a0fae81 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -65,7 +65,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .accept(MediaType.APPLICATION_JSON) ) .andDo(print()) - .andDo(document("externalCalendar/getExternalCalendar", + .andDo(document("external-calendars/get", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -79,8 +79,7 @@ class ExternalCalendarControllerTest extends ControllerTest { @Test void 외부_캘린더를_카테고리로_저장하면_상태코드_201을_반환한다() throws Exception { // given - given(categoryService.save(any(), any(ExternalCategoryCreateRequest.class))).willReturn( - 공통_일정_응답(후디_응답)); + given(categoryService.save(any(), any(ExternalCategoryCreateRequest.class))).willReturn(공통_일정_응답(후디_응답)); // when & then mockMvc.perform(post("/api/external-calendars/me") @@ -90,7 +89,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) ) .andDo(print()) - .andDo(document("externalCalendar/save", + .andDo(document("external-calendars/save", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( @@ -103,7 +102,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .andExpect(status().isCreated()); } - @DisplayName("외부 캘린더를 중복하여 저장하면 상태코드 400을 반환한다.") + @DisplayName("외부 캘린더를 카테고리로 저장하면 상태코드 201을 반환한다.") @Test void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { // given @@ -119,7 +118,7 @@ class ExternalCalendarControllerTest extends ControllerTest { .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) ) .andDo(print()) - .andDo(document("externalCalendar/save/failByDuplicate", + .andDo(document("external-calendars/duplicated-save", preprocessRequest(prettyPrint()), preprocessResponse(prettyPrint()), requestHeaders( From 8e82d297f2b021f933ae2e87ba3b786fa9b8ed9c Mon Sep 17 00:00:00 2001 From: koo Date: Sat, 17 Sep 2022 16:50:07 +0900 Subject: [PATCH 063/148] =?UTF-8?q?refactor:=20develop=20=EB=B8=8C?= =?UTF-8?q?=EB=9E=9C=EC=B9=98=EC=99=80=20=EC=B6=94=EA=B0=80=EC=A0=81?= =?UTF-8?q?=EC=9D=B8=20=EC=B6=A9=EB=8F=8C=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 4 +-- .../category/domain/CategoryRepository.java | 10 +++---- .../application/CategoryServiceTest.java | 30 ------------------- 3 files changed, 7 insertions(+), 37 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index e4f5f731..de36a6fc 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -77,7 +77,7 @@ public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRe return response; } - public CategoriesResponse findPublicByName(final String name, final Pageable pageable) { + public CategoriesResponse findNormalByName(final String name, final Pageable pageable) { List categories = categoryRepository.findByNameContainingAndCategoryType(name, NORMAL, pageable).getContent(); @@ -86,7 +86,7 @@ public CategoriesResponse findPublicByName(final String name, final Pageable pag public CategoriesResponse findMyCategories(final Long memberId, final String name, final Pageable pageable) { List categories - = categoryRepository.findByNameContainingAndMemberId(name, memberId, pageable).getContent(); + = categoryRepository.findByMemberIdAndNameContaining(memberId, name, pageable).getContent(); return new CategoriesResponse(pageable.getPageNumber(), categories); } diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 7e9192f0..fdc6f411 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -12,14 +12,14 @@ public interface CategoryRepository extends JpaRepository { @Query("SELECT c " + "FROM Category c " - + "WHERE c.name LIKE %:name% AND c.categoryType = :categoryType") - Slice findByNameContainingAndCategoryType(final String name, final CategoryType categoryType, - final Pageable pageable); + + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") + Slice findByMemberIdAndNameContaining(final Long memberId, final String name, final Pageable pageable); @Query("SELECT c " + "FROM Category c " - + "WHERE c.member.id = :memberId AND c.name LIKE %:name%") - Slice findByMemberIdAndNameContaining(final Long memberId, final String name, final Pageable pageable); + + "WHERE c.name LIKE %:name% AND c.categoryType = :categoryType") + Slice findByNameContainingAndCategoryType(final String name, final CategoryType categoryType, + final Pageable pageable); List findByMemberIdAndCategoryType(final Long memberId, final CategoryType categoryType); diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 4e7d68d5..8b012e45 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -452,34 +452,4 @@ class CategoryServiceTest extends ServiceTest { assertThatThrownBy(() -> categoryService.findById(우아한테크코스_외부_일정.getId())) .isInstanceOf(NoSuchCategoryException.class); } - - @DisplayName("카테고리 생성 시 자동으로 구독한다.") - @Test - void 카테고리_생성_시_자동으로_구독한다() { - // given - Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); - - // when - categoryService.save(파랑_id, 공통_일정_생성_요청); - - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); - - // then - assertThat(subscriptions).hasSize(2); - } - - @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") - @Test - void 외부_카테고리_생성_시_자동으로_구독한다() { - // given - Long 파랑_id = parseMemberId(AuthFixtures.파랑_인증_코드_토큰_요청()); - - // when - categoryService.save(파랑_id, 대한민국_공휴일_생성_요청); - - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); - - // then - assertThat(subscriptions).hasSize(2); - } } From 4cce7a43d9c5edacd07b8d616cbdf3c076c53f7e Mon Sep 17 00:00:00 2001 From: koo Date: Sun, 18 Sep 2022 19:59:05 +0900 Subject: [PATCH 064/148] =?UTF-8?q?refactor:=20=EC=BB=B4=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20=EC=98=A4=EB=A5=98=EA=B0=80=20=EB=B0=9C=EC=83=9D=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/application/CategoryService.java | 3 +-- .../application/SubscriptionService.java | 20 +++++++++---------- .../application/CategoryServiceTest.java | 12 ++++++----- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java index de36a6fc..61c2f41d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -39,8 +39,7 @@ public class CategoryService { public CategoryService(final CategoryRepository categoryRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, - final ScheduleRepository scheduleRepository, - final ColorPicker colorPicker) { + final ScheduleRepository scheduleRepository, final ColorPicker colorPicker) { this.categoryRepository = categoryRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; this.memberRepository = memberRepository; diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index cb871c75..8face99f 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -46,16 +46,6 @@ public SubscriptionResponse save(final Long memberId, final Long categoryId) { return new SubscriptionResponse(savedSubscription); } - public SubscriptionsResponse findByMemberId(final Long memberId) { - List subscriptions = subscriptionRepository.findByMemberId(memberId); - - List subscriptionResponses = subscriptions.stream() - .map(SubscriptionResponse::new) - .collect(Collectors.toList()); - - return new SubscriptionsResponse(subscriptionResponses); - } - public SubscriptionResponse findById(final Long id) { Subscription subscription = subscriptionRepository.getById(id); return new SubscriptionResponse(subscription); @@ -68,6 +58,16 @@ public List findByCategoryId(final Long categoryId) { .collect(Collectors.toList()); } + public SubscriptionsResponse findByMemberId(final Long memberId) { + List subscriptions = subscriptionRepository.findByMemberId(memberId); + + List subscriptionResponses = subscriptions.stream() + .map(SubscriptionResponse::new) + .collect(Collectors.toList()); + + return new SubscriptionsResponse(subscriptionResponses); + } + @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { subscriptionRepository.validateExistsByIdAndMemberId(id, memberId); diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java index 8b012e45..98996842 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -49,6 +49,7 @@ import com.allog.dallog.domain.subscription.application.SubscriptionService; import com.allog.dallog.domain.subscription.domain.Subscription; import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -120,10 +121,10 @@ class CategoryServiceTest extends ServiceTest { // when categoryService.save(파랑_id, 공통_일정_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); - + SubscriptionsResponse subscriptions = subscriptionService.findByMemberId(파랑_id); + List actual = subscriptions.getSubscriptions(); // then - assertThat(subscriptions).hasSize(2); + assertThat(actual).hasSize(2); } @DisplayName("새로운 카테고리를 생성 할 떄 이름이 공백이거나 길이가 20을 초과하는 경우 예외를 던진다.") @@ -180,10 +181,11 @@ class CategoryServiceTest extends ServiceTest { // when categoryService.save(파랑_id, 대한민국_공휴일_생성_요청); - List subscriptions = subscriptionService.getAllByMemberId(파랑_id); + SubscriptionsResponse subscriptions = subscriptionService.findByMemberId(파랑_id); + List actual = subscriptions.getSubscriptions(); // then - assertThat(subscriptions).hasSize(2); + assertThat(actual).hasSize(2); } @DisplayName("페이지와 제목을 받아 해당하는 구간의 카테고리를 가져온다.") From 3dbe4872e76efc0d3ab6c8d47b6bac112493403c Mon Sep 17 00:00:00 2001 From: hyeonic Date: Fri, 16 Sep 2022 15:52:47 +0900 Subject: [PATCH 065/148] =?UTF-8?q?test:=20MemberServiceTest=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=EB=90=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../member/application/MemberServiceTest.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java index c5cc4bbe..221a6e0f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java @@ -1,13 +1,22 @@ package com.allog.dallog.domain.member.application; +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.common.fixtures.AuthFixtures; +import com.allog.dallog.common.fixtures.SubscriptionFixtures; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.member.dto.MemberResponse; import com.allog.dallog.domain.member.dto.MemberUpdateRequest; import com.allog.dallog.domain.member.exception.NoSuchMemberException; +import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -17,6 +26,15 @@ class MemberServiceTest extends ServiceTest { @Autowired private MemberService memberService; + @Autowired + private MemberRepository memberRepository; + + @Autowired + private SubscriptionRepository subscriptionRepository; + + @Autowired + private CategoryRepository categoryRepository; + @DisplayName("id를 통해 회원을 단건 조회한다.") @Test void id를_통해_회원을_단건_조회한다() { @@ -28,6 +46,29 @@ class MemberServiceTest extends ServiceTest { .isEqualTo(파랑_id); } + @DisplayName("구독 id를 기반으로 member 정보를 조회한다.") + @Test + void 구독_id를_기반으로_member_정보를_조회한다() { + // given + Long 매트_id = parseMemberId(AuthFixtures.매트_인증_코드_토큰_요청()); + Member 매트 = memberRepository.getById(매트_id); + + Category BE_일정 = categoryRepository.save(BE_일정(매트)); + Subscription 색상_1_BE_일정_구독 = subscriptionRepository.save(SubscriptionFixtures.색상1_구독(매트, BE_일정)); + + // when + MemberResponse actual = memberService.findBySubscriptionId(색상_1_BE_일정_구독.getId()); + + // then + assertAll(() -> { + assertThat(actual.getId()).isEqualTo(매트.getId()); + assertThat(actual.getEmail()).isEqualTo(매트.getEmail()); + assertThat(actual.getDisplayName()).isEqualTo(매트.getDisplayName()); + assertThat(actual.getProfileImageUrl()).isEqualTo(매트.getProfileImageUrl()); + assertThat(actual.getSocialType()).isEqualTo(매트.getSocialType()); + }); + } + @DisplayName("회원의 이름을 수정한다.") @Test void 회원의_이름을_수정한다() { From 2137ca2ef008f95d4de175a1f40dfe09f753888f Mon Sep 17 00:00:00 2001 From: hyeonic Date: Fri, 16 Sep 2022 16:10:45 +0900 Subject: [PATCH 066/148] =?UTF-8?q?refactor:=20SubscriptionServiceTest=20?= =?UTF-8?q?=EA=B3=B5=EB=B0=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/subscription/application/SubscriptionServiceTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index 80e7e114..ba7bcf2b 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -1,6 +1,5 @@ package com.allog.dallog.domain.subscription.application; - import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; From 7a35270a8fa1e7a5d51aa5edc4d34c98cf0eb3cc Mon Sep 17 00:00:00 2001 From: hyeonic Date: Sun, 18 Sep 2022 23:55:45 +0900 Subject: [PATCH 067/148] =?UTF-8?q?test:=20subscriptionService=20=EB=88=84?= =?UTF-8?q?=EB=9D=BD=EB=90=9C=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EC=88=9C=EC=84=9C=20?= =?UTF-8?q?=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/SubscriptionService.java | 14 ++++++------- .../application/SubscriptionServiceTest.java | 21 +++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java index 8face99f..17806a8a 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -51,13 +51,6 @@ public SubscriptionResponse findById(final Long id) { return new SubscriptionResponse(subscription); } - public List findByCategoryId(final Long categoryId) { - return subscriptionRepository.findByCategoryId(categoryId) - .stream() - .map(SubscriptionResponse::new) - .collect(Collectors.toList()); - } - public SubscriptionsResponse findByMemberId(final Long memberId) { List subscriptions = subscriptionRepository.findByMemberId(memberId); @@ -68,6 +61,13 @@ public SubscriptionsResponse findByMemberId(final Long memberId) { return new SubscriptionsResponse(subscriptionResponses); } + public List findByCategoryId(final Long categoryId) { + return subscriptionRepository.findByCategoryId(categoryId) + .stream() + .map(SubscriptionResponse::new) + .collect(Collectors.toList()); + } + @Transactional public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { subscriptionRepository.validateExistsByIdAndMemberId(id, memberId); diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index ba7bcf2b..8d54d5b4 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -34,6 +34,7 @@ import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; import com.allog.dallog.domain.subscription.exception.InvalidSubscriptionException; import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -149,6 +150,26 @@ class SubscriptionServiceTest extends ServiceTest { assertThat(subscriptionsResponse.getSubscriptions()).hasSize(4); } + @DisplayName("category id를 기반으로 구독 정보를 조회한다.") + @Test + void category_id를_기반으로_구독_정보를_조회한다() { + // given + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); + Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(매트_id, BE_일정_생성_요청); + subscriptionService.save(파랑_id, BE_일정.getId()); + subscriptionService.save(리버_id, BE_일정.getId()); + subscriptionService.save(후디_id, BE_일정.getId()); + + // when + List actual = subscriptionService.findByCategoryId(BE_일정.getId()); + + // then + assertThat(actual).hasSize(4); + } + @DisplayName("구독 정보를 수정한다.") @Test void 구독_정보를_수정한다() { From 5e9ffe4b779a6d0c295aace7211b1854d6329c8a Mon Sep 17 00:00:00 2001 From: hyeonic Date: Fri, 16 Sep 2022 22:33:21 +0900 Subject: [PATCH 068/148] =?UTF-8?q?test:=20=EA=B0=9C=ED=96=89=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=B0=8F=20=EB=B6=88=ED=95=84=EC=9A=94=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/subscription/application/SubscriptionServiceTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index 8d54d5b4..1884c8e3 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -158,6 +158,7 @@ class SubscriptionServiceTest extends ServiceTest { Long 파랑_id = parseMemberId(파랑_인증_코드_토큰_요청()); Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(매트_id, BE_일정_생성_요청); subscriptionService.save(파랑_id, BE_일정.getId()); subscriptionService.save(리버_id, BE_일정.getId()); From 71d65c929c4003449ae93b7fcaa56b7e14020012 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Sun, 18 Sep 2022 23:49:20 +0900 Subject: [PATCH 069/148] =?UTF-8?q?docs:=20N=20+=201=20=EB=B0=9C=EC=83=9D?= =?UTF-8?q?=20=ED=8F=AC=EC=9D=B8=ED=8A=B8=20=EC=B2=B4=ED=81=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/subscription/domain/SubscriptionRepository.java | 2 ++ .../subscription/application/SubscriptionServiceTest.java | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java index 5707f1e4..a544968d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -14,6 +14,8 @@ public interface SubscriptionRepository extends JpaRepository categoryIds); + // TODO: N + 1 개선 예정 + // TODO: @EntityGraph(attributePaths = {"category", "category.member"}) List findByMemberId(final Long memberId); List findByCategoryId(final Long categoryId); diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java index 1884c8e3..e9bbd421 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -134,9 +134,11 @@ class SubscriptionServiceTest extends ServiceTest { void 회원_정보를_기반으로_구독_정보를_조회한다() { // given Long 관리자_id = parseMemberId(관리자_인증_코드_토큰_요청()); + Long 매트_id = parseMemberId(매트_인증_코드_토큰_요청()); + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); CategoryResponse 공통_일정 = categoryService.save(관리자_id, 공통_일정_생성_요청); - CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); - CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(매트_id, BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(리버_id, FE_일정_생성_요청); Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); subscriptionService.save(후디_id, 공통_일정.getId()); @@ -147,6 +149,7 @@ class SubscriptionServiceTest extends ServiceTest { SubscriptionsResponse subscriptionsResponse = subscriptionService.findByMemberId(후디_id); // then + // TODO: 개인 일정 구독 정보를 포함하여 3 + 1 = 4개, N + 1 문제 개선 예정 assertThat(subscriptionsResponse.getSubscriptions()).hasSize(4); } From 33cca36a6e6b10f3caf49aad0c9530d242109c01 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Thu, 15 Sep 2022 16:30:58 +0900 Subject: [PATCH 070/148] =?UTF-8?q?chore:=20react=20query=20dev=20tool=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ea4a8e8d..8f6fcd23 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,6 @@ import { AxiosError } from 'axios'; import { QueryClient, QueryClientProvider } from 'react-query'; +import { ReactQueryDevtools } from 'react-query/devtools'; import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; import useSnackBar from '@/hooks/useSnackBar'; @@ -62,6 +63,7 @@ function App() { + ); } From ee95df132ce34f3f3d99fed022239c4f2ba5d517 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Fri, 16 Sep 2022 18:01:12 +0900 Subject: [PATCH 071/148] =?UTF-8?q?perf:=20react=20query=20stale=20time=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 1 + frontend/src/hooks/useUserValue.ts | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8f6fcd23..6284ef5f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -34,6 +34,7 @@ function App() { queries: { retry: 1, retryDelay: 0, + staleTime: 1 * 60 * 1000, onError, }, mutations: { diff --git a/frontend/src/hooks/useUserValue.ts b/frontend/src/hooks/useUserValue.ts index ced217f8..815d6a24 100644 --- a/frontend/src/hooks/useUserValue.ts +++ b/frontend/src/hooks/useUserValue.ts @@ -37,7 +37,6 @@ function useUserValue() { retry: false, useErrorBoundary: false, enabled: isSuccess, - staleTime: 5 * 60 * 1000, } ); From 061f78b4b974d4ee85ed92d5881061c303492c1a Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Fri, 16 Sep 2022 18:02:39 +0900 Subject: [PATCH 072/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=83=80=EC=9E=85=20=EC=A7=80=EC=A0=95=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useControlledInput.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/hooks/useControlledInput.ts b/frontend/src/hooks/useControlledInput.ts index 96f763aa..287ed3a0 100644 --- a/frontend/src/hooks/useControlledInput.ts +++ b/frontend/src/hooks/useControlledInput.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; function useControlledInput(initialInputValue?: string) { - const [inputValue, setInputValue] = useState(initialInputValue ?? ''); + const [inputValue, setInputValue] = useState(initialInputValue ?? ''); const onChangeValue = ({ target, From ea4b3ad7951eb0c9f7bec751b3eda967d37bd053 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Sun, 18 Sep 2022 22:34:43 +0900 Subject: [PATCH 073/148] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=EC=9D=98=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EA=B0=92=20=EC=84=A4=EC=A0=95=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleAddModal/ScheduleAddModal.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx index f8d94c94..39304a08 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -58,11 +58,9 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { const { isLoading: isGetCategoryLoading, data } = useQuery< AxiosResponse, AxiosError - >(CACHE_KEY.MY_CATEGORIES, () => categoryApi.getMy(accessToken), { - onSuccess: (data) => onSuccessGetCategories(data), - }); + >(CACHE_KEY.MY_CATEGORIES, () => categoryApi.getMy(accessToken)); - const categoryId = useControlledInput(); + const categoryId = useControlledInput(String(data?.data[0].id)); const { mutate: postSchedule } = useMutation< AxiosResponse<{ schedules: ScheduleType[] }>, @@ -119,10 +117,6 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { postSchedule(allDayBody); }; - const onSuccessGetCategories = (data: AxiosResponse) => { - categoryId.setInputValue(`${data.data[0].id}`); - }; - const onSuccessPostSchedule = () => { queryClient.invalidateQueries(CACHE_KEY.SCHEDULES); From 3eec56c034fc68781e18eaf17c1364b59b12b87b Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 15 Sep 2022 17:54:23 +0900 Subject: [PATCH 074/148] =?UTF-8?q?feat:=20=EC=98=B5=EC=85=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@common/Select/Select.stories.tsx | 19 +++++ .../@common/Select/Select.styles.ts | 69 +++++++++++++++++++ .../src/components/@common/Select/Select.tsx | 68 ++++++++++++++++++ 3 files changed, 156 insertions(+) create mode 100644 frontend/src/components/@common/Select/Select.stories.tsx create mode 100644 frontend/src/components/@common/Select/Select.styles.ts create mode 100644 frontend/src/components/@common/Select/Select.tsx diff --git a/frontend/src/components/@common/Select/Select.stories.tsx b/frontend/src/components/@common/Select/Select.stories.tsx new file mode 100644 index 00000000..ee9ec077 --- /dev/null +++ b/frontend/src/components/@common/Select/Select.stories.tsx @@ -0,0 +1,19 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import { TIMES } from '@/constants/date'; + +import Select from './Select'; + +export default { + title: 'Components/@Common/Select', + component: Select, +} as ComponentMeta; + +const Template: ComponentStory = (args) => + + + ))} + + + + ); +} + +export default Select; From 1c2e8ca52344fadba3fdab174a7bdae2b416b788 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 15 Sep 2022 17:55:54 +0900 Subject: [PATCH 075/148] =?UTF-8?q?feat:=20time=20picker=20=EB=B0=8F=20opt?= =?UTF-8?q?ion=20height=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/date.ts | 9 ++++++++- frontend/src/constants/style.ts | 4 +++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index 877d4d00..cfaf8a17 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -5,4 +5,11 @@ const DATE_TIME = { const DAYS = ['일', '월', '화', '수', '목', '금', '토']; -export { DATE_TIME, DAYS }; +const TIMES = new Array(48) + .fill(0) + .map((_, arrIdx) => Math.floor(arrIdx / 2).toString()) + .map((hour, hourIdx) => + hourIdx % 2 === 0 ? `${hour.padStart(2, '0')}:00` : `${hour.padStart(2, '0')}:30` + ); + +export { DATE_TIME, DAYS, TIMES }; diff --git a/frontend/src/constants/style.ts b/frontend/src/constants/style.ts index 4cce3499..291cc195 100644 --- a/frontend/src/constants/style.ts +++ b/frontend/src/constants/style.ts @@ -27,4 +27,6 @@ const PALETTE = [ const TRANSPARENT = 'transparent'; -export { PALETTE, TRANSPARENT }; +const OPTION_HEIGHT = 36; + +export { OPTION_HEIGHT, PALETTE, TRANSPARENT }; From af52b03c95a6fbbe4e36bc3b01c65082da9482a3 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Fri, 16 Sep 2022 16:36:31 +0900 Subject: [PATCH 076/148] =?UTF-8?q?feat:=20=EC=98=B5=EC=85=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20dimmer=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=8B=9C=20option=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EA=BA=BC=EC=A7=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../@common/Select/Select.styles.ts | 22 ++++++++++++++++++- .../src/components/@common/Select/Select.tsx | 7 ++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/@common/Select/Select.styles.ts b/frontend/src/components/@common/Select/Select.styles.ts index c68828d8..4b3a6546 100644 --- a/frontend/src/components/@common/Select/Select.styles.ts +++ b/frontend/src/components/@common/Select/Select.styles.ts @@ -6,6 +6,16 @@ const hiddenStyle = css` display: none; `; +const dimmerStyle = (isSelectOpen: boolean) => css` + display: ${!isSelectOpen && 'none'}; + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: transparent; +`; + const selectStyle = ({ colors }: Theme) => css` width: 42.75rem; height: 11.75rem; @@ -17,6 +27,8 @@ const selectStyle = ({ colors }: Theme) => css` text-align: center; line-height: 12rem; + cursor: pointer; + &:focus { outline: none; border-color: ${colors.YELLOW_500}; @@ -66,4 +78,12 @@ const relativeStyle = css` position: relative; `; -export { labelStyle, hiddenStyle, selectStyle, optionStyle, optionLayoutStyle, relativeStyle }; +export { + dimmerStyle, + labelStyle, + hiddenStyle, + selectStyle, + optionStyle, + optionLayoutStyle, + relativeStyle, +}; diff --git a/frontend/src/components/@common/Select/Select.tsx b/frontend/src/components/@common/Select/Select.tsx index 85015ed2..10f120fd 100644 --- a/frontend/src/components/@common/Select/Select.tsx +++ b/frontend/src/components/@common/Select/Select.tsx @@ -6,6 +6,7 @@ import useToggle from '@/hooks/useToggle'; import { OPTION_HEIGHT } from '@/constants/style'; import { + dimmerStyle, hiddenStyle, labelStyle, optionLayoutStyle, @@ -35,8 +36,14 @@ function Select({ options, value, onChange }: SelectProps) { ref.current?.scrollTo(0, selectedPosition * OPTION_HEIGHT); }); + const handleClickDimmer = (e: React.MouseEvent) => { + e.stopPropagation(); + toggleSelectOpen(); + }; + return (
+
{value || '옵션 선택'}
From ae895756e81bb53b1f73e3a29a7e39293f2c524e Mon Sep 17 00:00:00 2001 From: jhy979 Date: Fri, 16 Sep 2022 16:36:49 +0900 Subject: [PATCH 077/148] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20min,max=20?= =?UTF-8?q?=EC=86=8D=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/@common/Fieldset/Fieldset.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/src/components/@common/Fieldset/Fieldset.tsx b/frontend/src/components/@common/Fieldset/Fieldset.tsx index db0eb11d..d2c8ca9c 100644 --- a/frontend/src/components/@common/Fieldset/Fieldset.tsx +++ b/frontend/src/components/@common/Fieldset/Fieldset.tsx @@ -16,6 +16,8 @@ interface FieldsetProps extends React.HTMLAttributes { onChange?: (e: React.ChangeEvent) => void; isValid?: boolean; errorMessage?: string; + min?: string | number; + max?: string | number; } function Fieldset({ @@ -32,6 +34,8 @@ function Fieldset({ labelText, isValid, errorMessage, + min, + max, }: FieldsetProps) { const theme = useTheme(); @@ -53,6 +57,8 @@ function Fieldset({ ref={refProp} disabled={disabled} onChange={onChange} + min={min} + max={max} /> {errorMessage && {errorMessage}}
From 20fea5aaabda020cdf7ad1bffebce0619d9ba558 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Fri, 16 Sep 2022 16:38:10 +0900 Subject: [PATCH 078/148] =?UTF-8?q?refactor:=20date=20=EC=9C=A0=ED=8B=B8?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EC=9D=B8=EC=9E=90null=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/SchedulingPage/SchedulingPage.tsx | 4 +- frontend/src/utils/date.ts | 39 +++++++++++++++++-- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/SchedulingPage/SchedulingPage.tsx b/frontend/src/pages/SchedulingPage/SchedulingPage.tsx index 9b37254d..95a0e67a 100644 --- a/frontend/src/pages/SchedulingPage/SchedulingPage.tsx +++ b/frontend/src/pages/SchedulingPage/SchedulingPage.tsx @@ -52,8 +52,8 @@ function SchedulingPage() { }); const category = useControlledInput(); - const startDateTime = useControlledInput(getDate(null)); - const endDateTime = useControlledInput(getDate(null)); + const startDateTime = useControlledInput(getDate()); + const endDateTime = useControlledInput(getDate()); const onSuccessGetSubscriptions = (data: AxiosResponse) => { category.setInputValue(`${data.data[0].category.id}`); diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 4b071421..a447bdfc 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -62,8 +62,8 @@ const getCalendarMonth = (year: number, month: number) => { }); }; -const getDate = (dateInfo: Omit | null) => { - if (dateInfo === null) { +const getDate = (dateInfo?: Omit) => { + if (dateInfo === undefined) { return getISODateString(new Date(+new Date() + 3240 * 10000).toISOString()); } @@ -72,8 +72,8 @@ const getDate = (dateInfo: Omit | null) => { return getISODateString(new Date(+new Date(year, month - 1, date) + 3240 * 10000).toISOString()); }; -const getDateTime = (dateInfo: Omit | null) => { - if (dateInfo === null) { +const getDateTime = (dateInfo?: Omit) => { + if (dateInfo === undefined) { return new Date(+new Date() + 3240 * 10000).toISOString().replace(/\..*/, '').slice(0, -3); } @@ -85,6 +85,35 @@ const getDateTime = (dateInfo: Omit | null) => { .slice(0, -3); }; +const getStartTime = () => { + const [nowHour, nowMinute] = new Date(+new Date() + 3240 * 10000) + .toISOString() + .replace(/\..*/, '') + .slice(0, -3) + .split('T')[1] + .split(':'); + + if (nowMinute === '00' || nowMinute === '30') return `${nowHour}:${nowMinute}`; + + if (nowMinute < '30') return `${nowHour}:30`; + + if (nowMinute > '30') { + if (nowHour >= '23') { + return '00:00'; + } + } + return `${+nowHour + 1}:00`; +}; + +const getEndTime = (startTime?: string) => { + const [nowHour, nowMinute] = + startTime === undefined ? getStartTime().split(':') : startTime.split(':'); + + return nowHour < '23' + ? `${(+nowHour + 1).toString().padStart(2, '0')}:${nowMinute}` + : `00:${nowMinute}`; +}; + const getDayFromFormattedDate = (date: string) => { return new Date(date).getDay(); }; @@ -152,6 +181,7 @@ export { getDate, getDateTime, getDayFromFormattedDate, + getEndTime, getFormattedDate, getISODateString, getKoreaISOString, @@ -159,6 +189,7 @@ export { getNextYearMonth, getOneHourEarlierISOString, getOneHourLaterISOString, + getStartTime, getThisDate, getThisMonth, getThisYear, From 0632242a963066e24cddfcd3c521eab1b6e4815d Mon Sep 17 00:00:00 2001 From: jhy979 Date: Sun, 18 Sep 2022 13:48:37 +0900 Subject: [PATCH 079/148] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=EC=BB=A4=EC=8A=A4=ED=85=80=ED=9B=85=EC=97=90=20dat?= =?UTF-8?q?e=EC=99=80=20time=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useValidateSchedule.ts | 56 ++++++++++++++++++----- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/frontend/src/hooks/useValidateSchedule.ts b/frontend/src/hooks/useValidateSchedule.ts index 6141a88b..3981edc9 100644 --- a/frontend/src/hooks/useValidateSchedule.ts +++ b/frontend/src/hooks/useValidateSchedule.ts @@ -1,28 +1,35 @@ import { validateLength, validateNotEmpty, validateStartEndDateTime } from '@/validation'; import { useEffect } from 'react'; +import { DATE_TIME } from '@/constants/date'; import { VALIDATION_SIZE } from '@/constants/validate'; -import { getOneHourEarlierISOString, getOneHourLaterISOString } from '@/utils/date'; +import { getEndTime, getNextDate } from '@/utils/date'; import useControlledInput from './useControlledInput'; interface useValidateScheduleParametersType { initialTitle?: string; - initialStartDateTime?: string; - initialEndDateTime?: string; + initialStartDate?: string; + initialStartTime?: string; + initialEndDate?: string; + initialEndTime?: string; initialMemo?: string; } function useValidateSchedule({ initialTitle, - initialStartDateTime, - initialEndDateTime, + initialStartDate, + initialStartTime, + initialEndDate, + initialEndTime, initialMemo, }: useValidateScheduleParametersType) { const title = useControlledInput(initialTitle); - const startDateTime = useControlledInput(initialStartDateTime); - const endDateTime = useControlledInput(initialEndDateTime); + const startDate = useControlledInput(initialStartDate); + const startTime = useControlledInput(initialStartTime || DATE_TIME.START); + const endDate = useControlledInput(initialEndDate); + const endTime = useControlledInput(initialEndTime || DATE_TIME.END); const memo = useControlledInput(initialMemo); const isValidSchedule = @@ -31,15 +38,40 @@ function useValidateSchedule({ VALIDATION_SIZE.MIN_LENGTH, VALIDATION_SIZE.SCHEDULE_TITLE_MAX_LENGTH ) && - validateStartEndDateTime(startDateTime.inputValue, endDateTime.inputValue) && + validateStartEndDateTime( + `${startDate.inputValue}T${startTime}`, + `${endDate.inputValue}T${endTime}` + ) && validateLength(memo.inputValue, 0, VALIDATION_SIZE.SCHEDULE_MEMO_MAX_LENGTH) && - validateNotEmpty(startDateTime.inputValue) && - validateNotEmpty(endDateTime.inputValue); + validateNotEmpty(startDate.inputValue) && + validateNotEmpty(endDate.inputValue); + + useEffect(() => { + if (startDate.inputValue + startTime.inputValue <= endDate.inputValue + endTime.inputValue) + return; + + if (startDate.inputValue > endDate.inputValue) { + endDate.setInputValue(startDate.inputValue); + } + + if (startTime.inputValue >= '23:00') { + const [year, month, day] = startDate.inputValue.split('-'); + const nextDate = getNextDate(new Date(+year, +month - 1, +day + 1), 1) + .toISOString() + .split('T')[0]; + + endDate.setInputValue(nextDate); + } + + endTime.setInputValue(getEndTime(startTime.inputValue)); + }, [startDate, startTime]); return { title, - startDateTime, - endDateTime, + startDate, + startTime, + endDate, + endTime, memo, isValidSchedule, }; From e97a55aae3a576bf8c4fb9080fe369fb51c14d54 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Sun, 18 Sep 2022 13:49:02 +0900 Subject: [PATCH 080/148] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=AA=A8=EB=8B=AC=EC=97=90=20Time=20Picker=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleAddModal.styles.ts | 22 +++++ .../ScheduleAddModal/ScheduleAddModal.tsx | 86 ++++++++++--------- 2 files changed, 67 insertions(+), 41 deletions(-) diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts index a42993b6..d113d781 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts @@ -136,13 +136,35 @@ const labelStyle = ({ colors }: Theme) => css` color: ${colors.GRAY_800}; `; +const dateTimePickerStyle = ({ flex }: Theme) => css` + ${flex.row}; + + justify-content: space-between; + align-items: flex-end; + + width: 100%; +`; + +const dateFieldsetStyle = (isAllDay: boolean) => { + return { + div: css` + width: ${isAllDay ? '100%' : '45%'}; + `, + input: css` + height: 11.75rem; + `, + }; +}; + export { arrow, categorySelect, cancelButton, checkboxStyle, controlButtons, + dateFieldsetStyle, dateTime, + dateTimePickerStyle, form, labelStyle, saveButton, diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx index 39304a08..fd7b0640 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -16,13 +16,14 @@ import { userState } from '@/recoil/atoms'; import Button from '@/components/@common/Button/Button'; import Fieldset from '@/components/@common/Fieldset/Fieldset'; +import Select from '@/components/@common/Select/Select'; import Spinner from '@/components/@common/Spinner/Spinner'; import { CACHE_KEY } from '@/constants/api'; -import { DATE_TIME } from '@/constants/date'; +import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; -import { getDate, getDateTime } from '@/utils/date'; +import { getDate, getEndTime, getStartTime } from '@/utils/date'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; @@ -33,7 +34,9 @@ import { categorySelect, checkboxStyle, controlButtons, + dateFieldsetStyle, dateTime, + dateTimePickerStyle, form, labelStyle, saveButton, @@ -73,19 +76,11 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { }, }); - const dateFieldset = isAllDay - ? { - type: 'date', - initialValue: getDate(dateInfo), - } - : { - type: 'datetime-local', - initialValue: getDateTime(dateInfo), - }; - const validationSchedule = useValidateSchedule({ - initialStartDateTime: dateFieldset.initialValue, - initialEndDateTime: dateFieldset.initialValue, + initialStartDate: getDate(dateInfo), + initialStartTime: isAllDay ? DATE_TIME.START : getStartTime(), + initialEndDate: getDate(dateInfo), + initialEndTime: isAllDay ? DATE_TIME.END : getEndTime(), }); const handleClickAllDayButton = () => { @@ -97,24 +92,12 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { const body = { title: validationSchedule.title.inputValue, - startDateTime: validationSchedule.startDateTime.inputValue, - endDateTime: validationSchedule.endDateTime.inputValue, + startDateTime: `${validationSchedule.startDate.inputValue}T${validationSchedule.startTime.inputValue}`, + endDateTime: `${validationSchedule.endDate.inputValue}T${validationSchedule.endTime.inputValue}`, memo: validationSchedule.memo.inputValue, }; - if (!isAllDay) { - postSchedule(body); - - return; - } - - const allDayBody = { - ...body, - startDateTime: `${body.startDateTime}T${DATE_TIME.START}`, - endDateTime: `${body.endDateTime}T${DATE_TIME.END}`, - }; - - postSchedule(allDayBody); + postSchedule(body); }; const onSuccessPostSchedule = () => { @@ -146,7 +129,7 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { autoFocus labelText="제목" /> -
+
-
+
+
+ {!isAllDay && ( + + )} +
카테고리 From c3c7698be8c19a37c67197e24c6654e016ed3989 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Sun, 18 Sep 2022 14:06:21 +0900 Subject: [PATCH 081/148] =?UTF-8?q?style:=20=EC=98=B5=EC=85=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=EB=A7=81=20=EC=9A=B0=EC=84=A0=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EC=A6=9D=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/@common/Select/Select.styles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/src/components/@common/Select/Select.styles.ts b/frontend/src/components/@common/Select/Select.styles.ts index 4b3a6546..e3cb479f 100644 --- a/frontend/src/components/@common/Select/Select.styles.ts +++ b/frontend/src/components/@common/Select/Select.styles.ts @@ -76,6 +76,7 @@ const labelStyle = css` const relativeStyle = css` position: relative; + z-index: 30; `; export { From 21dacd6fe26998fd512c5c2a1921cda67cf1007b Mon Sep 17 00:00:00 2001 From: jhy979 Date: Sun, 18 Sep 2022 14:12:10 +0900 Subject: [PATCH 082/148] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=8B=AC=EC=97=90=20Time=20Picker=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleModifyModal.styles.ts | 22 +++++ .../ScheduleModifyModal.tsx | 95 ++++++++++--------- 2 files changed, 73 insertions(+), 44 deletions(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts index 60ce89b9..8c99f0ec 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts @@ -33,6 +33,26 @@ const categoryStyle = ({ colors }: Theme, colorCode: string) => css` } `; +const dateFieldsetStyle = (isAllDay: boolean) => { + return { + div: css` + width: ${isAllDay ? '100%' : '45%'}; + `, + input: css` + height: 11.75rem; + `, + }; +}; + +const dateTimePickerStyle = ({ flex }: Theme) => css` + ${flex.row}; + + justify-content: space-between; + align-items: flex-end; + + width: 100%; +`; + const dateTimeStyle = ({ flex }: Theme) => css` ${flex.column} @@ -149,6 +169,8 @@ export { categoryStyle, checkboxStyle, controlButtonsStyle, + dateFieldsetStyle, + dateTimePickerStyle, dateTimeStyle, formStyle, labelStyle, diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index 7958598c..85f0a435 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -14,12 +14,13 @@ import { userState } from '@/recoil/atoms'; import Button from '@/components/@common/Button/Button'; import Fieldset from '@/components/@common/Fieldset/Fieldset'; +import Select from '@/components/@common/Select/Select'; import { CACHE_KEY } from '@/constants/api'; -import { DATE_TIME } from '@/constants/date'; +import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; -import { checkAllDay, getISODateString } from '@/utils/date'; +import { checkAllDay } from '@/utils/date'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; @@ -31,6 +32,8 @@ import { categoryStyle, checkboxStyle, controlButtonsStyle, + dateFieldsetStyle, + dateTimePickerStyle, dateTimeStyle, formStyle, labelStyle, @@ -72,24 +75,15 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr closeModal(); }; - const getDateFieldsetProps = (dateTime: string) => - isAllDay - ? { - type: 'date', - defaultValue: getISODateString(dateTime), - } - : { - type: 'datetime-local', - defaultValue: dateTime, - }; - - const startDateFieldsetProps = getDateFieldsetProps(scheduleInfo.startDateTime); - const endDateFieldsetProps = getDateFieldsetProps(scheduleInfo.endDateTime); + const [startDate, startTime] = scheduleInfo.startDateTime.split('T'); + const [endDate, endTime] = scheduleInfo.endDateTime.split('T'); const validationSchedule = useValidateSchedule({ initialTitle: scheduleInfo.title, - initialStartDateTime: startDateFieldsetProps.defaultValue, - initialEndDateTime: endDateFieldsetProps.defaultValue, + initialStartDate: startDate, + initialStartTime: startTime.slice(0, 5), + initialEndDate: endDate, + initialEndTime: endTime.slice(0, 5), initialMemo: scheduleInfo.memo, }); @@ -98,24 +92,16 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr const body = { title: validationSchedule.title.inputValue, - startDateTime: validationSchedule.startDateTime.inputValue, - endDateTime: validationSchedule.endDateTime.inputValue, + startDateTime: `${validationSchedule.startDate.inputValue}T${ + isAllDay ? DATE_TIME.START : validationSchedule.startTime.inputValue + }`, + endDateTime: `${validationSchedule.endDate.inputValue}T${ + isAllDay ? DATE_TIME.END : validationSchedule.endTime.inputValue + }`, memo: validationSchedule.memo.inputValue, }; - if (!isAllDay) { - mutate(body); - - return; - } - - const allDayBody = { - ...body, - startDateTime: `${body.startDateTime}T${DATE_TIME.START}`, - endDateTime: `${body.endDateTime}T${DATE_TIME.END}`, - }; - - mutate(allDayBody); + mutate(body); }; const handleClickAllDayButton = () => { @@ -144,7 +130,7 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr )} labelText="제목" /> -
+
-
+
+
+ {!isAllDay && ( + + )} +
Date: Mon, 19 Sep 2022 08:06:08 +0900 Subject: [PATCH 083/148] =?UTF-8?q?refactor:=20=EC=98=B5=EC=85=98=20?= =?UTF-8?q?=EC=84=A0=ED=83=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EC=8A=A4=ED=83=80=EC=9D=BC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/@common/Select/Select.styles.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/@common/Select/Select.styles.ts b/frontend/src/components/@common/Select/Select.styles.ts index e3cb479f..30aa265e 100644 --- a/frontend/src/components/@common/Select/Select.styles.ts +++ b/frontend/src/components/@common/Select/Select.styles.ts @@ -7,7 +7,8 @@ const hiddenStyle = css` `; const dimmerStyle = (isSelectOpen: boolean) => css` - display: ${!isSelectOpen && 'none'}; + ${!isSelectOpen && hiddenStyle}; + position: fixed; width: 100%; height: 100%; @@ -21,7 +22,6 @@ const selectStyle = ({ colors }: Theme) => css` height: 11.75rem; border-radius: 8px; border: 1px solid ${colors.GRAY_400}; - box-sizing: contain; font-size: 4rem; text-align: center; From f651e9845c5880bd28824fff6710e75f043ef195 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 08:31:55 +0900 Subject: [PATCH 084/148] =?UTF-8?q?refactor:=20date=20=EC=9C=A0=ED=8B=B8?= =?UTF-8?q?=20=ED=95=A8=EC=88=98=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/date.ts | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index a447bdfc..0c82bd12 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -63,7 +63,7 @@ const getCalendarMonth = (year: number, month: number) => { }; const getDate = (dateInfo?: Omit) => { - if (dateInfo === undefined) { + if (!dateInfo) { return getISODateString(new Date(+new Date() + 3240 * 10000).toISOString()); } @@ -73,7 +73,7 @@ const getDate = (dateInfo?: Omit) => { }; const getDateTime = (dateInfo?: Omit) => { - if (dateInfo === undefined) { + if (!dateInfo) { return new Date(+new Date() + 3240 * 10000).toISOString().replace(/\..*/, '').slice(0, -3); } @@ -86,32 +86,22 @@ const getDateTime = (dateInfo?: Omit) => { }; const getStartTime = () => { - const [nowHour, nowMinute] = new Date(+new Date() + 3240 * 10000) - .toISOString() - .replace(/\..*/, '') - .slice(0, -3) - .split('T')[1] - .split(':'); + const [nowHour, nowMinute] = getDateTime().split('T')[1].split(':'); - if (nowMinute === '00' || nowMinute === '30') return `${nowHour}:${nowMinute}`; + if (nowMinute === '00' || nowMinute === '30') return `${zeroFill(nowHour)}:${nowMinute}`; - if (nowMinute < '30') return `${nowHour}:30`; + if (nowMinute < '30') return `${zeroFill(nowHour)}:30`; - if (nowMinute > '30') { - if (nowHour >= '23') { - return '00:00'; - } - } - return `${+nowHour + 1}:00`; + if (nowHour >= '23') return '00:00'; + + return `${zeroFill(+nowHour + 1)}:00`; }; const getEndTime = (startTime?: string) => { const [nowHour, nowMinute] = startTime === undefined ? getStartTime().split(':') : startTime.split(':'); - return nowHour < '23' - ? `${(+nowHour + 1).toString().padStart(2, '0')}:${nowMinute}` - : `00:${nowMinute}`; + return nowHour < '23' ? `${zeroFill(+nowHour + 1)}:${nowMinute}` : `00:${nowMinute}`; }; const getDayFromFormattedDate = (date: string) => { From 1134d6b12198e91de5f77b16e509d3ef80f85365 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 08:32:11 +0900 Subject: [PATCH 085/148] =?UTF-8?q?refactor:=20date=20=EC=83=81=EC=88=98?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/date.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index cfaf8a17..cb516f48 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -1,3 +1,5 @@ +import { zeroFill } from '@/utils'; + const DATE_TIME = { START: '00:00', END: '23:59', @@ -8,8 +10,6 @@ const DAYS = ['일', '월', '화', '수', '목', '금', '토']; const TIMES = new Array(48) .fill(0) .map((_, arrIdx) => Math.floor(arrIdx / 2).toString()) - .map((hour, hourIdx) => - hourIdx % 2 === 0 ? `${hour.padStart(2, '0')}:00` : `${hour.padStart(2, '0')}:30` - ); + .map((hour, hourIdx) => (hourIdx % 2 === 0 ? `${zeroFill(hour)}:00` : `${zeroFill(hour)}:30`)); export { DATE_TIME, DAYS, TIMES }; From a4ed0a7e922bde6a22ac7d4e6897e7e1d0f795bd Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 12:27:31 +0900 Subject: [PATCH 086/148] =?UTF-8?q?feat:=20=EC=8B=9C=EC=9E=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EB=91=90=20=EB=B2=88=EC=A7=B8=20=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EC=82=AD=EC=A0=9C=20=EB=B0=8F=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/assets/how_to_use_1.png | Bin 175449 -> 0 bytes frontend/src/assets/how_to_use_2.png | Bin 146407 -> 0 bytes frontend/src/assets/how_to_use_3.png | Bin 215547 -> 0 bytes .../src/pages/StartPage/StartPage.styles.ts | 73 ------------------ frontend/src/pages/StartPage/StartPage.tsx | 58 -------------- 5 files changed, 131 deletions(-) delete mode 100644 frontend/src/assets/how_to_use_1.png delete mode 100644 frontend/src/assets/how_to_use_2.png delete mode 100644 frontend/src/assets/how_to_use_3.png diff --git a/frontend/src/assets/how_to_use_1.png b/frontend/src/assets/how_to_use_1.png deleted file mode 100644 index 19330c39631c84a4978a42e2e7c6f8df95f1b664..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175449 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�)I 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�&}%=IM3WJ;-PiG*Z6u&RF%YDNk^N4=He=Wi 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

29Q?sivk1K^nLXDe5ZH9Z#? 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 diff --git a/frontend/src/assets/how_to_use_3.png b/frontend/src/assets/how_to_use_3.png deleted file mode 100644 index 7dcb9867dfba6121086a923377d1af5e735843c0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215547 zcmYg%1yqz<_qU3KG)Q+yNH<7ItCaN6%Fx{%!bq2Zgdi>5F@SUr-3$z!($X;GeB-^Z z_y6r#EEeaP=fpmH|8^4bUR4nXlLGV6qenPz-^gh^dW0JL=n;xJ1{!i?q0>GY`Rkd} z8$H)YkFcKq`FZ>(?K>HA@Ug3gqRgX;QL0_!A5Sf%Riqz1s*c0D`-u7o6V>{)&824weC&O;Vxx?c@_DZ)iFY)de z1z==J$r6}WUVRM(8}0R7`BU1`L8-gxReQ-psWuW5v12E}_4)z?>wF-Qyd>96^}PQ& zo^vIk*Fr~a&(YcxfnTPYvg|Rq7bsgvdRckd)Vc5^P7ZF{@Ct7}YEVy`F}xM-nvFVnJnwMyt|=n>tN7PVQYQM%j2fVdJez& zjk~xnQwLc7IvC4kHiU=&P72#}FdfSISrX4XJ@Azxml~WwYJb%YZC2i7YP(DYP^Aq=Xy+Cvsw!Ho z-#;hydU0pZ+t|Ok7~YN(lt1f+E`w)(v(!$5>Ubr4y}P}4HSc-RhE)aPSnHa(oPH8Sas>EmSlkX`I^hXO#Cy@2LrqXn%gvz zTEg!S;kRJ>%1Y}c?Jnq-aZ7r~^9Esr-_3$Ne6K>qu?pP(N)zSm2U;8aZYrPCK5tPW zR6k!3|&cdN9x zMb;!?N0CcQYv^z)ktdb6 z>e`i}wtpyPr-#?A%hH6OVoOTiUG9t(=Z|suHGvvJE|Z>Q z*(wcj+?_=mn{=5lntF$}s=@i9;NQD{k$=o`SIY8+19KN-&&5MB9giEPj<4}dF4x9l z<`kc$LytX(+eYOZrjqBK&i$(HYybB_FH|fh@%^l=&zn$>biMYiu1u5&%mQ?{1i8Rdll!sKWD2> zPpqtyMOPuy9lsVzoZ~x;u9PITn8nB1*`X3ra5R;DjSubp2%%1Ihj&kr-?D=+oiBIJ z;vGJvJANgnDwYOas-hmQ+M>NA4=G@26i@fKd$Mmljcc&!Lx!=zFnhfd$7APzwX%wJ z+mf#OMK1FILs_k7IE<52(9{TXs6ob7-6^d*IAy083(MvXPdmM!A^(#0(gbnec6=dJ zbB`{Suxk0K%AHjGkoH|q{fh&TI#MXw#`!RA>AH=@n(l99vfQmiyX1dwhJq10kWx+> zy3Z?x3^qTXns9tw6ZQ{KQR1w*SJYrkE~l7z=$1rOT^D&Rd0u%N1!!u^@R#ufDpVvJCa_&d*0&l!}F*dycb_|*uzC0 z6Qy)Q7$uLEahh$ z92mxl1rC9PHB9Uz6IcI!sh;%~enVPsn7dLd&*?8M*Q#Sh)Ab7s+a?ieS9u;8?#{d} z$SBD!V50LH>()2?#wErOA~VF~M=sBqbv&zpb+k0;t00a(aoC#F4yvSV6Q9tyb-Z?G zET{&LRfdd}dEdq@LrX@kn!8tO^T*m}E@@#T<9(LTCAc|NA@P}o^)n9!dSMg#x4QZd z59kY0$WI{r$PQtL;H(ckaZG}Hwo3E*WtS|=Vd4nz6f z@v4EkKK9UEB`v|;=a2|abn~255uU7xsg*kXPsenE=U#HZqpgsPp2}AU=tRZ)&6sSy z+S>J`qssbN@YYnfdxe`p8C0SW68nzleGFVY9U4-^{Tj71V)}RVT>_@E+9s*(6eg-R zPS)73Gs^E{Rh1hgsvMvFGuN)<(Kg>rC17#73$E9`pXk~qyOewf=$NgulUad1NXeYM zpBk;?cFB4vLeP11v1-fcV$Ar9-!!dj_LZQEzfG}!Nw~h$OgQy{6+vYs8WhywYG7xX zd}9ZPJHy4@*!>=!74Mb zIm>XD0|$PdZ=aXB)dekJ94_H}Jd_e=jfKx5o&+W1Lx*}I{=_PpHg!C+=Dyznx zbuRx3*H@ES__{OJysN}_e*M`3UmZPFh2H7>^!53^up^?=gUTD9tlHu8@^ZUzu}USi z^Ox9;E2}wE+yJ!@W8s_X*VB({F8jkl{X5qVhaCiK4W%|`AkpkCl*=7RF(*}v0{7X@ zY=lotzkAV^b=*S{ezim04H`Q-d#U#u0xLm&u!dtzVu_}(BjP)>@=PCp$L zL;Y-F@{Cck0s0#&D^Or|eml0xeA4AM&TxgH9@KdiWrYZ$=B%pNA{OsUN274gmn<>%|32IHp)vG6%rbq3nWI{G+(MkjY_!6WSmQEPHPvgw^w zOoFLWfe^(`kOR{xSY`-q?W`!2dOVCo^Ol;Jf9P0_=6~wm^ zm8mkhisv5>nY;{GF=UDa(TtmmE#1%e>7xnjW5YNXK2*+*PodaG3;7!(de9`Po(_J4 z4{1F81wqiGF{|Q){ABn(wWsh@X;L7G=GlF@N-6ib==*NZx^!q~$8gA@A6mLtmn7nN z8`bj1Zu`TvqYa_Ww8Av$-3Ra(lbdM|f{LjoqiCdoE8cP$ADnPQ8K{}P6KH2uDBvfm zC9uM)urF4jg2hp{hNEJ91{z*51^sCE^}<3| zia#Sys~B%kn(K_5KQ|^wn-83RtUT9g3`ZwP4IURI6c;)dEZ4*^NkLi3x0zc}{0L2Ug0n>y z?BXr>eD;qhRWXR_@W=2?Lu7^<4`3J+-bb!QO8m4R6eTTXg}H;i-%DzeVZ#G|cp>zBs;iy= z!@43XtJ{6sgW29y&r2)+jQTZ5b5Yv>XlLrbH9G*1kwF( zzFbW;8yhx0e*P~>NyUA=y&(>GM>rKS{+&gvS3IvD);>Djp`0OO!r|vQh*u{Qg;pw3 z2ZLE4bgU|~0lv^)x&M(A4}+YJ@5LTHB8K(qao|i8%6hUdjrZmn>1!!682@BrG(#-Mq&?uxJ7uDFPc#Aog5G5LD3Xs8>+*CrZOZAQI8y~G zDE|z1b;(c>N{M+K$l2T5r^kU|>(g!iw}Zz0BQ_>cZ+0^kX#(Jq<9NZ(+y)c#Un(I^YgWjHnSE+O!6M=6+M6krFES)@Ztq^x60)0P6~ixS^|{bmUtdo{ zd-U1msM4YR-VeozMoIVk4H;F|x}4SGlARFu=Ulfbg>1ZN`gWfS3ipA@Elyk)7SMXS zb)BSZu0wPpLcq!%hL@6SIYzTH55|%Ks}h?x zzDXfj4!Av>BB#lk$IjMF#*R_3ID2wZiXB5gq}iVhPgIk9Zka?Pj1^=4;cmq;s}u2X zOB>qv-z7cbd!4K|ZkZJj?K&pGvMw110cD7JzFb#)_|2`mR_Q-4fluR!l2L;^M6QfP zOb|_{hQQ2>pw|UhxXOD78a@xLG3NbG7ISI2U|81=QY)qAaxULXovyFZleUI*>1v+m zGIRk$)=pkVcb>d_0~JWlsTMh7q^uCZ+f2NOKAGQl>{r5Okl84$XnEq;dQI`)j0}@} zxnokdPlcD&vbw> z0JNO`@1|^`hQB=A9t5U^Kq!Um2w{l~fo8v7Jmft@T*RtzV#lh{DbLs|Ce@(P`fvdYlxGQk_M4DQFE57u=sTybSl2{wG(-qlhYw?6UNaKY|?UV zA%Hzk3zJfcBKBvN{Yf4H=6}~0Ju!=g&dn9wQ@uTQR!O;k-b?fC<$&GG#nFuBuT4#& z?q2;eU(wi8VQfoS&lM;WRmXq23Nwa-&pW^Wi;q+@i;V{Y$zf{vfxb5K`kd1@-CQ zr=GC)!uHFA>a$+wJL-+sztXcwad2>0Sy^Ap%cJh??}HaM9~*jj)L$~tJY`` z3pE~<2u8|K{(YP4n6W^U5vOEjNt43u?3PPjRTY^*z5VQi&^P5bDk{>+aLGt{J~?qi z{m2_M#q>BO637|(uJ!y8R|BQF`Ib{Sb~a~*OU=jUES?};?WwD=!^;l~vDEO>Qh7jw z^K+tFoXEK_jb0zR!=+}m-Nm1YY_kS|?Z7|UTZOu}h{fIF6eBX{O;e*90v9)T+!&L* zWjOyiNJw10ywKRvLX{P8^HRuR5h>~@=(utY4qOR|iQq2)nzDfpdZFulhRdzK4ICY@ z(jc^tr<(%@LCSJ+Zci4GxYl^Jtm$cDLNg8E)N$^}>i|6^JIJV`2FBG=Fz3@m2%#!~ z2*B>(FF?gRd~8Y~Wie}~x*T4D2|QRmb@TB>mt&RXiX=hRPw;|`?cRH?SR?a}`JbBg z9!W|{Eymoqo1|7OeCGXWX=!Ck4npbii5W4l+5+I*m(Fm(5cyvC>c&`=6yigAT;kbr zsmYzzt^fX|k=Ido9N4(j{+swmj?q+JqwmAub*(iL7NWr+cDeE8PhQPNSW{aM;l>;$ zKPenrujuQK<|0;KU8D`V9I_Nyj%C_Z8cU?9HkA%u$BCbgqth*!#sM?s_}`w( zI22OhBaI^=IaSu6L>zdThe6=7Sb=Q4_Jhj)v#cR{#pq8E^G^%gpJ6|&Sc(E!J5O$p z+Q-II!*^H@#J*|sl_}uj@LBq&e14uE@R6} z9fssEn#}h-4kToZe%UX0h6Zost}}2LwVj6Rp25Y9mA{Ffe0>({c`y%R8q)cI2tu)P z68YHBo1@s^km}lpy}4<|2qh*VSqtQDiwu?xA!uNEQJ7ud#5cV!A|++A3#)CuTth<} zYF4=$Tm?YLsP^ZOqBCoB$In^Ssf`4e;Jv*96v|4GIIR41#e2TdxnbVnr6U)->igtd; z80#5k-H`H?s&D-$*}C)K4mAqMC4wt_SQoLgQ0UoXpDyG`eAdpZRUvzNJ^kKF+;fzC zhsuNF06%CiQrou5^@>Iye8bIzE;`!WW!Wr(Fb<l1Tg-IbHT$j51?>`*u`VOs%F#~FrFN1J9eoZ2k?2swA*dW%FOutXRFsrtkCt1-&M9hsuOdxI_^e5wQRohQS1qt4CVZL3N8a`VTJu7;um6})NCA+!5aXU{rd`h7Hl(E%;cntv)$2D zg+|k6uy?AYyGsvH>UQ0>yrQ<{{s3l4$Hb&JY%92YfBc-osQ$@Oz%g%tTQ5_%wuxUW z{=Sf%!$kmWU!=t_q|BWocw;rIWWp+0cM$RXhZ1(IJIDJ187$gf()cFZ+28NKMgI7z z^@4>dicV9y$*QV)dI69bl+gN_l*_Ql|L5Qk>E@~@3|4NEsSW^yJGNab>_1~va_Xlg zA0One*?`2&c5NKB-Ky?`7mfMwlO^KJe@09($0+JpgbKY@{Q|QiIGWX7bPV^(ygN0EbT3uF}=jgP7F10Xfkg36%YF5n9MQLMzv{+M5T!VmMg_g0Oo1)%6@rp={)-- zr)cMDZPdadgO-PN>z52gvX-#$@PIi#2A;2wq|-*jfXWx2jpP@~ZAslorMONqrUbbg zW@@l%W)D|`Q0g`|j3s>K#8ul0ezc;SdRFPw!Z4JkwMCipibsOLo7G-$*J0*b5Bc|H ziMRNrvlZR7W35FOLaItC&(9nu`^!+zU7;f)Ict7aMH)Ywlozj2MqCJ zaP((iUyWSM8b$=mh8@H1DtT?Dov;sPNvkckDZDQ2~v=C>&0r0z`)7P3|KmVl#^zYmE0t5tK!enjZ!QAg&2!_U*h9&BkgM%syOHwY{j$0JaM_&cf?;X zGuwb5_Z!uLQMbvtChd|tD-FW9RQ&8rFO{sT!?RaR{@2_{ueXFZLe`qVYr@$ z%Xvw^u$B>d?A)>r4hNrm*L2DNIU$hFCIuH+#v#=@3&g{{*HP;gG7=^{eC5I;?rl+1 zS4X5KaY5wb;^MSl-eA)j%g)Z;>$hD`Aw!F&kLQ@8wC{a@UD~{Panl3q)Bl>BM6d1l zxL@5>rH^q|{%cC{KVr<(-fkX8=Dqbr+>wgKgF4y<z+3fIq_iB|UCO}Cljho?QhIyx3S>2HY< zgI!C7zdFbZMbIFkTNgfILxCuHT5Mr>w75&Nl_u?}I5AsAn+x^!&n>gqWEuvP9P}y6 z4NK(>0YynUU(g-$6yu<1eTkjb2SF{qR}O}O_ZR)c!#&y3pgQAW|C{p(v~e+#hxu-Y z@-Wxdxi%h(H4` z_&Ja)>p{wuToug7<60MG$)j7i{@%2TOLaU`!nev|BrP0;*ZaB~nF*1-2<;xA>2Yko zulG1+K1vs`CiJ;Hh`89F-8g~JLXH9vzMRU*n}>Lk@a#)Z#b99hc~6Q7@V;QA=()+c zaf9x@^$SIL_sCo9b^on=mXgZiusW)$4-VND>Pom&937A>|7WGWE$vWPiOa!K`8B???2t_CZ3b&pn_jRv zyN>)Kbk(W;71>?kS$k}eGsm{Nq~g0bRkG@)wad;e7|~+eX29LZnYrepM>iL{i< zW857N_t!gL6N&deK$*BnWzq0D3Pud7?5^2{#ryb_wR*J#imrMVI{qvmM zT*l;aClLz###E|%c@kpm*!{h|U~1~Eto32>CXic1XSFIhcIZd_80QImC0yEAR!#ab z_`y=(>suqVbnD{c`EOMKr{h&=yQL<pRZh=)9wdCX;+|-i-amJk_Ua^^4GspORTDeO%(pj6YRS?@P zE|1^yF?)LyhJhp6Ifjfn{TG1HKKl!@lv6w!!f#r|{VI17u|aWNCbHGA^(Wp*lhps% z^>?3(UUt$zR24UOlFvx}!?gDsK0o|ksl=;jJJoR8pRAVNP<^VmT~Jnboi9{F6;kH! zx9q(wzu1NhUEkx;T{Cg`a;zXTQ}f?SL$F90-hOV9cKIX@^My^sN^ftH`g(XU7kn2Y zi20b8Z?I9eldz!o{h}_M|NF^&$J2@C6`&Qtwsxa{ZxL zd`40P`BWhT|DLj`iwhyg-zqWWNu)vzr)`XK?9irA|CMS?H0Hag!5s1j^0i-)Hud(Olo&d#!B=SNZkY*k{C3Z}i2`gf#zdLHWijLYFK4 z?7flyW@)v}{@(F&810&8dmC|vm}0VW5-fVeWKfoaV#VnT7q1AkQfuN=>!);iNkwE) z9@h}NR%nt=ish-<(JTNX)_~EMO6qzukxaxXTPIHh-rH0^pa-CKvEFl>tOP84)2%3A2C8`{9UEbJS(*TXKPj@#DT`rWaT0q$3ps1>CN_lV7u z9C57T)+R}+lv{%c&X|1p1a1Z{N5G1jL4%o@ea{Uy^wR%7y3{{A(! z=)Nvx#%3bmTs2nP?YfsVTQ~0aT%CEkPC{NG+&>v3k(2Xl*}+n67)FF6WcmJ9-K7y5 zrboUrQEoeI?7g^Z^;*ULkbsuuETwmatGmY;7@tP+Dj;#{rPxJ2+Ji- zg16{4r83WiTcdj7f3*~LZ`#dfOM#x@4%JJQ@b##jO-6zBe}OFrB@iO(QeM~K2Bd3+ zI)8K+&-!a^9cz27<)>@im6+2i-S6xD6?I~aI0MG0!Ig_`bo~}Js7$8oFKk2q-h7C7 z741I?O)wF`JjC}kIXOoL+G(GMlG38lXfWF!YsWWKL^aukTQ_ZUP+#;{u(}aQa6FpaCf< zNiSvt^xc~^i~D{{_@*y$?Q;zb+3)RBI@JwZ zFuI6&#=_PS{|bxT8fH}FN!GS;S7=N+m;H`afnbBNrap*%tL=s__M-?{o4xH9evuN(4PoWS; z6+2)mi8QSx5)V}vvsB`c>pciU-zB2)+F?(yZ33GDEYjW9o zXSYy~^PE}vbwEIy4(Y4K$$X`gaap{zVg9k{fMB!xjc@#x4Am$p1O5B~KuEM4qRl@R zux@&_+zOvf906?%4`biK_t;l2y;hx@b3BfpV0-ImsD^O_(j$-o;vb517Ze2R!}{uF zfYxVF`<#_6ps?M1G^w#S^(`J1Hgy>KkocfRdd(CRW;sd-xqKGRRMWHNIg|{12=4t8 z_hIQEqi4C*B~pdF$L8k5teouK+a>`a(REt9j*?|Y?Qhc&624b?6>X|mBOo)9FZi^T zNy59cg~Dif6rUtpeXT36{cB6Mf^TEFTl+tuW?+2rBe_|obcgN9OKWAmQ9>s*w;Z%8AR zyX}U#-hL&={eV)^jwj*y^WH0aLOW+?CsCuKvSI}lcO0Z0>}sYB2+lXv8~HATYB?%; z?~50=b?3G}Ppqf+ja5WzZXIa0!>N>axd)?08g8d)vE!6m#NvT-jWMb()jlawD>1(( zP3`r{rIdub10k`!U+874oYuNLaLi^2UjZ^XfX){)y0$sh9E03LT84fnz0^0R@kiXk zlS4Y?Bp=TNza&kmb1uE%iHy>C3<ZF z&dCq}_{_ObZwp!9&frHU&G4*%*3NCYFfwwg>_-^t>%?AA3g@U*X6jq_1L%}^{qA5X z^V!a`hTi0=SsrOZp_*RQKs^VBIoBIOf`FluFU+cBg&}*p3;;ocVSNbyHxn;$;}ZZW zg%Q`+6y0^C>-?PlmaPSGQmEg-n3agX@^F%_`V{tDv^i;2gq{C6>9Cjq`w+dY=+Je7 zx%@&04MY?fJhkg{Xwr|EG?EPj)oPyB1pi)Y&Q%-B)E~(P@Hnl$Hgp+anrn1s92^{U zsES+{#0EmLo2`c^x_?-`V(CXb2WjmXoIyx&$H3}pZ?#q~8RnS^O0lyMIFPGVhFzSnCb<4Rpy zJ0{CG(47uoAD|tF9apY{wYfOd)Gxf^RSspp1`DrrHYg>VuEQr*e4RLCH|Rm=rVFtd zR#r}*9a3}+O_0_@A2!w~Q~|f(-q1urN=pJrBlMnTg3QGG-u^HlE_->MV@YM!1QdjF zLr>Cgod{YPpy}prISv-7-RYW0sO1vwnl<(<*I5BvN=oV*XW?D1E2p)TZN3>~XB%%& zbNo>t5W~mTF5+{s6MI=yUKuvS+-8+Z`J6yK+V|mlo7c1_+yaO@Uc8|ec$4Tsn2C(R zrYt(!{^)mS3 zr|W3XX2eGCz5RAqiPUvUd{S=Tq~4=Fd}J6zy!{G`(FH5uB+7(NQj!KDqdKHi*)~xD861ZfP*%iX#3C~as`7mA zziwdkYoMXyQa#GhN0ylh{#1#OH7HS%(^SbOyS!og+^H@BXY8z$1@dcjtm9qMGca)P z;6S-taX-O)sM>XwhO_Db%Bi*=Ps#3cxf9yf>RYdurh)5sHR7pM=e;+{M4FT|Gq0xD zzdbOCmtODCTSl(=kpwnyRctUTuAIR~5F#&MR9+f31!I-jG@1+iK-c6k04k<_EM-fz zPYDA0Yd%55%#o;(iBSv;JiOM7J%Cj@I#$Q;#{w7&PfQRuDLR#2ox!O365nILf8Lv| z%+kq}L4)k>?ut;jEX?Hj>MCdPy;`4)VvJ9*=qJ#M(@7KA7x_5M`ZY;6JjR~%PZpk! zoaShy-DKbY?&0>R_NsbxU|Fp@3~#n#&QuNAFd#~!Q)UPXmX#-HI9vo%8aubps)okY z_;s&MYLq`za~MoCsBxFqfy!_W+(f~p`RM0|c}cF}#4c&RZu^mFg|#z~xiTHUyIoSJ zaHoqXZc-~LMOiMCN=OvIdH(asT%$Esaul+t>6DD{X_fr`%2o6xYV0jE$Ke$z)WOWE z%oRKKf*qfNeWV78RHzssn?joYWB7z3ZEqNDKzPHlj}p6yy1M$~tjaCX&eLI_g#~k8 z3}qOy9D&PkF+A?WP0M*LFJRNdbGU7`moQ5%)wE8?$twJ94m%?J4XxQYYakK=`qgMs z(Sf{`NBtX1Zd%8JTfRLmH}!74B|1k~QomG`R3@H4ZbH%gn$5^BY5Qjbg5wgYSGc}= z;xBt%qQRXMUeraK?bcc*H>h3JrNQ?D{j<+5|7nZftppO~AP;rk)+AK3Nz*<3X+ z%;VKTVOBs1PUbf5v~a@Oaeq9cb04|ohmKE==O6jjK&p)gIz*F4GYS9kH`O8EhAqk^ z%f;JDD@yvh9NT0)EJ$L*YEE6gex-tnzGq^3VSPlbBx-K?vFDgxpKHy-SKm)rCYT!3 zd*a*3<^8h}e&@=Y?Py{*U!#o`Z_a7rZC-f*LniUZfE4XJx2D8X;>fH%nLu_{YM~ER zIUN4I{^aY&s=N5S1``jMxriUU$|EB5EK@a>YpL@T2p0nXmf?BqdpJO=GZcG%Mdp z8*h)oZPrZ7&kMvqIu%u6^qxLd#Nt&sCiG4&9p^>@QU1+B1_=dwAs`M>S0`|&c*%ha zImG^Vlf7wgHz3ID17>C0H7;({G2APir3+S^$ie`%%6~C61vzOY2W(~c|t<1k(boHp6 z&|0v!CGc)eeg2bUEG~A-GD#^XF6ib;{>WHDnuz_tLktb)pz^*yfw2@?0r4EtS$*G0 zPqcl6O3h+O^A;Bo}^@pvvvQ&dzqCXCj zKuZTX<>fWY|K869bgIS{K0$JyWk|coF8c4{M2t_&+3I3r*?{azrZK>K%$W%FxsB9+ z&x5q@pRuFNGa{=*AwOgfoXZ^f_}^@T-26^MfqwZBX9GHR7P`C)>5SJ$Shw0)4|&3{kiCI%_dMXC6kCtv?L2B-W2pT$MN*MX55G)0a`rFEpo0{`f7KotzCy~N7eokcZU&9TNflLtbKGi-M z^1b5Mo`HdkW>_!&vLtnaNc-AP^3rGe<)sLe8@3=ajtP7Cdvw-ctOrL${~T|617Plg ztOWi`yo@5OnS*?#|KzKKbE={Hsg@4^y%oyC1C>JV5URDEiWC2L*ffOV;J82Mr&}~K zZ)R<2FV}rG5U5@M`af23ayXSaGJi3Csqb}A>Btc%gX6q4nDjBoK@N8|o?M(hz+*5ncqVjdCbD{u(EF!mou=O5ZJv!)Nz2yWmPgCfz( zhnX@TdP7Et>|FbY)DJ#50`K`hxxIX!C*yIKg5Z1gzZM^cT(TUT=)&VjFMl91*Af(T zaz~We@t@EF*3_cS3xT1KKXO$0M6bH!X71)Mk5~(;v_k$ckz0_Ag1t;j`mgX+%bKeJ z?>APxM7JD$-(^o%4<`SoKGibM_ML|p-xVS+UP~?p7`TQhB=eVDk>pg=YAeVm>Fvl? zV`M+T|HxF4N5G;Sc>hQFZT8HE=l<^r1`6>0&(76Z?C0?Ra)Z^P|KJ^(E{AT835RwL zP~|Wd=gWU{$?_DNT9i2=A%Sgt3Q#Xed~p4LYR`bLZT*v2NoNNcz};&+k!K?klKO9V z^f@ia@c7@0WT%X(FO2*JLHqAyyddxDe?&^&Lqw-;O1Ngb8k0x-KRyHS*{a`(_95{& zt?9P5kN1C9gY1&%mEua_01r|?n#>>VrtV3(cAp9VWhD{63~;@eLoeqK6d61u3f5?a z9Jpft-BNC9wiR%_x}m!5k1PvG{vD50Ik5L*`%sLTG3i5btF}_5^`~3$AL_*qwBC4~ zrO2!++KUXyn4SP(kG2Bk+=egzm+X_1tCqFia~Nj-;4WaTHdSd9{2Cmxquur zr82(yNu-ewg%Z)!LV5o;GZNk3I|4&w-FE*lFQK@4FA*I3|6!FyQD@MJRtV$fF3F#G z$6obUFD6mn|G}W_lj7<@wo;!zJo0-8I(ZU!GK}-z+?at@`R;yZ#h6RTq@XUF7OMV+ zrWRD>KmL;ChSmT*yr$Wa+hUdlUdJ)8>->ify`RHdQmJYF07J!qrm%h>Q0V_)jY&UD zF}dU)t4}O$%{aDPH6*nw51bJ7gU`FdeGCY`xAot$T?K%IS9~cA@Mb$^55D2dosA5^ zr_!U|Ywbt2l-iI=241~vsXcwPwF zGn0TPS@x{GZ?)TTXdku-I7ZR`J>taGFf9qcz(1f#%&JL%5e>uGed;RqUe{oyjW&MS zr-idUx}k!MJ*BMsviss#zP!HT)F04hE&Oi(uCr=v1Jiejf@--LC;jTTtgO+L?<)$5 zHCLSvGpEFJPDK>UKnG08Y3k(|K?9HM9~Jj6>uTLtBz=!w`c<{zb9oYbaOp)*6B|?E zNj9Lz>Li1O5S`v0DZq_8$Euab47o@07xUXrvnXR94UEoiF9Y1Ae*4;^?l~Roc?N`{ z(|VD!4=w}-a~~_)`CLgaUQ9!ZI;&l&3_}r;m&lrA{`J5(BYWRE1Q8rKwt~eVN`ARE zBefJ`nM^CA=$)YrxL8i~It6dBA`OZACvkmk7)*UdnXWYf=SxhSYUJL`*s3BPcU0rZ zep&b0JK|lWzx1p6GtYY4I@ z-x0o1#wN6+_m|9qpGehvUGq2YP8{6@TA2v12e=CdfcK(0(rCj1fZOW@QD>`XH*x^~ z&AiK_%oZZN;kc#=BT&L%1$;kD4XD$Euq#6T`{vv;a$Q;aHt%h7yW)lpQ%qYAiP4S` zel)9g=*(>A%+3U}4)GKr4W;hUHAz;>rHHW+NGROfNhsUz01Q8l_1^i)no@$|o6;6n zNb66#kV|7^aQONtf^a;X^Cm|hMbGkZo zkG14 zBXn&qYblhnZ?o=B<6_}tZ{;B*DTBLbX;xJ>ZE0@MSLnk^4QD^?@-&Hd97=EgOip`@ zvrx~sP00!rRkdRsF^$C5BKGAlqb+|Yza~_Cg8TQZk!9-iED7D;R)?xE6?M0#6=u0&qzQIL@XnwsM2vd;EL#|XFUcN zJL?tp?mXC?M#NCktx?mdxj!+)*h^wv@g%p&iqrT3A_maZj$YTtx5V0erfAFY6@`s>H)5Nen{+eVJoC(M zQ@pk=aQfd=tu?Ak==*A8qlsK{bevUkc4gk9SAW#S302ODB+v8BGrbOjo4VpC7k_&b zlNIXw;LMDSYpjaaT4TtK|3ntlSfPDvVl-0&zt}oQm%vT}B&Jt6O+vvN9MKZDG9sl> z&L1Gmb}$nL4`}a*F>2puWo#NTg4W5q`y?gIysUkBqXAXFvk5Uk%gQrbLk6?a1KTpW zRwle!?qzpS90K zs6DtnHs%x~y}IAmaI>wh`ZgyNap-j=i6Zy(DNG&@2L{{W(+Zl&4EHJ>kfM>*U@l`u z5!Khqm0h3YwfO}`f~@7WC!Xn6y{RGmteNMf>4NbV0D;kQisE; zp2`Vr`scou7>--RNo_M!}~Lnk1|aJgW=d;C2OeextcMyu@6JNv_SIa$UdJm53qQt z_6}(x`u%BPpZ>;11f^(%9}eqWBlZdrfXB{^s?8tas{Gqs5G}u+D(YqtXRdiqqD34* zr*VrdHyizejCy7cd`IoqP>$B#cw*=l=eQPOO#04)vpaW$jd|%a0NSv}0i*%|9yCa-HBHr#On&h^?d&x;iD7&qIM70u+ix+1G44wnqutdv_ zDJ)ND`CWO-!|4JjA!f^Ct)+1mMpWL5HRkgrV}d+!G&M@{r3jj&2&K(? z4b$M}dV)xLy{D$mRpE)gXnCl~B3MLu7%+1(WT|1Uv~l{-t?PBMWX^+~8pB@r19&l{wq6~`4&Mc* zn96PcdRDcd{&~h$sEdD-V$o?raHkh~`k8Ky54w$rpXLmk?wwyhCwsm0;pz zi>qsWcDbW~%hFWll*{jAm+-vNj;)EQu4QP4Lq860#hWrDLYBAA#PLOxy_3;|Y8l{M z=-nQXp%0YI%>Dl{b(R59bz1{g5s;SdR#LhdQV>uOB&0h8=^=*}QF`c98U>_>?q*2o z?v#$9d%lD3z4!gjUznM5_Lk=8EaRBM_Lx*>tIXqkG@_!^h|O^8q>**vi19QrsJ|me9hpBFkUwHQ z_WT1Q`?z;UBBE|yw}&EESk_5EBO^bHz+(J4i}1VPFVf{awIz@!VUb0TBaa$HN)tC( zxK$Bjd7mm?b-}){3nmc0UZ9x8yK0`^hqfQVFt#uamUkX!fy$5E*WJ7k`;-uO9Sl!p z5xmJk(A%BY6I*rPjlGb>H>+IrJU1tNj>{FW&4dr~+}Cj^lRYR7Ub+-9tB>S2L-9kV zrxnfwGMX~u5AXtrr*D}}%+IwBd$avo%cL8J8G8b5L1)INhJ1e(#K==Yj_X-5Bc$aU zZl8Z=wb=5sndvFNYp^T#(t%*v8Cvj=GXNv=EA!{DnRc&IQ+FJ+ zG(VD@(MU91LQM}nNc@=OehD%6-z3tiAv>42eIDA2G7sr| z9pC=yc~++#(BO{qmf5*Mc`S}NyZ?JQ^1S}-CXc-(T(bu3km@0R&it(?OP;ud-N4qqBt z`W7o}V166ukedVcp;{!rKDUop8KDQuTUnRKt*r>iuZ9T3ta7t#-|@MyCK<&j#(V#G zmHEi#7cZnIT3|JYe{39QTJ)ch`7rF9&^3)d>2DMzZPfJkKW~OSZ1%piOF13=?zrCH z8g)GiM(B_&fT;TuLy|2Hv+fP{2Rl}o32noAbGIbMNubberJGdrO zI+C5;DS6UbqBwB!4Sguz)sN5{-Nz)Z7N4Hec%aCcjr`buTf%Bsi0oDxqkSY;WblM^ zrtj;>yqD_2(#N`6Ok$@yn<{WONW%xwSK^b^KyUt ztq`nkeAL@rv0C}sAjr+wL-i3_u-(~bhsl*du=CKLLr6YWJbJ?FI8q|=XC(@FPeoY< znKs>Bhj0?R8}wV<-zxxLx=tETWJr11a<`fDR@D*OzV2_EZiY~_o4+>id{FS4?WGA~ zonGxjdFzAN&PVm)H2V{wX?5H0bX7yXp-5nz@I22(LOplp%)?qO3utwi8{7BV z=D=Fp5^#d>aA1L+&)&*s1IPLuRo&?)F0ibsoJSE?MZy|tC8%9@VN4x6&(=&gH={S> zk_*P|nHjPe)dDJ^I&0d^KprZ7Vo`f`)pRwR@C6Pd=bb=W9Rqr5%r`-D^k&}&evk&5 zNy$q}rJc9{dcKm@D00b{25+f}%7ycA#WY|)jK@0NSI-*U5xrsV%aXI~nQJbs9lwqb zyNfomY`&vdT$@W9A-;`sKVQ4tA@{N_wVBo3#T1u&b+GIlC6J+*m+~sieE#jFRV1Xb z?G$p{>0ZhR7WL%=KZyCQ`n%gIa6Ii(hpb=s(-(Hk@=JnWpjppU=BP@r?8RKiS-`pU zjH8^NR~aQ2jB?;uB~%{zk16Gwf@6bOzL5$9O)UKkqG=@^pH6ZcP(d6dGe)Ocy;@yv zWYH{-7Kh;aUgxT6H(%H_oU9lD(U>!MQqiwd*zVX$uA1JA=g)W2!*t{=95$qv>C$=vqyxrEsjs1%Zw5q?SRsAGV z{pv0Jd1_soQ^zlGrsJp(WaD?|)MziGHx4lio8U?~=)q4%!&Gb&t~sC@fuxxm3K*ub^B zk{BmQFry})YW#0VM#Qf%)}CT4@3hghIlVHWAnmkIr}Qm8(4+|Gomv%n{PUl}1X`V( zFYY{f5(*6?>k5Be^o(1Yx(tSEdX;|V(~TKwbY>>n)eUz)?v$2;Qs(!dg$;*Skb97g zmseWm>q0F{ggjRa4zJesXle}Xkts~UlOn&3B&XT!kCXek$(2fT*NxvzNbDM3ieS#+ z#*KR#hbs=DnedWfb9c>h(^b-nCDSo%a|cX^c2u}U7&2GA-qQK_+rBF2y!8ajH>CBo z#nj=K7p*}l^&d+w{ zU2c8{QBhN#MQ&=C$+=ZyT$~!&x0Y7*CiO}0D#=TjLunmp?(QZ%?1+Zu!qNXMM1CGx zlo%r}gnvIM4&3YS$ZACw=PGlF<%iV#z?fk{j@5xd=dQlgJwxQ8n zQw=p@-2Fj$pUpGu@HEdlP0XyE8Pr(uhdr#U-Fsu?2x2{5h#walZXx{oz&E1o!q%Kg zcewCa*Nj7{CwcRmeGm+qW@l*mP|z-4_I#AH^W?SJ5J%;0fIzZcD$jbEk*S6id9aj_ z2!>#hecJ0U#1=&j9~ncdd;`v#=4oB!$pt4w-W`vBhj?oB#e=R>!OuUOR%MP3jXJ(h zjCKr~w|uQ29W%Z{8^x+^R_Pmk0-09ahFa}tqgw)Z(BPXd^fI}-QW zNe`L=af%Ed8Ote0(kBUfaDh}L-uy4^K2@+z&uqUNI{D+wrxE7HVH)|eULL* zOh=&ET>r54>6x-ix;IAPpVO~n57T;=Z1t8BhHxzvX^P|gdrz~x>_G$B9LfY+2(EZ)C4bf!n@ z3mnI_jEzDFJx1ltwxd!sg>=g-?ezpA>{8*&`QHQSf2vcwptj`!f_v6!4+ZJ2=g)HJ zLF}HF<|Y$Rlv!`XP`93%hnik&WgU1xr25B=Xk|yss4X<>&pGwX8oV{13sG`?&QDpv z!Pz9lS+{IEwHuoU_vel{+qE2c_SutJ14T8i{@R;d zZdb}+Wz|!bLe@r}<0DQ-{)?yS#!o=x#odq`l|G?UrGJ znLCB7WXZEH6T9~fhTYA@>Q0qM(N<&VwQ?V-gWu; z%^d~nt`R^03oDRJrP5r(N&l*d=6dp37xhE^+sC!0T8j`Wxzu8Icm%Ogrf+p4wD{D= zapKg{MAnmi62#_a?pn1f%ME{?ZJBNYG*9`Xx4ksYCVp)ozjD)nzsb@$6@h;QWVQSU zZKOOmiZ|!ma$xQyJKUA=8`H78CW3m=qh8p5&O)2CQizCVeayz-o}ifz5sEU2tsjf# z3R?Up-Vcw0?IaK!E8GP)Ql@-S7DId~EGP>tux zpRX+;DX7OXHDKAU!p@`L%<8<@oIq|c1*)_6u{g@Y%`;176x3>~D1UIM+Z#&0)ri(M z`)&XzYj5VUw5Gw!I(TBuQt!e0JAYQ<_=miy+GyIg2q&o>!hwVZtXv$$S8kt0c8zDg zj1^~trz=_Wocd6LswU2f)|jYvzEI$&Pf!yEO$`Ykx@DMAJAaA*R9TrI^_v*W+%+`~vd{NNg=?`G3@c}#>8WMp7SK(? zalU*Yq#w~W18KAcXCd?T_B+M~fSXOo`BeYci;{dxpv10NC5}9dD+DX$e)?gUyWI9c zd=gusfB!4bbfcpaS@eR9KGrDM99$)3Y`pKEOgbMNJFtL9dh>%eICw@SWr|l%rFXy> zK7G-k&H?r`3<~{%MMo=u>&*cpHpS>Ls>&5>(yQctVQF(x?mA7xqm?*3UX3(88f)nU$8&}X$-le=(7Fv zzp^)F+o)**Ko$VpMUGV_tat6-ivL#v%*g50zu{S8{^QwQb#RKytj~$(osTE$;zZs7tC*9UvI8q9hfh|K=ko4r5%?h2 z+~6N78F+>xAh11pQR&Z_;7#`tT3{f4UfsZ8k0wNO2xZYQg8JC#wD`y&Kb|qSSaBcn zqvET2g*aH1Jh7x!ChcWoQ#I6q_MgNh2`jJ{6iU0kTFIrsQQgnl*s;*4wOo+x8f_TyY?7Vac5Ff*7$ret zPbv^}4zv&q7{9A9?l$KQ4yX{_J38rjeEoVJ-aAxNQ=znDH&*FPQhgxHBbU~_#WeE) z_5}Rq4SQ5{TGq}8@%^XTp#?nr`0F*do>E@%ugSBw<{ABNZ@OeMs;u({c(w=uNQ-qm z7ch{77P?{!z7&n1Ft9JuQcyD$bKegMR{jL}{&p6wIX=<4j#x6!&&v;d!TK%M!}`Sh znJ95j6)P8|d#yNwi`}O*aB~dHS&tj57saftW}Mb|%g#Ck-K8OKSs>!|Jsp!sabW_C zE_9c8#me@}u(9mtRLgI`Cp@mTi=Wl5EwZz6w4PmvZC!WEZk-MeAA3dyokM*VYEcI5 zWhbJb!mUwh!(h@ms394-@lLWm1WBgo<7GQ*Td8m z)d1zMl_ClG8LRM};}DcMxn~_=QmaI?!{344>}S<51cO3!z$*_QW@h2y0{1p+L7RYO z@M0=URgWMADYMrmMTJi};qNV%6~HahE`ulj<>tnl3J)015z^S6FFzn4pkY+nC50bV zi>+R4n-~B5{0fH{Stjn(p@n4ev0nnd`VwD%OPLa*|X##V124?QDngo z>cf=%kOY6Ny8u<0pL{ohoDZ!)s{{_TdPS}h&^R~8DEOKCm<7d(M{HeS?0mcjjD&^L zB7SAQS=|+s?F!lkdVlj9z9a{4KI2XfKZJNQ@12x%?R{lk=w5)!iqfMO#V~u$(QqkO z2;=Xk0qCvnkk_F2xs^B`@-Rq}j)Mb7KI}SP_R36i0>d0pcHmSdST}2w4 zEi8N#ksFHaK_Lc^YMAm_m8OL}9)p2sa^g9_S(1{3y~2VPOwfmxv z5HBw;(8n+)&6{glVwMUpP(~(25gh+;PTv6i$UQF!_mDx)pGZA=>=%m{~HXJx^PG1o1AvWR$-xhmr|NVKG-gv3=~ul+Hn zTd=lWy@aypG-F&BY%dA7fqtIQhAoS@-LD%(fMr$U8!z7w@Hc2Gk?VZg+V6S~02eoigiA=rDV?J9TYj-+Ul-A;|>m8qzAI z`cim)n9^fy@_l;j=gN4enF!?xDAV&Rsd?dtT+Y+_yd{l zEEb~fot^<|Zfiuv+G8?3j}$h;a##}upaLv>%~~2p7{Vc`@wH1QEYvy@qR$Xx_kn7) zKlrt+)cd!LN{A4JaJ~?ak9G`=j$;~`6K1k6O$l}r%xKv<*f)N{p%-y+f<`K*mrDqU z34h2IpJU>IIbbMg2P{;>fCr$stE;m!6VMH9&eifMG5Jv0>%1U2*_p(HDcZVISijTR zk$I`hyZdKClrrzogJSsIXR*fYl&bX=sqLOkNLge|1*rzDNKB70YVHM zBRd#2g!R3C$CEAQ^2Jo7+QW`@^+Hp3np!9w8iJ7tUWhREK`DFFo$ooBl6824NGI%j zYI+Dl7Pjb%VN^Uw>YuCr7_XL$+k|8wS%9GS^+e8L`-W^!aplF;@mKA7Gs*mTSrQdf<<}%Oofk?s9zOa# z7BUH^jHmTb7;1}RB=dq7`+&(vS=2M`wJctdR>F<@x3)3FCHCi`1krE-eQffdl zAv&Wt=dM3PVKMtm;AOh^m-hl5NnQ&&WRvXF{f^3MD=VgAhb<`GgM*~>=4$n6j3^(- zJ9<4^fxZ!RYD)Pnnitns>fY7%%x8&p(Fxy!{8tA%cDKxiMDo+6I?qr6krfvV>{m(h z#GPCWV!>$sqafEi6gz0$fF{H|11vdsn^#Q zl=^HXLXe{t-6e%zc`DHZmB0Cy=SE0vnA>ccWbTc&#ZT$H!-y0i-x3cp9jM({tyt*U z6eb?nmeIcLl-DkGqQreK(DvfxA&rc3*Ym{G5YDQ+AP{Eh$*MbIc|!C=>>6!OSQN|U z2$&=Hsu$t+eHV6$GtQ6R;96}t)jHnGu(HB9O(9A=LF0dEPy3!|*7asgFG}_B;|$XS zCfmiS;E?9VM|^=Un6rmu9m+O>XsLpDmIt~a*Hy*R(x*iDRL=52!7OJ)CdAjNw|~%FD&lOZ zb`T5fTGR^~^N*D!OT?64Ch@*JNu7%Glf1hK+v+Z_@>A9*Ap|&&hj`)$`d$zY{0qI$ zP>m(VLY+JPnHLgzT|)lVn16NZ9EYa%9BJ~}RS+Id`xQSylxpfkAnndgY3gQa>0>g<-6A1SLFGyy*VCv08e}WRP8RGSe-dv=)r}gKB#~z1|=-l@v`7zqugOB z%t6aI(F3zg{%xBTS{L*9M!H|bPsPNQ62IGmiSH%i=8eYWg3VXk1=tah(YfBF61NBK z7lV`N+*U_hWFozvj~aIlj{+1+3odBp&a%PXxD}G;t(P^(1W9AXZAT?Cp$Ap@)Y@mg ztJ+P2A@T=JWR0uqDAd9=`!ZsPB|jK^5=)&K_mzW@fM7_K+hWdq&240JYi+>DE!PzA z#k&?M-3yR++-O%crV_-6MRSEO7xf(;wHLh8c4fA2bfHo!d;@%8`Mv1ic;un7Dm+W4a_qHo?o2(L_h4ZTxe?pQZRG=TkmWXVSi$^5U;?P#kcbY zHOBg!yVle>D!cy8^9X(#uS!Xp8dm~ye)ZCm=VFK3()wq;pc=3~Nz7ZrF|Ga9Ae9?@ z#ldV}P!=?OrJ2h+VD9*!Emf#rN~oT+ zvb<}xC^xO;J6F@^)<)m24x`!J9) zow;GR&_HhFb4t9aE$nEu5Qgd$S z$mMW<*3wM;OFsB=ve$mQ;$V1tH~OG_+Rw(>+_Te{i4Evl%RRY^a)>CSm7?nsS5#*v) zWD~iOY=tDj%QH9!=mlqSPvjBn&7V2{Zl%zGl1ywqbcrwDk={a0Nqppz6s9tl@M^@6 z5i*4Mk4VjO)w0#aboTxDbVZ9pHwIF}0PkP*wFj>lZbCI(C@KT*gwv|=ulInxB6GOJ zIGeC2YMG~#mrwLGJ)%7K4BpFp&^Ah2BBNS_E>Syi)qx9H)`YctOWsxfASoXZC!HUi zmB_x^yCuz%kEF4?3GYCi^!efg1OltS91jGiybOa21}Hx%vkE402tNDZ(oSxvEYnc% zxW2bBs-F3MQ1w#1!z8`KCTbE-6G+WgQ+s!djQ2{c|9eab$0A1?4F`&A@MPgj_*fqp z5m($D92qc85gC`#nj~O`8D|jB;&M?T`ond6oZBTmu$E6*=G>gJ@B`n9I>_X(f}Cuo z{=s*}d=M{t-dqFOqFq=!xt4w?VtxeQGKBDWgqYBw;UQl?2r)B4edn(|A3s%|^&s8{ zzI4)R5N%%GVp_>q(8V2|K7l2nLeEB#vbTtToM1Ma*av%VN5Cm;gYB6+Z5*N~<++F< z>@y(zL$eT`FaA6#-o=0zr{-um@o*?r%gi)U>DlKdyrYo=pb2*A$u-Syzq_NvW-ANM zO|lwl7Lb0x*uzntgNo>GLc2xtGCF~3Ja~!-d8$)rX?Dc~aw=;aa=KoWqxMMIYkYiZ&J!B(iPuP6w!`^KF>k&uutx z3J!<906pFk;E9fkiFqWx<2nG?VMSDh@(tsNcE50O8a`$}g0w*M+Zv~IV*-4IaO5^> za@I{spP5}usdgJSk$|w+xCQxe%p7h=CF*&$1$>|^)6Eotg-Yh#?0uc?uS#AEEiJ}= zQ&E>e(8Sq?$md=}bW&bVzGxBb!!B18|!4V9#8B& z57$39tb;?`+%BH(no>q(#5a{zI$Vz#T4PjkypX$0Voe(3xZ#h&=n)SxmWv>zwOe@h zdDUN`!aU0+jZ*IjFY`wxK870%syc6#H}lzb)>a|;8&tzZ$*JVEaIRLz<5ouW&DlnD z;@8tjljF^_^B;HhQ)s=P40?H&m?wuG4}kD|uFvewo!$25Le3ZcXk~diB-zr8m*P9` zTeh0bbN$A1tt%rugr#9BAu_rC>Wr9Bp zF>0sSEsnljANkh&vv=bGwlUlJ}22%7j?!epL0$L-GY_5@0{?V zhB&u_;94r&xXD_R!QWCT0!~6=5)!Xdyz``d-_lc+6^}+M>W;hy6uAM2)h?T6uSUZ? zc|$o)WV|>^k!Hcf#Js&w|A%t7M<1`x_H+j&MeiuW1ecG`n`Li<`#RjZXd#+$d;XK7 z7#^(w=ZpL_FFIyw+`B&O!+gRFh5R^NoJ^ml7 zEFllB4i{2|qwZaV6U*7i#?SOZwi&8=3Fg8{i zeBCq-7X8=5CNlG;87aWV!@5Mo<0iw`B`7<(c|4Q9@N%G((0x{FN~tfwd5oU}ro_(Y zoFPX&%#{lvM-P+(-}EJLv;(T4TvP?8Pv6yu~@{XH0IdpLDJ&9_zu>{ z@kEJ8LPPh2C*yn_*YfippE}qs6??p32h@&eeSN*XZEHQT2w+=*jMCC~JJ1IEc~RTB z8hrH}rB=ZAE6p5edrd2O>hA4PisgIMqK}Q8;&)Vo8)B9<+-`CB=t%A@au9h|IyL>?bx77qm2fdwSrQUjMbG(Zjxv;!zCjIN-70NvG?H zM%JejT=l!WMRy25%nCTbKhW{;;HUeXk=MEHg_aM9V#&go$SBaasRB+yymCc}tzB%r zh|0zCX8ayp7g*~xR6{x?5xSSQb1s^aQ@S;h7E;+4CDal5PDBTbk+&R;3gsHH7M@$uB3p&-)(GiaurZWkGHLO9W;`D7qn|D zs$R%Hm6H(2bJQeW3fn-2Y&1EWPC%t$qbPa!$c1hRel?n8z=b5)ha!b~3lQ zxQCT&$@olbHXL_kUs$;_an|j1CGpUKvchE*+NHgZM;L3a{jMG1jVenszYG|TBaZAK zmaU>=BJGQ;gaOPEolAz~GeR#L8VTnXF!_at3oT2!=BG}Ap6Dbr9SNshEYSttT&Z0A z{>V5aGVry{PVsOVM7!GKM`3c^4ow#f68w*^q>pc4F!SyEW*347E)2K=ZrZ zF#>GQEUsRNt=01t7gi~seq+o&S9n`nwJv;ZL8bg%&yCkku!bdN?o9mWt>Bq6x;2$o z*J-45+VMimz)@^?`Ze+|}dn(3~7?GvOI!f?)LrL(a)87gKs zyDrVC=hKz`?y9dv!WPtfV4__(DR4L`3?H6+-~`GVJv*S{F(zy8)1aHPiiy zN%AWrDgGS9tT?X$koDbkZiVJRMVPfUVze*+lI0Jw4pul3m{mF7@MaSSTBV z!tbZ_`I#iB7UK-cJ+|A8%;>U3#h;i5J`bFATP#~o1-Yj~o;Vbjn+^>6hF~$f$iGzd z`h|na!0$`s=Ue}T(7qO{x_^Jtd#I+28d$+5->Hp1bz6O)pD<78gVGB29C(UP;UM?A z_JUF9rpG>xY6dgRf{9?o|LV2?F%Yy9U~$Gjk^+IW5P>VO}`zHUR@yUG(6 z?n->TYbVLC>&Z7~GV^ zaU948B_+jeDM)v8`=n`N!Mz{)v?sK@1UhNnH`_f(lgsDoU{%TK(=7ZTO`lL9iRr*^ zrN_!5W?WL$5;EV=yWkL)Z4rf=Zb>H7nUAYOkWR9OoAh$w}LSbeeeAzBq80 z(+AY6MKEd3dBw6eKh;9;rGsDSC1us>&;Uwaj1ZIi%XFY)!m9U~zM!ROnNAKgSeaMn z+slMJ$S$nQv5%vA2@r?kq4x0dE2~IuhsN>1p{HC6A%2Zj=-Kv{AYT!OC%o;1Kt^kJ z)GNa)2j#2Hd8W$;hc&d%PtptUy{Zf4b(|bddT?oHei32OT%n|0D0LG1)skwdYz-61 z2015~b$Y2REhGw=p1AsTP-`i_@>KLHGhkY+Mox-v!y#*D+Z1(e&&#WYFN#wmD?wr0 z?A7X1yL%m>CDz80mMllIZTebGctO@0*!v@>ADzRizJVo;2k+F#>0P>x2< z>rL(MhTxCNWvWlHSu5>^Jd|-#t%lIDr5HO>WnY~ilt6Ro0!(YE|7wH|Y5;pm&Do4i zaaAJ}_Xq2wSJB@y*uL5$M`1_h&E!UqDl!3s1qI!E||2h?8&ZbvIp zQ)j&vZG(z zxmlb^=dM{7PM2YwTH;B0vpg&5iYV%H+?1jU?4gop=c62-lbc8uv5`=wr42QL$Y7TUZh zc@fq4VEvPaOG<4Y?5IfI@!Tz7j z1(`|-cqzpwEn-PC^KMBO$P_bbM>`7cH{Hq$7irl9%vCyHKW2<^s;+e5{_)Ec2sqr=7m@U8H5 zJ~BvrexUQDeQ|l(&zBVJH!`Jo82iiM(-$$N#tTeu*opGZc8=Vf_pu1s(`r1t#Vg8P zg=So2gEvK4#j_5mLCFnTxxEj@K*(fk<*APtOyEe|EB%tNpOPR5rC_mx*7uUqRzZS# z{=B3OG+2z|6-PafrqUKJA`?9`-~qG5?sSzzV)%N`ieGq`J@j>2P=HJ}`d=gOm_|zc z@Fw3XkGK^V`oLFb6uXwX+;6Ei4rV?X8S)V)!##HmE_u?t&txFXw-i9Cc-Bb=n!v*3 zlIkttD|n7I=Xr3-!hZ4Gmsga0XTk5WpI

B6~<(o<=W`;eKsoE#cZf&H0^9g`U7 zzBPgh#Ac%}%|su1yG~v7tX|yCBc)!KV5wIAVZU*o{alLv%&ev3m$020?K28B@G$Zg ziz~AJk(Rz;1jx@rGO7+s6NqpInPmJoRTD|VZ2A_*Gap`+A;3uOKXX{t-Y0fw{i1$ z^v;X=&hgS>%6qgjNuq+V;c{^$zKa&FwNQI;J~$|i8_8mp(D033Qj%8O>rD3TTWA$d z%q(MCA%pmnxO^+5LK~5DjZwaOt12t;bAGkwl=NG@-Fjz?&5SDC-=e=|Wz_w6{QmT# zLYdx8Wz3q-%qK-o;q82I4g85$3hHj*E7?U-a+=&9KbADqRU0WM=CFSxaCR1`ys&da z##LoVMZSCjn(6jISxo30;TxRteFdN+&C>Q`f6v~H`U&O zxz+rBgOq3n!5%1l0NwqmYZRyHNE~wTlR2Aa97BC@2m0E1$TtUa0=k}rj699Jru9H| zU$_aMitUBRE&6?8J`iKHwJp*JM*=st^qLZ~VuWltu=mE5==}7h!e01xLuGlw=0mp1 z2Xq@Py}G58MX`s@@Bql>;`wsd;6+rNIJUdkCEgsj1JTI$cpZVZZ!rfe?4AvTNKmw@elQy~q-~ndquI;Mp{-(QufxyGvwsS!?4foT;Z$ZX4 zKU&f~jt|GKRaPAQydL>Ao-*dW;SC(DN2guzWev2Pstl@Wgj&4|i=x4^y*|Q05MG9+ zcJ)i2!}z$>5+Xkip8c9{g_*-M3TWHB6>)vf&YM?A4<1$ZFC<;Jgk6kVs7EJmE_zkw zxU`?*`33F|N`CG#ykfS$b)fb(t^YRcEJovdF0K#CrWDnU=cwIzadSI5$bAwmxw>VE z#B&(G%m^-RoDHu>j0d$yTGM8!4?KqmqOQmu~8aoqUgsJ}Wme^^B~-6oO~ZX83pi2HG^ICxGU}xd`Oz*KZNDari_gBZ=8HtpdO_A-kF0fIYrQLlZy|q=_iwG?8?-_iUPVsXpp=+D<{jn0ggBS9*H* zYHI?|Q$8+I$$QjB6n1oURH4{mfhVP-S0R4yal@s`Gf-2O9aAlv8?T%+42x!zx2}X* zIIJ*6{{)-wx$59uYP?8%<@uAXtRi3VrJ->K=RFcA`txzIdJK%m<{$1Qm5f&`K@xDa zg<<>UDkVQ+@KOJ40?TK?xwF@1ng*D}Akm`9K9zc1>HXqnQdN>sdG@#ESA z|ECh->G2B@1O4N=937a+d#6+SKf9Od7XoZ(yi|$V2>m_!lt+l7W0~UmKcy8*pw%y+ zB09bE7dOW=$|4&@2ydLet&diJJE&(Tq=Snn@_r7@nHS~^TYi$yaEhC@XG5t(~g zb)_6I1C;dIiA5`4c(ZjNO}IB*w2Y_<$%q1y@Mr-$q!@tT$T0>-peO6F-W9{=F30Yr zMkK!jFk#nt+pHcUh2=P;`W?GYbf@59R)5fj+%VE)sj8C_|OzwQ0T=gF}^`{~4 zfd76`1;)+AlKp{)gM&M@Qb;FC^T-vdv!K0kKOnnXv=^_cq^n+va<1Vvb=t}WjAGn> z52yy@I5bq_V#`4!4%L~%>z`-$OaKU#+Oc-dG3-#7Q#rjAX8O*!x+_!pvFhBPr=1eh z$~HTcQBlSy!-%%M<~x9W`UR-H01PoLTn=S-W$8y#Yfm30Knc>-LW^vyhF%h{zOKWb zr?DJY#?>4jvrcRwRNakfgxb7XqOh!_Q_dBSu<%HE3~qaqR%mTyoclOSTT5^9L3%f7 z%*v{?ix}&}zk4ZZIXO18IM9hIuT|-vDL8wH=|cJm2?5eFeXgq4uCQyB*u_PExj4kbXNcRofW5^0GS3S-EG-+n^R$(%Y5C$;9KXr&cbtwe$3$(q#s{1sT?| zpto;=Cq)VIshKlV^(O%RqCLI~Tf=Ww?8q(QGTnZK@M;?n?tmPr`(a3M1mAz?M}e?+ z>*g_)YULwp%Z~gjjvnz7gi*n!K`&J3XLIJ1&3`a`)WXKne#Hb{O}LSWqWPBdaH&+m1R^8PR;wNZZa1dkWZDB_trk$DomJ40_L zZj;zfN|Nyg?qmF6o)K&G&;Y*aSuL8R=gdsO@7C8G^y~!L%dlBZMYrF*(`U+FdXewn18V^qyLptnypsv6=OAn9G_bH!Jd`sr~8%-^x2WL3mH+j~5J?&0{M zQdgEc{!!5Gt-fAlTZ|fiuk^9@b<*aKSQK+z4!Nm4H<#Wwqn_k_Ql}bOdwNXI>0aAj z1CI!0p>H@|BrX%#3h9O_z{_hV>?r*e{-=BGmu}=Vw z!Xi!Vu<(Dqd+)yAzPti#sJR+`4rWDgaH;wKL~O(7Tw^_|4@+VuIRN_@>nqipQ>aht zn}3y?JTmO+t!jXJ574yz6cF{RSFb?+cfQ5%{PIq_zyHlW#2JtI+z~EkLHR#tB-uzD zm9s79y(kTM1LCF4|ID0#L0p~a&->g5_bW$!zFU#EAt^XXbmpEwfT_9TuB(tCf|IYVJ|Ml2S?|(LcPNV(L<~LDV4{HKDilt(n zoLZXx>$rTLL|WeUSAEAbd?w4Mp@Ayq_3x(yq(V<40h_&2Ae>hW?LSK-aDJ-c?`}W) z@k&qJ>pP#nTs5{c`*-tOA{nBhCZ2M3A2Yz67s`g;&iAj1kG+C@O++}Y37B+CjN8>c zR{kBi*QM1w<-pbc!n_^PrKS9LjIv6di2r8o*tr28C+9yh9L zu5ypM@c;gU{YLeD#{V&O)d5v4UzhIgP64G$x+IlGq&uX$$AEhPfd-IAB??(S}+ z>pSXu@Au807tT48d+(XqYpq!}$G`soj(Y?9|A+x;!0*nXjGW@gDtjt*!#KD!2QTYI zgL|DmR{RkBGkD{LVOM7Rw-y8FDKx)X5HpZ6Y7O!T_`n_9XMWx%f7i-)dNzAnj7uG^}TqURP z^ZorKe)gCz!4@dAkq1z zkRW^kpQS`=PI`1YzyHk$@Tp=v%%>4Ie^Z)4hEic_d97A=@;jW zH|i9wK0~h2kHjAcfjv>H-YBEt<$l4Me*r7wg;M7~4Fnp9TffpbkY1fd3ZS;$iWit< zAw)jL#bp<65&^Iz#Dw8%wPJ|HA@jLUa& z2r*~EAsL}y&j$~l40&Kz;MSsh3lgdjU?L^0-51slar({b0xK%yqv09U616`E!@cpm z(i5_-!KD}2^Y)*4Kn^FSgUdMBCK6DM3ApGbvT}qNuWWfql$U}4QHIEllY`&a?DbRn zUPjg=92lh3)>7^B5Ti3HiVV^~_WJK4%(%1hbauQ1kvv%XWY!W*NHoc&*m&`~Tb@34 z^=O@ET|tkt#UI9%S7NSMTk>N3#r2G%$9})pUvAVqv_EoaS_gDCCOt?43UYAJBJ+KI zhprXFs`C*0>gIIh-=eAzvgX(nWzhWo+B)x)q3v$FMr}Jw=V-5IvR=jc%M`xwt0)CX zz$D3~qkO`}EBa=HSE8})3>}5~Z&PUl7!CHWI;bs49zf5K7a;lwV(nEfAHF03EULyHpJQ`J-X$nBOvuLN(Vk%$E`K?5+gb|1eb zHTAQ6|8HklX|fnihm18yAKA&0?uFJhtI)0cO|~vO|Dd?dfC9b5BuG z#nr#2UWswN-R-^?NT#S6BkTwj$8(7TQMq(FskFn_7rkoOqFHE8zV+hLKutaP+vY!G z`&~I0j|lK2zell9ByDm5fcS+PrWi(qRQ5uoJfQg%?urmhR2#gP4jNTL*K=nRfOqMv z{`_{Si3@53?$7d!JAw5i+aqVdCZ{;1Ei_ zWLG+0kaWqVBI{{jrX#Nec%Z?0WXoqL!8QeZEX->nBKIcSLZLc` zzU@)k?U>*|Z`D!wZtumPl`xOJbJF5lWV^=8gELbt32}G0uG@k|SIiocJxGxvCFk$p z#zr)It`^koHhr?T4MS6PVVGQC^ph<)wPXbbU&K4QqrHIf1?wT_Ie zRnF3LURJaWNSQhx@b*K)vE>F}!1bBLeepI6fg>M389C}DFU zMH*}vKjU(t;5lK3njv)bg(hBOI)uM$NTp)Mza&M9#_l?F2{Pm+zpHJIGSt9pSHne^ zp7d-_NSzumI$U3_)k4lkDV(o}OocsKLc5tEz4)v-<4SLZEgx`>F!}vn`^*M}KJro^3(>TOZlXxTY?+LeI{-$rx=Hp`b4GY6}pdFDbY0u6hbeyLP3s$ zKa7z0a2M@T=pr%7l@I~;KD$(s$IL@EwWX^V0ds8wcrySWtZ2^{^jp?6-wTL32 zX*(AhQC6M|NzD|-%&R&$Ezlx&u+Hz8A`r9q6KD$Fm5hD^bTuLYd7{%9lq_*OEz>JLG=YV+V6Ky)fOel*sG)@lX1s^$K z-AFpTV-eGlB>L2M8awZEz9lNz1i^CLV;czpJ%M?B1kWQ3_0cA{Bx<-a|7wD07QJ_y zRkhD;z1sePg<@LXR-4S6OOEJ|UY1X%?p@##We0;JAw`%h>p;tJl#7FJ3C!U?JJF0> z)G$BtuPwLMM+st=BeY4d?x8*M-b%kp7bKKS%bPZBXlo)H(HWDMe2qCM&l;+u;mMAo z%Ce-D1a4`&8j~x<&h}i!6s>LU59PnCae?Wzxpi?Hw<~zV6O4c+*8&JFtWx;*T8KMA zMk_LPsK2oGGO^^psd(7}r8vZErl4UmGr#y0=sAfzGABvsds$ucIHn_1;lA82*z8iR zzwA)lqIA=)tkSA3Rr-h+wzc<%2OBnA+}zF@1b}9*b$L>%A{ zqKE);>aHina19_zkU7t)T>mXI5H1+J#Yy0a8fZC9<>q1?d|*$0&f8ig%G36QW3N2w z*SIUfNw$DLw<-W8DqdTzhReK9__2tCjiMvfkq{4~zVNzDUk>BlM^l20;`L%g+91+- zHfCSbNv!jj(yI`d{JeNK7DsjTA?MSaRx_okfQL>#SG=J&=cMIEwHGQ=VOd-mH>SDr z`=vo3rXqeAfJU)1%!PgxZqx)Bq*~oZXIZ|^+M^QcY{CzkepWlL%ZL7fGJ$sDYq`C^ zMYb8VU>68zOXEgp1(^;L7kKVx;E@~dajC;J-GI%wAjc!#VIGR~8}h6?b8A1?Cs;_B zxg!+iF*B;;L0VXFMG~g6rYvH6!=(&Sl~`?x`&AUMMj4_CK9`3?u?LHhQ=>Fe z$!78pOWx4(;4|Y;+S@H-$h2hR=$0A=t5Ze)n4k@xE=wF&s-07->uz@`&Fh)bNrz}q zhdHmS;9|%$A#r84+Jy4qPHL~+SywbYLC}ri>Hi3mMR{;p?HbIcPy!!@xVQEZu>5@c zP{{)vjeq?1wykT?kprWp3UI6lj4NT4d?j0X{!HhbR)b!k!rNny%!R!`(?vf0dw0#4 z&l=DgN!2{-77=S2_a??}MlwPBRoU$~iHf}h#eJuoS~LqV0Nku&7VZRF9TEBJo;yoQT>Ji@y8 zgssFL?d-dfbEYQXCb<%(ixKKX0LK%y&4o0>abNSC(!<`s4Va=&KXS>xU$c;le%Oaq z&RS=^I)yj-uD+|Ccy^=?adYi;mZS>JOO5FY<}`z>o`$1gBity7xD^oOp^|_Zh2vc( zwVpL0fD79{h@+iJg|%xm7DI+B3QyUwvUiXe%9oiLEekw|{+jet)Xf!#YZ{k3uOppP z)yk;r9;q^ew^=H^gy;1rllVN+?!!$uIX{Zv19(ayy}kdC*`>8R?E7P)-RV5CE!d#) z*uk6*hYHCup+stvD!*;E9=xTeN;}RDvbo)I8K!etU7kKu4do>m2SrccOLMk*34do8 zd_8$0?r`${_Fzz6Gs6jhz2ll<%oXqJTY0$jL={Z#L<3_j{Ohh!FU zg)3J6)JBflZZ{=GPvPssfTwwb+*9-Fh32J7DjUUP1M9IRidg(i#}g;Or~e})mEz}tsi1X_Y-@wDZk8_TGmCoJ zON+2b{KM;u-h3U(>0e{)8aExQ4i24_8F(!KkFCoF#?D)9$zE3!0J)wX6njX4HR`uTW>#C-a1h zY_VMDKVwgqSu{HgpCK)8SCb`CqSk3ZDxkqR3)j%50h6WNQ|Pxgmx8dl z*I0p?30r%{Fx7HVrp~f}P#L8f*NKi#M-<$-hPznMFj65;LcLRqlUbGYFhKlsRP^y} zazt%4&dPW8Nhp#~8 zVSTyGJu3+%#~mTI%K?T|CAr^M>7;>*}N~dn5^@HzPkJ=icInHJDoEdcia&P z%e7MLQOgB`p#x;)WbpzCz0O696{7_dvE=oNbOZlNX7VyQ;;t|>_$d|J9D~`opRdHU z@&$tAmS=DWU#Uxl>)*5+@(6q2o`|yf5kBejzTj7&rXUW++OExGyH)hV8A);zUZL%<28fZb+Uw<7IR7UDBht7 z>Jqsi`mQ8q9W~~UxLt~qU;P!CY${82<%zDtNj&T4 zXhgcV1%H1jBEi~L1jxBh1XGE&b6a{1^)CzBo5J$JjK)X{DuM-Twto_ zml8t6hJIXjU99Bb5utc*@p`(v{U#1U92#6X&IqM(9&&)DMf)a0|0n@@6+CT6j&ykZ z=JJ&q<^pMx7Pi6rQKaIR1tN~f!Z1xmcpBIrHuf8iWkD8r2Krw}57w>E0xuSw*t`V0 z4J$jE`(kB296{&MLtPIL`M5)LBZ(a#5KNYASWoC5F#RxXQiA!izF&;+D_%U5 zSDShFV?%s1EBdflNFuX2KaZ-o@3Ql&jh#Ae8TBv)^Tw(2YO<))C+spVX{3j+c(l)V zXQYis^)F8o{9pk?cG)D01}@uxa@rF7Rz^PxU{mxFKSeqTs?f^-!@6Le!rz(|Jr$}J zM4G-@f?=CPCxkTaUEFP|ti6M77Qx-2tO}jyT6a6AoR+!A=x2Ec!kpU?D=oLZ6hz6% z@6gAkO8^@x{Hlpc2foVa$3oxtBwQWh`Z{}lLCcy|1V6s6~lqTu{#;RM^-eio@= zhXuHV5d-l>E(b?kRieuV&{;qec&K_o>~4Hk-1G>io@dY}w1>1xZCPJwh0TOsTpPyE zQYzJ+nKyir$FMHKXceS7n*I7!{pI>3;Ilsurh#pajOz?eR|YRyaM6|Fy^wFQIAd>M zS;bH*UXnp_2j3PoCkcwc43+<;L4Jg@CTFFWZ5;+l#I9UA^FkQvN@7;K3Lxk5onRL| z3ShPQeb%E!!Kvy`(=(3{7W$g&R{ZRf$NR@aOh|28Os>x&rS$w8lQ{7jf#TG)em_Bp zc?*Z>b{443@IoQga}=T)m=^;wYY#d|X-9pURmdl`mS3G?&W>-1iSX{a1QI)DovU6x zORq|F_BK7fjrO8ia}F2zz8M>bx|{o&KbEqh(NB8}Q`M4} z73Rvn6OeIf3V*+Y&PbvKD~K` zK2y*K-t7A2NtNTS0@HhPrMKC@-VNs55mgT{6@J6<%2UE6=*xrwv4E1Hn?dgd)SrvhdNm(bwu8aH{r8m4Q`0diPKVSx+dxBYNLVH_b4e2U3CS997MvN$Ys_NA`WG4Pf+JKIcG;g zr1q1ezRvd)Dd@JCgTGWbZI&1^+ptdpLc~KepfBC)Aw1E=-p*w^5hKR)jIS@YHeXFp zxw|2i~Yy?c1qpdqF8 zotM7du|((YArC8Xpt}F=TYb*NNkr7^Yd~D;4JLu5sr4r_;D8PSn1n8UYxOlSnNR%s zC&ExC!weGNRHLbBgrq&VhH~=3$`$yTI%#$eZcmi9-;63IEXj^`GoU4|&2(;PmuQAP z1s{rBIj+;9imGO4A&Y>EA>@*ZE2Ov-~`=Hsgy(*$+N;{g$npimjR)MzLeG zskY~jivgRZKJo%bi50}b)Fd?K&$2wUcxT`k^A%?eeAdrKg)d9o64*CMmP_CZx5^ zpf6kQAg%C+{LPoTf#%Y%8r$frLQ0A6Mh?POy8 zOnWkchwwFKdTRqZ)fyUQC@XUlx7k*t-6~sg5go*7=bAiA@dn*y^c9`ej;CqEhMUk? zhpCRS&nO$rw{K8XM!&CT(3~;)U^{EI24+9lrA$bq@sS)MX~Wm2yDlSh+ndlGm90u=8 zf)3+**9!R%KZ5{|8Ld&^M#RBD3S195YBnMP$D!SLdJ>34_~8%-6Is+JLo&5~7XnS( z5ktA|Ym}Zu41j7!!+DJ2(fQfVDHi4Shh{Xb8IHOI0|FuRI*n=r`Rk6BTM5pg)!FA3 z?P3yr7G$Bc6nu@CI!mGiv8HuCGNZ~nz6cTX76ka4_NMx~WzGG-8fHcICh*i7m8Do1 zqhw~*ToZ_(!L=3pK*h$N2$|DN33YuZ$hEVtQkHAataOoP>mF5g^|_myJi!8$EGRS7 z$C}~FE7P9q%Z<1zd>UV0!44D*#@T_5U*i&wH(Vq0)X%TUkU=a4z6pKFBSd8pLbSCc z4=l;iVe)Tp;DS?%l)=sVz#Lge|C)<ng_8Q=2>bLVs(($=~u zoQ<0;)CGfEB=ALz+D4Y!{>vIaC$pPgy52qvqXz23ugZ#57kuqgA(Y;op9OTDYZ)&T zMY|FGr0%#!lVuo(t3W7ezHj{HK$voR#~<@h`vh+7HM{ z3~nV*^kUZs{}E*jZsX8~%L;_aZO$t*_bSwlw;WBKoEnS)=K)>)C7pIYPkNk;yzM!* zR8$kN?qMMHAS7?EzrvBfu4jqJEW3fYjPZhGE~v*=_}x;1BZg0Rsh#%|vj9JLJDsR; zL;AC?EKK*06E95eV*vsVe#=pgD^j@2_EM^|r8S?WFyn;vAq;i-G-Tj3iAtQ_-?P8T z>~?SQ`gr+X+-%P#aAMo!)e>Ck6r`~RTseZ4n%aPTVQKs&IX*rnAAOy^j*h)2s0smN z+p^4W^Gf7RxVU}VC{u@QNSjc#g@5G>&3i22cU5Rr^w1vp2tNQ+!KcBZHud1$h_NSx zJnAFhy1i9IEC?m~m^Hc)>zX&eQ@ss7DqdR&)P&$N#FGq9EsuE%4i~(OLNoC+Pf&#U9tUFb+BU9wcw30MjkwwTmy80MTt2r zRc}LSZsV%}_cIKFF80YEQZv$Zgk6n4{HPK}{kszw@#^Gil;J^It5)l<`;;dX%OF?ynZucR1=`8?D`}^) zXQ(L4x;o)phGpcNDur*fJ$ElI(_l3%7XJn$%8<ojtMzAX^ zw9lPw3pLO%5ttEdCpG)Y?HreIaxEg z-s8Vd214?Lw)#pUM*RNYl9YyD4$MS0t60{UguY$90?v%)QWBBHVxFm-Fv0N%u`M!f zzNR4v56@@xeOmCRWr@!++ff^>w|JSm;UVu}JXenO-fcOsQIVfa-&qTZe(3kV zs~M!Wxja^n3_g&c5-Br#hUpb*XkE&Gve*meDy-#~Xt)kT&{%Yf)dJ1nmL?u!K(5Df z=%0LD>X27K1<`D5rm!{z=~p0~Y=GP@7c+0J9l=f3e!z{L7!$*<8xmn6b_(v$WsoIQ zLW6iEW2XlyoiRAp4C^7*p+dOvN38=1olHZnNoUI9gv}4POtrv4m!`gv=NXRXzI_F3FVn+>F>G!ED(Ul z^HjcW1*w7up@7Y&VMF3-I`&&YjCTU{ty7QyOTz4LQawCiLr;0pe)fCp5)mo|n(EuD zZQ&#L=c!QSekB{*HLVvHWBDmGB7lf$eEZJ#85X#|x`zr2)MYr9_SJP=)|d2ztp{kf z!rPoX8tSg(2l+@N8`QYoIPjobWAv-`wsxo}2rAC{DvLEDMA;u5QLP=lg?n##`0h$k zI$UCA_T%ZAjvbR5X69}5Otsy+(gL0l!FLjAuZ4oaXs|c4wqVdY4Qo_0b!cd+ZTLg2=(uha%NYpodxToxMRdS+CSq87AGA@LJZN)Vp7ziQNs4+YuL- zf&2T6R~Fw!fLt&2`ag607}oRQatg?y+xMNN_D>NjU1e#V{C)lDeV25df8=g8)E{ts z#Gs@GQiN%yxfSa7_}O~VsmT8&?pPf`s$cUp>V~^Tl$-aPgawP2%xba6;{q_^u5v9# zQD=*qAoI7MvI06VIZ-ww9Mdmfi+ZQecLzL1aPu??V?%KiN|ZBOi1kb216DqFq%6%u zq-6JKXzi2gW?y*-wK_WxOXxEUTD!>)#eJcdZwS5m031!-UwQGjNj^n97#y(S?5sfR zA1&a9t6Uf28f>k1o|So#j-aNAuQ6njFQ?VY%U%w|j5YMSY<(!0hoAN;QG+gcz8^Gv z`t%~~=>tXmAt^s3BkRs<)qYiNB{9|6RYlGDimh{hTrpQXF1C;KMBlX*hBDF@vI`Ot zl1hpS0STGwgD~ZF=fC`dof@M)z&?C8j`KNZ7+goC#9U^DMe>@WFiGIF!FwRcCS44eK36a7XeQwKI<@-p@FFr&;+&(~ z4RGL{kMN(KcqP58qrZ>_hlGesp*lmT)l24tNrRw=UuMe#LibDu4E<=hU`DeZ$_@A* z{Mm#w+wRH2+n!v{s&nUc#+r2zUBASHSnI^CqPx*3F!q(+=gBpVrC+4maMzghzb5@o zPOUWoG9Zv}W!T0>Kv0p}@HX>UpDWLKYQjX^lCCWKe zx|SSu^Q@NM*|Ixz#oSh{%2t!o^3A#?$%W?6ls6^y3*|X?vPpf13kIv>)ByPA%jo;W z_Sb^l6mk!qaQUcxdAu1Bq4MI(_#KZv=V=IVAcWiIGY>(#!L{OHiy)QSH|b51@N;u> z?XEs|3a3vucbB`YoSX%x-g?g~H_s<*EHBx*?S#EQrkNGTS0LTT0B+0jSXJcGW9J0# zn-|m@pKG6hrzsl5mDWJQ0I9J*eXQ@t~J%fc-{eUWMsuFxE!%TS0|c^TrZdN1JEjLYrw+YH&%MyXWQEgTpHS0ee~dGT3NnH0bl^*YSBer+V}V` z$W}(xW{gWPf6{bPVXHUF0b%u>X=aC|iCXzef?yyO@mFF_GE%{6Bh;U~6ZUxcx6yBB zxY?NCdVvca>yqmBznN#i8?G>t3T~0~fiJP!02j6L`a@5DiAI6_pGQ!cQk>royPo6jFnqK9Qxkz;s$|4Yzp2?Hm70Mysr3p1jrfh3*3 zWy17^{yg!fcC!CvN|;mre{%CH*~ctO;I)BVp3S+M$Z;a(*#AIHG{21g8wC@e-*UH(#4248u`0)=FeQNzS z|7Za}fh;stvJ@>tdcXEyq-G|eB^9#@0;4iZ*1Ycho=zkKUA<&X3?Rw+UA|{2B#KP+ zMKFDx>5r;)aT$V0jh2|aPApoh@69FnUDe&qXeqj$TlRU2NN)9p^*VthOOcWaSpnI0+G!y-%ZwI7aZ@dAb_pa#7cx|7NM_5uxE< zD35|^!Ic0aV%pGrRDkP?kz}tEiHGNR4}O8aBQAFY_RhcDlhaFKBQS6jA9dV>sM;O8s7cSaW^PQ|EyC(N(uA6g~v<%MH@HFB>x@vJ-WM4ru=*oO2<##BzazB-6D zF;>f%?};pv1;n2}IHS#Yc=p98Kkr9y;0eYm89O`Z6K0BRroHeIDeq?UFE_vXn|8d% z@IJX!cX?YvRCF^#W;Iw4BXDrvx6>e`(}r7GRlNky1##cpECU=YpJ>HSEl;gV^=$J>0xv?POaQT*>Ps@LSYz!O*$Cx%{O8*6-fQP+~`Pf`tG6t zHC1Ay?##m%0v{nE_SqP#w$)(j0F{%-!Ln}5qV~GO$Q$)*NShe8&)>__b@2GbV#92% zQLHk)Mb=0xuPDaclO!FM#74y>DkgRjGpd99+maTA#izDkSBzL_^|mf)%j&0+uzk_X zV{-%SxWBrOyj?lH!(@Tf)zsSL9wS3w-t#26{y?{6ud=E(@9Foqry94)9HB<4Wo=4t9^%Lq5rKL71hBTN%f(zxh-JD&f1WA%HIIq z=}-Bt+j-R}8=Xf#R9r1jcq9DJ&r}Dz_tZXL`dI-3sB`T2Q^{C+_vAkd+hXimMLnpJ z@_YJOg!Jrviu#w_pXva}(i@He7V&X#53r(_F#iPz6i5jaln$`}wYrfRW!0D^69_VhU|Hm7O|Co z;buebOW>=Hic%uZ_jR+_-kc*C;491{-3KtC=0z&Xt37xNc=!$`ahp(s|bMhlq<_e*itb9jIa*_dl=c}N0%hI zLZY0AQXL3uJpkmD_~@5qlXqjsJ^B=(=VkYfH^40@rA4b zU_Tcii`wdQ!$`lbQr+Yh~if1}xQQ2-#+p%e<|C*!=*=Umh3HD5-I%!}nAdlwNrF(*f z^HiE#&D;nU<~WB*yvxkz`p#X=USm%G2%{VXnOKl^)Df1&pbyN}PWCJ^mV){gSkR2t zwq594XU;$VTbAipwncSQ3oDD?EA80xK*8u`lllET(^)^jqCNIB?bKmP3rP6+(!*u~d!Bs=a6C;CXKRaV*C(lR}o3 z1Zyn)U_#^=j+F6QrT4b88#iZ#N(@;32e`zkj6&+_J@Ty(99qdY=g*H=e|{${T6HzG zBtBiy6Db{E{>tUS1l{sg;BoiBw3cyiHeYX=X7s0vjci7)-IOBMypx>R#aTr(!;clTFeOBD9R|F-Gn6I3dnH&=XO*#Uww+PZp?|HM?3(C0JkW&c!TA%IJ=5h?nAK{)z+L~-O1 zlCd=4nXF$n+(s}H16z#L{Rb#OP~m(02AU!LUT~gjJE+K)B^j2h7?mspwpJ5$+T_qz`Wdf* z1dXSZj~=%Xz1)|YiYnfoSBC&4$e?()B97_%R9EtViED>Z^*1G!oGG4LA$j*GW*KA^ z^B4xbi$m(~<#0eFgn!S~)8HQ2=&-$rkJRnDG3c1}$TO;_^V*p6lF;zim?bVSOJrRF zE>jE=x#6F2(%^+&A2GAs3)=7~Nwt0w`8gr7`Ar!<4fj5|z3H&oVa zOvPldYEJm4)>ovJGn6u+3z)y$F%xgb6Xcb@Q;cf(eASIZVp|TqTejPCHkRRi#SoH3 z-ni{`|7lI5lv2-<0Ce|B0b){! zjwn$;)gdv&2alkgdn5{+suj0(OBQWd&ySHbyO&%YF4`FCmhG|UwR}O8b0Jv0|JE3Z zX3LnkhRmDuSCHiSV+!>7_S+@dT_x`$B~S_(QIBjvkY_!fT~oyId+}e@*rdewp8o_B4jW?$lPdI6T30J=4E1MI&0^{TZW|`-Vp4WAL&^* zm+xoM%;$5N$YOVY$q;KXZs)Zm_;stgDj3=K9bWxhd+S}VUsHP}mMIxjXkh!`{w3%! z`?%`()5$;8L|-zoaV9R`D*bc?6kb; z@=NQpfxs%c2<_Tf$MHO|%;&N{dHD2`kulMA^xg0nybBe`@ zvBRczrbfs&6EseXEw%IhZr-Yy9BYX;+5VeENE6`ZdiI!6jtBDY@b?Yq_;3$nr#BR@ z3~HGH^=e1;g}L@+W3r$!{;xKXsL>muoS`YRkWDM%g_>O#GjKwhG9T)FgwaiUL+rdM z7`5`FAJQyu?T!&!Sc~D~p`jgKFsO97wJ+v)_Y2B<>+L*^h{HnIZCEnX(z>kPdj-3w zZ*dIaBB`tEwNHP#TB{J^3!SAkETG*XM~~iy8MPRHmC_z>j`m9e&d&ao|GWJX&s=Ps zGS!OI74TK}+b&}Cwio62s?z&_GhUJ&S1LNv{pnH_?fL%f<{NLTvqq|veOGYeC^ZA= z+Jun;Ntdx6N`42b@hRgb7Xis+zh&Q=Mx}Z}Y~LG$Ok{Bd>N)P|5;)1z-O9O@T0N>ENpPYZfwsgw>cH-T%4!sP@$)7=#u>^+Vk5!VVwu~0YH;O2dm$u5 zgw<+J*&5nS)W(IP>`3C$S-~;OsV!Y~6vM@H-L>`PW_ofUl)=!IR9p#cUA~Vc}XxZOOkES9k~2JI~w9hLjIiNKmk?+`zRYc91lzTs}GW1z)hJ{G4!G5PB87l~@|IcfFb@8-Hdr3}WlM zW_uSnWnBi%TBIsxRHgaWCn0jGiM!``ek?NLn_5E`3nW{4bZU#)s8ey3OMTh8I}wqN z$R2A@EaQ4Mq7oimuoT^A;`%eUXxku#;po{*3J+C0JiIgXXRvU)3`KlqrS)JtjIA;8keaU z->8+ML-%d0qe_QSqPi9vD{J_yNzaz4OF|T3Ki)005(-R!WZJ`%r{#Mx_Q8c|;jnsn zIl~@?tdR5&oGlx#3lDfG?^|nBkPjuzdZkCpfkg^tv%LoiHd!&|l1Unuc``hjS@O+F z5KN~FV$8A`-;u)dxr?~*H&+v+-L2+48tCiPqUeg6T4=CurQFNrliZ!nelH)(Jj;R) zSsMxOZKI&g-FG{yq|jE1vS?EqAfl49J@qMZloeMnH-lk83DLJUJ|B-NzU9f`lR`_+ zs7zZZ2IzwX%?AWk@5- zyJE0p35N`VUyp<+hnnWbyH7n3q;?NNld032gPcyegg!`{PGELxjSjEN>SM4B(bzxr z&`y=ij-O%tfSR})DBrj#E2mIVxq`N&t0!=_Cz-vOeG*}`|gdt8Mg-E6J+B=#OuH*@ZzIG zYikseQc~x_FQYFU9qX*#+iKphXm=~v&WB4|JU_HmUu^=@Rf}ORscmxU={5+NBP9{z zqGPT(vT+7Ya3@9~y*rBtef`%h1s)B?@aMc1qSW^70u*e#&D*nUQth`rqv;J!*HF5I z2y07w2tsj2RazY#$N_ADn(oc6S7>G1;RWXMck)>sEV5E;f;ix1C(VxK9G1#dK8~R3 zckt5lsLif7LMQm!UV*gdSsCQr`Y#BX#4$!jc?eQGp~?a8#I018?jPx&d1!iVm;Ikx zF%i4-Ss8XoWQYm1<1wBJ7$*)1dMzhtJ@c%a_>dvM-D!jZL-2A z41D-)Pn_6Mf%;&d=ly0Bdb3c8knJbA!AtqIyDOBH!X*9U`{u26(>~J~yQ^62(ztQn zA%Wvtc1%pNz@8FkD_tp_K zfzz9vC{tEOh)gv4Xfme8?w?VMQ%NoyR>hga&`(Y&iW~ECw#67<^-|~4rs~Oww!z@3 zIld-{(dcSP9iOc~z=Md*-xB{+l`2#h z6;ct+`y4cA9h4Jcpknz#(9`l$K%Rtq^Kg!RCEqEjoJBwT`+$cy+Y-LNeWXp%u?y)kkD-FpfHj(GEKmD0Y)n z?lMblZ8lGg>PgtQ15Bu}GtxyUd|yu59!}3J+paBdZC@5&@LqmpJuA-x+@Rf5{mn-R z8k&80j_Pu&ord{Quiw9SnTl9h6{hlOm(>Uf0t{xYVK==R~NwSD$o~H}eRECwLb0Zq?VgzUP<+^u|GgKS6S0pq`TJgKGc#8GX zbA0(9m60Fo5O!WQmnLJ~>T~$LB@|%kcNAP<7M9(n`I?^J(81M5E6i0~a}JHk(yWO& zYdbs$QF`CZpbq5MMwK!Ss0y2yHpdQoM_zsjQ7MA2tv=RDMP82!B`#1NsU{CKKi}z>ed|~{V;>Y``xm~X-ZG!KL zS5@ZScUI9iNU}(dm=T9ryQ+gTv5B2QP0mXo@rkG-8)*X7iUwAn>o<8L#NK6vmaI9- z8aJ@x)xY+MicdA~9{gTTKsGk0FT;KptK^vB-**4(yWd=(pB?A;*|<8#U)AF3Mtav#0a-gE+ z)2|we>b>dr%!!1Ikc!V0=IGN=rTI1tb+*7(C!YO8`N)sq^hzTF^LLUZ{&lm%pPk;m z3Hm?pIPV{QqXu4?iND!W^BA#}V=##AtKcM(!5*BOTNPI0u29KL*m3HDM!aU1xld5q zyzYOfktN!@k_}t7B&W5x5>;3VLWu}{?=Gb!HvdG64rX-B4XA1Ky=~p!G$P>Efc2G@ zhqrKYyg_jDZPA0DXjYAAjvkYy6 zx@2YHjD7Vlp~|8F@DPpdvHPjm3#{*+^*j0ARObaBeDsFTGS=(Wz_@PSomTwwpw{W3 z?P5)KH!s#|6!e;+*!kAjs+7#^gGatICk*%8)=d!sV%kkR?)*sUZ`ji6b6;=uy?*}n zSer5nrthSB(|TerjyWH)VpSB*eNz}BjABwva@kQlPL%PEAu(fM(&JTr2HWp< z;P}J&F~ecY4BDgRw%k{5knxz;fJ&dY0)9Iu_>o#qiS6ea?;?9YIx}+9!iUhZVmONz zo0-Hi!Em8oje&d>-Yk}&j190&e8|pH!vXsp7F&tau?A`U63v0Ud=*?NWP0HZ6$uC* zSC+8mvi|+0KdIUL@ZMxhLp$D60bb21=8;i9A(_giWf|V=Nb{oSQHsEyjlxXEzpi=tfE?}?M){cP>u z<-c+LKIyPzHH8*s)yi!lz4h!u(C_PR(?Ame4oP3#J>?Uu3g2p^M1yh+?4+3sVzY}Z zFb}S}EJ!q?X!pTSEg(BaQ+8f_mH+0_nEPe1!Kknh(2Ha8=q3UV5l9wojeCdY{_fv@PqiqihXIkK+ek{#iUsyz zL2~C2)5G~c%av{{Z}mKriQt3=RhiM|pXvI&Dt*LV(OVL9#&uxj58xU3^!4NPj*eFE zlk@o7LaMmeLqUQSM=A0m&Ms+hfBTgJ`P>8BHQJC8-EdXtisk+CmXBCl;+61U+x zQD!Z5>{wYKI!vPrrVt%T58c@p&lGGq2xLk<^Y(elBXc2o4%X%gW}wJHrpA}vG^>f% z(ghB==mm_p-t_XZhTwU}z_(+1+V(iK>-y|-OQ9OZpebzRo(VTt5qQ_t`MVc-+S|!? zb7c5J(b3l>+ZEp2WA3LS@4)}kl*6e|w{%|h5`b78Ubpl>ub|F!=0l&A@S3cwVEoy1 zuT?A3<*4-5Ej=WrGDfD6ApTj@41b-A?4DQtJ4++MV?T*Y$-*(-phymthN%-tq4w}B z<}y&@MCNc|>YXW1XsYya&i+p3kMyp{Zj8Bs4=o(+0RoMIW?>-@eW!UrGf-xd$YKVF zvz+jmWbZSZT!=>k7MdK(U3-$Q&b;i`pNI)##bpif%NfC^_w10+v958i~Idppqx2l-^Kig^ccW@GDG_=Fyl$T@6C(5`?|uHquzcV?R7A5 zAV9v8GmBLf>j{@lzYrnbm;n~$;-zNfK&kmNF-}C2v$;y&6Mm}Y0L|a%25F?--}A$J zg9RFX6c0G{liO6F??~Uvw*;7H!GJb!tpDQ@KLg^lJqoGell<>7xW?j4-DyLI+ul2 zWFloCP^KojwIu~OY6k{d;hyEruX=o^4Y9UrQ3}7?BvG)PEwBUI#SOV z^GWn=Wp+PI#i&r9?qap&$pcZqZq^IyYj#f$J2!)xgfOYlUQt;-sN9&DJ@>oU?Po>Dd3nKH&M5`%aO!^&Ibk*=XiIV?q{KUsZ=| zSB9i|vuK}oK-Juq@a76vT?&KaH!nUM6!55@1v2O$q^I|sMzSb$X?)taFO^*K+sq&K z#91Ex@hi(M`}5n?$8DaN3x)#q;@g#*w$K>i`?oLMFQ+2-CVvGNn`n;Yc?ARP(tq@% z6bBDa!?n~?_RUM>gS{r$R*n3*rkFv>38J&x`dEvkhd%3FXniWX0!?ezMXx?d)NYvCb*n;(?<{&s`g$L@wc{G^woqfzqNg#%XIw1 zF}4#vRvyt6JiQhkeBT2r<7;O&2lSKj5>U>9Z)vnMDsCEHd9sjA#5C8dA~c>-;ih%4vB2t$g?n0;{S-aCp|L6S)R)Gv8$!>gW?TEtl@}x7WFjzc2EKlp zy8fE7kACL(x@FUsKD<s0YdIkiED#JYQ+t%Sr-o zh4&i}cYFES1vLiEos_W5Bp4RGrl|5ldAZ0%_mdUDviu@+xE8p*wK92x4ZbYogx3hR za($I~_xYHdO>fwOev5pOGgB@VD!_EVAG&b2;(g`C6nS$p3Y8PIo7(XDjs#xi=Zdsw zhxt+J@i@4chB+kM5}~Wi)ZuFG(;DRbjc`IVK{1gfgjM}F(b5dhn)Fl|?rVA@9nYZ5 zSR7$BtOhkHUq1yy#H5Q_!YTz4+)xtsqN!#COEc+UAiJwWMDR$kc=ri_)TNzeE zS@&vXLe)vjQ@b6CMDX7mm#SXSadp3i162qZgESUnHfppS=r@CFmAe&lBXk+SBzvsf zF6fm0uO+B?SB@q_bMz&szt{GAEzi|Z5~g?XuqYae@wZMd@jshWOlST`(gSq-oi+rj zsd%}#kc*vBUO;$UKhmZ-&##P7yu45E0x^k28U*jXhY-@7_p7(qF~KcGC;r+TVTOip zVa}1<4(#p&nJ+zvEB1<}f|yqe-*Briqe%9h4*bT_K^|;~ACV8)kW?7fO}fdD$*n~C z#S)4HemP!!0@jw#^)IL|Pkt|}MAcv)VY0BMtwhj?+laj3?o1_3_Ss@&+FaRD+M9wkR^VrC+}Le!BVUUkaY%Dq z?_$_%?5~^Gu#^*p=fTz6fAii7S@Ij=_WpF{nGB!&JD?14vkdt*-aQpG*}5;CF-zVeY?Fm%v~(WD8K8Wx~8+L z-_h!g)wcGExu`d~<(!wfRw$qC>ulU$er4Sq=Y`?H937244+9Cefk>MGM-T<#Axs;2 zo+%5|aV$WLEwI=74WGx!EDtqrvSKbRXMkPCu~qv`KdU|gfy7WDMRj#R%x_S7F{Cn; z0nnW#ZGX8FVl)#V5!0*tvrqDPpj5WL91Q1O!Q^j`w?$7s3@g0!;pZ00#@)D4I@oiw zQ4c?}Eo!!l`)&Zc;`_XqoIZ3}0(USMp7Z1juA=&aR%HuAzc!~goWK2?*R&?55}E0so%-TlMof>bLr|m(_=1BHyR%h-_%@3 zR9t6i5e&763F)~e6xLf5)vxY`I=d|Jg)SYOf}S@w3DVCk`c2&`J`WMjPR^0M1{iet zTkS05=`J*3ef@4zNw0$CI;+$rmM}b}49IL>uEJEr?IXlysmKzOzxlfMWQKV~8tFvl zjaGf5y{9oT3#vO9bh|dE)v|ITTCESU{ED;4-lI{HOdjeP1aflBT(he$SLBg`zqhxX zLyuXajk9%8yDt)K58?7?jobZMgVT-gTzE4ZY9PoF^)6K=KbIt`KFTQ^V_tDJam>Br zgVE`}UQ27H-(x!TRjK=%i$7I0tfYc&+bG+ri-% zn_!noI*G6XQ&1bh+$ZC%9xlz_u0Fwq_aHq__i4N@D6f~7lf7-J_C%xyvw*bH zJgMi9tIi&Hl|ekvgbY+h=+<(v-8}zV=7*mQzZiH?uVMMwYL(=srDx{~)WSlU?YzU1 zszAV4Os-KF5VmV9NY~Lr*tgwF&}V9jy|AEhoD#O10-?z}wfvOmHntW!GEt1D-&>5n zoD-79^9=jq+ZyfeiIpo*De7%9^8QhD4v)`PPB2yE5TKE@$F=FZZPHW_@V|8@e{z*Ejl<$_jE zsMYHhZtR}kMtNS2T-=swqU0Og>a<(?Y!z0qfS-w5s8O1sY%?N}FY?a1T{0x<#m=Wk z+M(~@Nk~5RBeVHfKera& z(GnX84QxJ&R6D@&XwC8qLPz(O+QT07SzT;}=eEDJvX*6W@iM}wU*g~;A?#%l5&2eB z)@h}&?)91>&8nexI^2W$V_Vz4xV7WQx@pPBS#cN6nro{PIm7e$4P3d}zDY|3VZ8co zimb0lewRi&iGzM31rdcg)7CO!!xx|VJuE3&-36mx_WbU%PR(8pvl7cQVI-E3p+q}0 zY=)P0*1KdWJz0BM-n*n*kGQ_4D=teyn<*!W*T28WtG@*6uzEoL{^>aW!UV+4jl$jLlK}u7{0Ob3Zr^B-FfzZEtQ0 zvZ}pi;VL$B?o^*2TspFq z4gLZ@yK^Et#`En@1(`1*o6a5QL}Q5{gkL=}W34>4^9dDMccxnj?Mo51U2BA{ClQb3 zwfKn!!@3}16*$=zhH+h^7oJ=2PR_|a1BAWLpcQDx!_MIwN3$QN! zsRNI!ZJD-HLA=`4`f2YQfjPHCXy}Sjb)e^%}>*t-KT{4}@PEPW@D3 z&bPEWQQ~_U2@@ zkRtA=FK9G0R)gdqt-xeJMwZvt&IutcS6gFNuI@LgfnFOpYbv5j;qh$~afQrhy;xU! z2M__zLd2@YMy3y&OPjrRlZ8k+F?@+x1{5*Wx)pL=lad+`FKOFT_=2(_mREq0|+aP5f2PE6%Dr1ANeCvA8p&dNIzNVAnFY> zPmJ?hI>xO?NOfJ`ObVP93}%|>s3TEhqj|r$l-MwS1r0|3?ECQgThL-6VNr~bwD_}d z@SVgkor;lL!4GJtMfSvS;*5k+r)f0>K1#su-sb5KlzpF>qh)m7ZM@Bt7xTpVtt~Ij zxa#`yl49#I$9eX{>)*L&C}El>Alzv&!5uDqN)6OIi1nNOBxl7SAFN!< zc{(Ep-6F7_u1?25(lT9)QGzR#TA9ca1US!9=MiiTNJoFGj@=b-gp+*Q?Pk%CkS{xA>Urjd`*x+jSK8`YU3z{B%)e!)i{n zlYac4TnD@64@QlxLeN{*BBUR|U?vP9uxldH2sigXJXEGdlp+{(93*kMRWwU4BWOYp zVkR$LsxMizqUY15og0*qp!p!lZ+Z9iiC~z!h)QSIRH`(nb)rxKPjM~qkL{Y0?Y3bd zPSYbCBD}FqQqx>gsk)v%G+{*vRp5u(V~zDuk&D$-7A2URa$jqi^uiDK{l_xdlw~yz z6=`}u_LH3X0;-Fs`tZ~k6?z=y{rwfx%|S5B()LcIM~mM9%^{e$u9=ZRH)(cG?elW4 z$DiwK{uNo9PDf)d)*qL9QTXHDx}G~py@@-Hw*&#Jy&+2n9SMfnR$?Sr91@#5I+ z@4chM)#ig%03i7hPoGeWeX@;IYL=r_PSGa_EQ>3B{MpJ=cnYazi}F{DVIlN_@HfK$ zW*}4F+lH$_)c@vr_qcY0>}mh4Mv+;`v#1Hh)ccz<+~Vv!pvKDc|M3?m%u)H({{aw+ zM}%qpSh@cFl5|2AyjTtXq4xuj9Z%S2jH-XgB5wR&M)5dW>69@DNV*05eO_+fxR@|CfRt(w0n8ts1QLH+`FmliQNS3G%<};fT=luZr$}v&&6$PhMzu9{&T*09oNm zY&3%Gb?m@nX>yO4qJg6D&;K$ZZuVtRKzt9B@FfzOT}?;oCi?Fypr9|C;F}-+yJ?Si z@%7)2MN*(xnKr>ogK%=)!4&5xf*xWw;+XRYcWG9EZ zf0#;^QY^w^p;!SxFY!Ou6_QVVT_uFFFYoXFvjD@xFAn18OMM(QT{i;c@;#H)7CsDr z9hoGX?$MqDq*#8Dn$~NHIn>;9!iq4*gn+1kinK4 zpHY2EwaP)&{JIN201i$SQ<9>GDax7ssvIEzg50&alFQaNP;&kcmUU-_tZ)C`G`(}t zZBpME^=OONbr_2UB3*#LyoCh_1Z*$>{T~jFGm>c4f0sc{#koE74=D77 z5nX&VqNw$D{}Pra2Sf8t2rwhkp9?wbpU2Fz?jN_&^rWXAQht1o=95c=p1|5T8H9aHhEAc*!W<|fh+#hQe2>qnbbIW zO-HUdNoU54U#C{dVnv6`<0mmmAC_&GG#3E`C(^q%z_BzMmu2hyUlPc)wrj^3-{eEy z{XOgAYUt0!^aF&?ERz5W7{%XLqGqK355wbL1j^ngu7>__E1_WdtcF~xD6-^=J=4fJ+cPFjFR%IjR@{|p!yHUV{bqyRbd7p!7=>I6`T zrhgq*W@WP4|1noQ#~%Ma4&)3EF#rlsC`3*7{@c78{9Jm?ysp1+ovPWC)RQ;>Mg8{< zA&Epyt}cdwx+E6izaHUNR_pSZ z`^oAbZvcI6xl}C0hyO38EHNdonYY344S?k=7TSJ1FCqTh7@i8=UJV$)4grW{FuMQ0 zj!BX&fm=VpGOc#y3M~Oep-!LiZ|YP{6m}YQ*lESc830si3DIC3V@j6sAvB$131tp z(OicS-hZbK{Yss@H^zUFmM_f$r2n5MWJ0GQ0A_@Lc()P7{6Z0}qW=RRf&VOwZ8EMJ zz)AiRi>)xn4{pEAOsLYjrTD?08l$vZfs$>%%>LL`lkd8MSl{)yb4Ha{nHC4<8Y3+> z3Z73R5rY3QCIVS>T~{K&*GW-f^myyP6Qc6Whg3$CrRe;1+|?7D87fbETJqRPx85$O zgW=dn)AW{L_|(T0z$7?KYc!&-(O?sS=UclsXEoF#`Y2XWW zs^)CXR{5Qfo_s~Z6ampbBw)Jb0A6_ca%*%pTLv9hj{AqWju_kJUTvE0zcZL`o@zl6 ze~n0+4_-YtkAmM;q~}vsS;<=d%{CbG^IyZ;HIu!%!Nrt)T!S$6*AIEA! z*r%nz`nw_}KO!cc8#@Rz15`dr_#5M-Rw9VaMu`%Q+~p@^Go!8y7+0pt0aIGRthI5q zrhKk~z(K=|^;aXWKDu@QaGkO`hCpR-BFOm>9h*!)xf`2W16j8U@@5te{0^4NnO+mw zYJLF2b(I=Yqf`X%*QOgnkc$LYV#W^n_z#b!43o~o!vjo>QDbtm{2%jr0Oaj5sZC&Y z^%h2g_9Ih#MdI+^pVy#%?_D}ItmP+SP)gA~4;bE5x4OsGhYWn17i-YAI*kd;k9gBV zKZtLiG7&#pX;O5FteFEaIz~eSg37^3eC1JvT>B*?vH%svq*C^6_jjf4KO?$wnG$N~ z{)tv1k$hvT8hGrWsz5G0|F8^KC6jA9@XMiGnsu>?(JLR6I}^yTfD%{03=yqm41D#U z%QHd((wqy{+L-$rtjyXvKwDG)SC34@DqeLjqa{LPAPxU-0Q8W#l6gJ)|Aj5#NReQM zEk3}b0C#_1`LVw`|GE(YcxC@w%1WPQr;l;YnW~vTivm6~g5!T)7!N>t(+|vT0e2;& zJCi~HWMcm3x^9f3&pKi4nPUqaU` z#=`&1K`x&18k5h{r>Hys!)wdOcqiu`|8b~w?Q92GUjMCwgK-tVe2A3pVy*qxes~zv z{r1tzzyC`nUvl(@M{oB5e=5M&6^n31Fa3w}1_H)WRvNzj2UC&+M8MsPDE=?LCH+Nm zPi9OC^j4l{Ky^)Y{y!yNfR_Hs64G1LOOB*gAO4#g0Q7&~pb_TZcXcx6T1}r(8$CP- z&z`HE4`FNIr~dby;3ld${L1sr8UQdY1S2rc9Q;osU z1B4)O%Wh%MpMJ_$QjpBh&y$A0>8|d+45X}m%1dF%1{7#1o$RGT9N5gI>C<}tL&w=B zW9g^CC4NN=hAAf1UcYHtlRyssN`UhYLkLw52&hoDHU7qoOHmmyF7#o$k(D_t*dK=u zJ{GHYdDexAl{%~m*|;K5`jg*h5F+Jb*0BeR3t$QRy$cj(N%zDlbS4eAM7^RF${UQ9 zEBsdKjHWahJi&?lEBoD7Od4)WN$dUxy6H58)p?rHy$g^GzvQqbT3VxE6&G=qY49Sn zJ9d=mq&h@ZIPxeWC>g#3dn1h1?aIyZAk;!xBa8c!=I>9Vsg%biU`bE24m0==r^kr zZ7`wZ7Q5U-yQ1UD?l~OWp-Xpeofb`Gh9wMoI|*J$4yZwJTk3p9sD-8T6iOC@XGTNA zMl|KH6KK`&hVs{nN$osFQeD~T(Z}j99j^2-Lq>z&=Y;Ul?lD|n-31U+MdEx%=ik;j zI=y>?f5x9ZEibb@b#@uscUVJ3p@ZB=E3}Of?L=x=*}#U4Mt=C+RBGkIv@FUoCm34Y zd6HDebm(8IEGC#NdLB!wHZMW`uIpQk{EX?eIS8{>dND=3fP3mlN@hhzZDIZ}r`@cw z^B~R%AxoaLIch4QuD(JV!G5L?B&~m9y1uSHL_pAapklkESLB964CHa2pu?|;;~e}T z{<3xNe^ae#^LF4)qkR#rRXuK$mNu}wRC}=BN@4cn9Cfm{AgwvD+omUNild(jBjVQ_e%UYCLr$4rpWI7SdZV(uwAB{J$2?$4A zbStCqW>n~C7s%tMC7#|coYNoY$DHE0yv=V*DAU#Y)+KszrwrW0-T+SXz8GE)bv*Oe7J>f z)E!2};_uSZ98D&W{=#U?%oog@V`wz6K2k}!E0c5Sa^ z@8J}I?lF3YoejCK-D^8L>bRrFrS%I!U6xG!TsI1nd`NOc)S_20M=NJuCZ z`&5|yUqGSp!h;K$Yy^;MA=Fj!6+t2JPM1C?Z^AYEy;d2z4+hKqlO>S7(vr+5yIVwo z9kw>SqX-&d5iNF}i=TLTd+UvV)3`oPXs(~t=R-Aru@T&;)gnwBqVp`A$Usi}+969M zisNUM7fG3%s$ zi!WQxoGA;2O5u(2v$`R!k|K8(*G6g8az$dT|3tvZ!W+JSxJ73;LTeM}Unn0MS=XI| zX%3o7t5P^Tes#aq{;=d)Q{;B)!0y)(rOg28qdkL6y}K7u$3EA3|9&17{wW3jlyZ0K zb5rBDT*cVaiQYkXwK{)seV@EoX;Q_WN70~G;RZsS$LFI`m;t`IwYVG9Jwu%{&h+yK zw#z?movTH1`H$=K!?;{&w&gD8&_t)5)BA7e+Os!7XqDTd89sZ?I`7G6-Cr{a5%Jiq^sL_N_#v;}jkx_o8$dou{DL|PJe)!13DU0y z#~W#EY}!zgQ-$bX8}~ycT0P;&-Y?i>$csVkh7}C87s|A2!)j`3eeFIzFEXs#lf2x| zz<*)K+;@gQ-DsS2|F9ijR}~;p2D{8;<3<-!5j1&a3MZnkB6|!ylBu$m>_6%IPFx>| zMp8(`Afp~_c?C=duTT_+Hgq_;cdM=F1`O>fS@MY;SI3_*P~2_k{M?D^i}h+w6v=34 zM>mL|6a@6&og%z3P($$o9ak`21UA){yB4jQvu%JN{TZ8CdU6NoRrP)BbUSrP*o zZp}RL>;V}8q2=@%nmjW4Reiy}ewy;NG^z<=yxXE=lwp+t1;8Z-!v91L$557aU6|o-7)P(P$(^Dm6^BW;{3n)dMGL19nT%0 zwzM_{UN<|gfp=KesyOpz)b&P!iz@iH+VokvgaroXmF#oxccB8TzSqPdXx_FS%Z|2V zU$NvR48j0WFRP|9r-ht8_;@*Bb}OTRe%^Ds4ZyDxc)bUsSI~QD ztAfQP%m{Q4rJ=adLz#2OYz6g+xXW8JrS@+k9Jn2go$qF<_)18pm8%rillyY)r;!`# z?1@CQ6=hGY`Ywk&9%-w6c!eBD53I7*wFiR>PhXh^=#y6my*v%P0ptz#K6%>v0`MLm zMpq*n*|EHt{loaqC4^M~KVhUYI#GaZ!ws(r^5Ej}>-N6)!kuj{RKAVm{e|piYrefj z2epwO{K;~2c$RJ54q7vUB21zFIng_o}^?;x7c=li~erT?u6~mo=7=V9Jjf{3XkI>&jK+x-w9!vjb`?`wLj>2pLnf`(cY5@aE^)a~n!pnjwm`xu7Fc zU)3`U`rS!BZhRv%lajI*sto!?9e@WP!n*JDv&ISiWmO4RSrsO8!q(^D(ZZrS|Lh7( z+}55Enxh2o15*4xL3I14bg|)wN}qxNjgkSxAO&e-sN=2r%(3Bmhigy3NAtI6Ms2%KY~$P?F6B)HaP-XSlm!y`DnnZ5*!!oheC@MpDqUJX76 z1#5Ew!ggeB%tod4alH?-w$Pl6bO%NI>#l>_$sAu@d;*9ff6SsJiGQK+Q1JUOmXo#o zEQ8K`h^i5TDG+Di6@@faM7^G6p=he-gUrdQx2SZ3iZt{++( z5Akw4*$7nN>agyccVgMlfWS2H3WpxuDlGY=(i4YHybBAlcda60^FpmZ@s1`dGgXdv zs(P+;y1mHXdQwC)Dpc(N8xxG|U9iWlNob7#8ipRISq1)X(y--vypgjHIqr8|@5}tE zF8e#&QH$M9)(8AKqJ0{?O)+!QQ-Qs;C}sUxm7+Jsib~5Gzq3kx7*sGZgy4`%z=W>v zQ5vZ`k08eeJP|TmDs(e0yhpw+M>M*)c+8k@r?Ra&aP&7;OYJO(Y5l@k*|+;m zc@cug=EErnvJsJGYV4bGJQb%-|3>#r#x0uZ{$Bh3^9%CA1q2l~rD-7146Q@uM-ETCv(2Pb)9j zIjzo#d-$@_l8uq=zyPc*Rn*`-Tit+zbJ9~{G?zl_X4Bjki;(!OWG37BJWphtyAe%J z0YS9TCF__B!}JUO^~47}4hL^+Kh}2s#>((Jgql^6PKCFMnh$Fm*rf8SL z<4v804@DuDK4f~2rK8!ubqyb{am-?rThs|f#MfA-vqfxAuHU$IH)_NpWKbxXqMtyk zi#io>?N>=0fb;e|fq(8z)M;?|7(kj5MgD&AOYP#IK(hw*w1br|%py}{O}Q`%s?c-Z zW74%paQCqokKLV-Bij4Ulb?ejj*1Jz5{T4S$~;#6)VK~DI)U9xi%aWq925fj5&a$3 z#o?-QBXF+eGex)`Ln1n#__6~c}XuAE;axm$o(l? zyV2{NF;ol^9W1w`lRrms?MZ0X9njFhA|jFxt@Gc}R$fuY52+7IZvF<<`3aj-=zsL3 ze^oz$*RXa{rqA_dcQqfcnN(sude;UGHdm9J(~JZn+U=Wc>;%_8{*3 zkRg??a6ZUCv4}oO6_HQhFK1S`y3_R&p+Uds*LPVe4Kh(fx~NJ=0?$ecyZM-LBc8)j z!Wk!=&0^jOzAU1oBzk4#u7&4t{#u*&a=>ht^lCb{sUS^rsez$B)NhqCLnej=w%%DH zvKJSvr028D+LM)|*DecJL%OR(U==v^JkvygKq)K!DAV#~iJrt=&2OBB!pEHa+c#~l z*3TWT*Jt^sF^%hSs}_x$S4EV)i_6%1bA8M(@8WqEjdgeflyxtqqU`mPqmNwK!@=HY z0vXfEmm}H(3W!*wnPxK*dMly#mD0knVJsy}3>Mh=^tbtAJj?|)lCC>X zV*U9DJ|@lDtAaskxephwepk)WB{|SRx<^^DTlwJ6lA2GB`v#xSD)<MU}ue{Pki5{8*s7KG}Bd6w8~-bcZOq*awx z_4bJ8meVgv8gFY?Wu(8o`eSgI8Pk6wVaLbjdWZQuD6Su12|B=GE)Fhd1u%&ly#Ym|PVa(JJk^ycPEbufo})f}&zI zHH4!M+}^K@YfctF$pWu&>!zZXwvF^uX{8YI&m8+dWK6;)eJRWd1nmVakXS~jpe9Ps zWGD+wr?&9w0qeMazvSN+KbCuHUC^23cQ5t}U*$1y-T7oGKd`kmMk{Rz@dh|i2+%i@ zFeV|zFm9WPbFu~vVj11$Cg5Lq7$z!i(q0sM)zXrku8V0-6D8?(9ZULs^fmL^*|lkl znf}$e^SQGX2c}y*jC227R&(7+*385G)rD20yPXH}B4$7dj#WXevwMqjI>K%MWd_ZO%immzkd}nHKbY`Vw(Fb6P}k; z(~AndwW#G&tL=v&Rf*1x6ZHmh!s_HOc3MBi!YOkRpqy=#5SN=#D=_`8uhe9M?jrT| z@uAa!d_e>h$svQ4e2?)Ap~iZFt5C`Qg^sQPB;gg|rnX9jmF+M+CNYgf*T%cgi&Nhp zjTH1Fo9OASYem%<7j)v)pGQPk{bwJ9>=&2AJV**mfvB&G5N+{*p_aBi^IOeb(W+X4 z;q|(W8jtCPrR}t^h$AL8J|t!Fs|0oSH}uMH$X10QVz;S|5+!)k={3vjTpB_Va+Y6l zcK9|0-DZiebr7aJeVSGza_{@|KO9xD`7)ezt}i?dva8q6jujo^FW$+fx)6{fV2n@B zms$*# zOQ^Ew5!YF9y($SfJX$aby`lz<^pcWbH;XOL~{j<&%J`URzbGPm*aI`QS9OI{FZKJ zKG~9@kNjmTNzVICgBF2JKrrG{S=gZ|Zg{E7Qj%zR@@8$T$2E%7mXF5thI`!Gfc*(8 zXolu{-Q7NU%p$Mm6$obD=(Rt)>`#33I<7irjXhpBA^@ z7o$Y)ebB^+M1@{8Qd|lUX|}i_%qC>q;~4zZI}5!(t(`~wdKT%ZwlV!CIqKdflc3Hz(zSoH2DoXwBjaWX0n%@IJ2_RCC{Y z*vePK#Z0bzSR^;9_k+dj^NGnY`(EXCwyx-vw@~@#E~*>aZO>}R=uCUZz)A4^vT3T? zTZ^I9!`@bG#7xyA@)KJ0$G03l6eU~rvAd?jt7edB8S;o9zGQ;SwmSLfPEH*73}%%L z2_3E)Fv}7W=&YRFfIJ&buB&hc5UGwMI=D-nLiRok-7$sPF|K!;O#iSZ$dDyDN)1vf zadzv7>$kjGYXlbPg{zUCzE~$LWnYy8M*5sU4Dj%fv+bdGQbPR4tIc4yS&n`J4!?|D z^c(17fwSOE>o+N`P;X(dq^zzE#OQONahLn<{U<~PrWu6t(T#IMZ%e{tgeP;;*|Wf2 zCa941Z*e|0;mG8#;_fC*5%JFP$vzb6qy!RUP3>lC-In}R{;p_OwzwU9ySs;Ekn;4b zt{`O0-(?%nTpMF6X}$ML+-y#-R|z7!V%Tj<88@Krq+5Nywb2r$aDL7vFx-fp;^Ri% z3jF1Gd8T7UcxMU2t{>M=A*UP0C_WQu1`S?h8a8I9@=#@6QZfl!-_q7yWjy*VO5tV) zIG$SEs^B*fVB9IYg$ruV*4K~cKM|-V{9|N{TeU{kplNP~B3^`eU2N$$lHhseUa>cJ zX1eYixB;~*9nn-_cF|Fko*e`cdHa$BAvU#_(%j|D<<$P#nVoDx^M{9wz6bTb$bD+e z$!7xX7Xr7tIWbWaI6%_$>+v(qsIl9Fd|I|Gm8<0wXeh(Pj7v1M$i|w)McuDs)CZHW z-O~~`ze^DttN8Zj1}a0w{+z!M$Mc)yj0<_D5&?BnZ)+Bv!7U~7&ty2SuUF$8|mh?#vB+udJY;HS7h zJ}H!MKYG~CEXFj{6Uu`VcGDawoVpvP5>d&`nj*vwt*Puyhnpt`NIy@0!olKHLf|O* z%R>e|`-)aBCa}La$QNed;ft$6N?zLvZsc(hwEusuh*~0vkSJ$3@+9ev@^iVTE zzo8V9N#0P?uOMf?Lq3@qYQ=_^+Dp>n=l8mvor@C<5!~)!iJ4 z>ukQ2p`q5;qoBJ;p0;qIKwfDvu*$PkAHn$Bc`sBr!=K0scR%CZK6FbS1F3ry>eGnmh}Gf3QorDbSq-70vegIH{PbQ%s`>!iLl z>FmrpzPiX;mddtAFAGE4Ysf*TrNZd&RLXaen8eTc`dMk+^FI;5hpE;l#M+r~OW^cA zr|!vHIK&A*cPD}aJ!iBu1w_y4n9-#YjUpMFBxM5EZoHcALUjxJ`q;tVGwA4q`)E@$ ze!~7wZZ-}#S$KF(WBLyzCYZOgWF^AflS4<2P=V3*AagF})%@m$3(XyaRj*Zv(6c-5 z2JgG`N?++a1MF3R|%P2|3Jqa%v%KQ2fXjM|tLKfVZY;Nb9=Z|^asKaN4dl-IBjy77e)IuT*mnua zM@cOqQsOpPe98DfI(69Jk-F5_+db0=%h$2CwERET-ZChTu8rRe!Ge2mcMt9mBoHLH zy99T42o@lCummSKgTvtNPH@-3U4l!t$@@Ne-v8F_hppPK;tO5V(>;Cq^y$;T>%Kz9 zt8+X=;nH+#1#TdhEM*>r+)87rN`z)x&S9Y349LA_~Ihh@?`W-6jAVIC-JA7HNL--H56mau&ULr z;{c&K+wP^&6mI8ZI&W2RwPmG=9n-5e{XWV3VB#FoT+v21NrkCteUmS62t|rdXpf$^LZ*oX7cY*C${EN9>{GjZkPoR>NcgzeQ4$7M9Ti> zMz14KCOOQ{BI2PPWXyl}cG^CKZuMz2qzbwsw9TD}vb2Pgu4+p*U#v_aFSj7bV9vmG zKHl^;IP)IWWhcycqX{>yqV748-l916{iJNJv2n0b9?5_aW{ zA*&VFU${4qY1mZoW_??GpH_% z-}`2}$bW_U;9Y1rG-WOLMASIv;5iK)&?UqTB(n+l$-{NRV@wrm#zXC{8d%wn=iOlX zG40^m%b{*8EzwWOXq=A6jcJQ#r@Sj85%qU?g<=1CaoR0~(2>tU9?qW`Oq8_c%vj^3 zIXLUEa?-14@d`TW_R8~9^ZEce=?`wC{T`+n?&;U%1AK|3xaI|C53wjir21VkAg(#0 zt*a7ugs}NuVAwQ4dK)hn)=@cNS>gy3qw7J8aeuWcEE1%Jz%P4i%Vd)`e*T$l|7hu* zeU>5}`J~Op{x)j9**zN{d*tLajXIL6rWnM7c|)QlrNf8c2qwGO>~xD`=j=0sg3^eI zi8q$C9)HUE^3Jpp=yh(;l7zp304IqvI9ASPBRaSo!~a#>=%eb}Z}iwoUdGg+6n4i{ z4&>YK3_S4K1l8JU2M7+Igqz=Bi?>g-e}UOx=ya97YffS|svlz%IpAO!`yh>3G_Q+C zn$i2MFVHyulM_#bS*iV6Qg9Zonsme&J+?Yle^_ftehbW4qpLQ&4?v1q<=$GHO!_ zN^^)vgHlhlkDHE;0NvDm%n~w#4Xex!CnkQo>_ql76aL37@}PBuX8Lrs5Bx0=dChFH zCEo`qRbq^Jr$P;Q)!OgRUb=wb&1q=g;5uYwX!RtfFH6a}$39@UzH=O-*;60RLWo^5S$f^A7_LAdOv(Lz zr&Jv!PkFqsspWgWaqnzs<(^7?LTX>WU60~w;drm5+U;@_1B25;HS9eVolvMOIXuHH zOxY2(QM~BlxO}Cdo(8m=XRbEzR@gmaCUharOep4KkrDDRalTjN#fweZFpYio3cOCF z>Yzfq#7K6;6pl}_LB3&pR2CmhE5ax_59o5$*)mZ!8rt8mpsJ47d{naHJKJy;e-yOam19AJWJBl(Hc*t$X zs}+7+Wevqnqv~v>Arn7mlE9%yZK#$A>O z$cv28))SQ(?e3IV_s0b}-}HJE>{fe54&zs)sapeL0)?CTUU%)y%YI9-8ub2Jp;T_{ zY)yQ^Qo}l5k}y(43e^cht8&xr56}0$6)+3|+atj5)v8l*Cwlfq76gC`X z_+D)1_eVn_X0B>Yk^-`ri-B_j?#TOFHbVT)1RHbk2km3J)=Q^f>!HQ@XJh3{omN+I z2V&HCyo|)H725Ms_R>4ooKK**&=ckNtL<%#4dJDi)IQkfYatykSVlAYZU;WQ@Nw}{ zx!7_KYR+l)MvbJUSOqsB@d3=8p4t~npW1JdbyM6e;3^}V$?xkbHw}bxM{$X| z-7^T6t|#4muh0O#FXk)tQrs0Mo$XOx>#Noar}ttTNj~tUg&ODem5qVkiB*n*DA8j; zIg_J0!Tce@9-c9wrLrk&OJGVIt65K|k$8YwxI?5R$V3DP={VF*Zkmal ze*29D@$~412e;?t-Y+=ewzjYw+8P;e?ct4-;M$HN^2iSvox7Ui3)%vQ+Qf?kwmwWo zSCTt({`Mv)N|{)!mm#O;4ap9bD+!ZGmdUoorGt>;Ud%GL)n=hwRRFo{c_FA0gxNi2 z{IR}Bogi56<-CUzw8jk2qABOZ;*HW>MIH?GZgjbE^@YJPOGaJX*!t5x+#M0*+g@si z|7-mCQ_WGWn%9W)yk+Hfl$A|1qE7gvB{yfi7BIN^W9!4g$dNiW6}2DVdYI#6mXJJb zGqd1|Sr}Mej+e?4f`lwgOz}vkQ;;=X>WBcgPyCm>MV{+^VLTrYy`zsfbnyb^N!q(4 z6_fpdfJ_)G1JJAo-1@!I5%gyXiVL!G)EVDPov^Ua(@6%AcpPEASC5z*%&)9Zm#CV| zrrT7$vB`!?zWfRQPJS8^e_vk4nv=o%&^WG59U-B?Wli0zTOXrMAAwPhOJ43}tM>6+ zi-{I%&ccmf%?o-_;J~9vy<4U~^xGIxW^)Wss$HTo(uO-c(FH2xcqT-gru7r)6cQ#f zJo;7r+Ruaj*Rm7gkq~t+cN%+~#R)+C=y!e!rxCckBpL#pmm|>D7}@-Yuvn=3cKl|V zqHL)(#D(Syc^i`7-Yfp>L0i?x=<5gdmqq3nCB?QWntcLMm^0#{zixFU0g#@-P=~*n zQ2>GJN+y%Dve^esB;jP@V|zqaACxTUvu>r~FhS}V(G(+?LQwcK4;-P#L|;|QB~Z8g z#dvtiw{PwC554w)xTg_dph`z@n&X8ZcvlLOE5@aj4q)*;nrGj#5O-wNsOaNIG9}B- zjpnjt4wGrvE8rEq*SWHr_uDlo2FY=&lCNx!AH4^wdOBIy>^=#;9eL9G+A;j~!A25} z^HCp7@Uxnc$? z3>JLwk(2u5w<0tb=ev!cw4!un-m>gii6o9WGovS*1S4_lt(dUHpg_{{>Q98G9o?U} z8AALb;agtSAO1YR`S^v8sjsfmYb`LG``3s(Bz2^#?M0~>igjw;=^i%#{n_#qDPxh~ z>pc6C;uiqQM7;$TpmN45)pDz=Y3l4wR>iaemli>MOg+>RmD*GeiHcQEr)RY3N%HoM zP1d428n|pRSQB){ex7jd9U#WDE_**%v@Kwt6Msw ze`C|OKZEGEFr7nOPV8BAJPN(A5U?Y!9A7(QsynS>UFg=}d1#%ebIHJ6$Mf+M8&r80 zViWQgGgMFn6LddMl5>S-w3P|!c%hNGH4c0_L6LJv)RC(Vwe9J99rMIK^&QmGf}_xZ zlWCp1@nv316D)SJbj1^cKpHtufA&OUm68hj;x===Va_F-|+QGS%CSE(6v2$sj;elPJu z2FyW-a$PAH87{f02seA^ZB-glVx|j=YRBCb!-l{@oNdV+aw?OGsD#1Krx)LcLxIF_ zQM=}mx1u@x(r-YS(yfM}@VmqqX<4(dpktH)i21{+$@1CwoMEgKH^lsvsI{wCP)|3tARM4SouO&JzU)dvy)#G_l)qC_i_I4+y*Z({k?W2Ha&g^`1e zy0-V{%9S$5;q-q+DcvhnQYg(bE6+SnVm>fA@(*wl<`t`yABaWrCm~(*z4uGL3db(;f0zP6m^iw4e}p=kO_Ip}BeSAL1AK)*5*yHr zSX#|6Lf;=F4lvEx5r)$BFVjMwX8VUa zkeSu-7Uy1q|GT+ahsMeVK=-I(@+`9X@1~fWJlOy>3L)^9Vs{uq!0RPWE8Z$wq2-}T z_8dJY^M9lF*n!cIHmxAwnEB(U3F-ByH~~(ieDJbW&t3-Tuwg&N8j7woNnmm)yuK)%yMW`y^ zZB;=!rVu$mMvL2*6Av!eg?)B(#qS?_H1#ml!%u|oqrsf^7HP>{_C!|U5M=?U;r}bb zmkZ`#%q{+1Ce(09x%v%9Rx2vUWRJ>@% zV}-BKOCkCA2!%iOz`)16Ub%YI8wEzO`B>Gj!ALi|zt#ve@n0qLCF(b6s5wkMss^NH z_xI>7sm_lt_ALo$v;Xjw%0nvqL=D;I^c$CJiAB3m&%pO} z?`11D{zJJ=(<>OMmb}m?$Ter$zQ*SP8Zy6@mD{BS@5iO*E4!6{K}n64^VuKCRBWbv zub#g-`fnHX(OpR}Nt25WKxT`UEuWBM{$3t+g8g5#OsAWlPp+Gkt)}I_OiM&<6Mx3& z?%wl>6?YvIok-6XXSbwpOXHdk7fL7pn z`u<{~QoslLzn3z=zMhd)ZTLw2u}P(;%Uj1RKTl5nck$}g*9Js$&s@#7C&L-GP`iKD z6czxTPXjRa1zlNuXzFEi{N;A;eYPe}jyw}enuI-zszpRcDkX**Dj zFmm{d?g{1kCJNFLrUFNb74KVKeeL)^NO*Q9!>9`<-*X>4e>pV_R5!g+_-Csvo&Cj} z>VOXlpt`5aRK_#T$2@=kvvNv@9|^)H#7$4wpWx;$nC{B3D_apaVnmrPnXfBZy7CWK zitf_HhWE-?kf2vXt9rUqnLXr4m*mz8HcAo+*wk1g)hm`J6rfwniF;)%%$gDXnO1{h z?vd|N5Yp-Khw+I_KUr5IB#GPWrbdMIX>I(xC4m@Xg#(s^{y=j5hrZKha?mV2-^Da!wegK)Y`@zT_+|L zX~Z-$+9ou?t$2onpP9h6$%IC9Z{nF8z%vbiFHAo7x}5+2hVJ=aqiq}q9g_wnwR*0_ zOpmR73GmCX?9G+JA#vUY?p%Q2tw_jj74_AR_#bB2tkjq{Ua6NtfzFJXlsQPxi|EcA-}j zK6z-Li*nIc>ZOR)@kX_A_-!An8J%2C2FG*Xb3sn#kIRDOxk1tm}9hhLK zy;#oV1i&gGYle?4RaMckeG%DNoT_8aoW+Ui|lcz5j*MrjCURIQa6ha{nK2z;V*k zF00OPMz9$c(xQOm?H`KWnTMn=uTsf_BD>6A${pOU1=hNQl{rP9q^SxDr=Cc5KdYHPb)J$%5J#aJF5f|;8l^jab=$#^P z1&oY;1bcD~FMq@v$#HTIs#W~gI}GN`7>sboErc0Ih^>63FDwg?gde~vR#}sgs#uE~ zi9on>s*8Y0=RIv`z?sI2XD}DgQN25x`~6RE@ngj}q1Z6|4-Rj4BoqV9`}YdvAj$1X z^L)mlKNNGU8mJ@-IIaELkqxYL>LQoP0nD4{5$Mh6{}gKo=_TmLu!;ZM zlkqV$8VB0wbI0->%@kXlc>hnwud$o{bMD4HmL6zL4SWAn9%5wz6}a0+eg8w<#_uN- z{p9~W_&*j-oQ2kb=>I^c(nqNuOC0}aCqruqUvqGBvjL#1fZk)wJ=x!ZEZhh0!YYQK zKY*!z3I;(CSMn-=AT8pbVG|`LhyS$CPI#C-@H~4bZdR<2bI-=Gvv5*Q}9 zyiCK3?sgcLHhfA?gRmoD=59qt#GKXMwK!Lh>0dQlY;bHwB z4{P6yM#32}ZdcUAixC*Gq%I@0O`?`6NfbLdi0?aiqQ}-`DKlp)0$CoFweY+Dl+egN zJKjhDG`zt1+(nvLbEzN4{Y7q3M`kc+#H=HE5Ib&^ITq9LiS$YmpAaDb5`)AgK_k%) zWMP0YVnhaNu}F{|UN|4cs15BA3XSQHAC3sMpg9Je!i{*(c*TPhcnKSX_sesA3iLkj z;=QW1NAXh}z~I`%lb2(u&AXlXK;1$cw8Dih2X(}vL3#RyC_`B{uoILX%a^D+>|cAV zgE?dA)f9_G(#ndDh<|ykP3+InCn(tq-_^?_2aE4s*PyS;88=NYTyx+`-bTkK)~5(Cq<56;XKjzX|T z%w%O<*WW%1Md-iK%Se+btc@fcqm2cM!WH%rm;1VU`b@OKt%>4<+mIAFp}-SIHQ5`G z3g&Ape2ydnxeuyMIr#Kh8ZgU~N6NHnJohy=w^_Y#VaK~X7 zp;P`6c8VFl;maFD;baGnaa-O(zMr_nz1Av^c&CkPu{kDr`tSIwgJMnq?VJB<|7@s6 zU#KI^M$nL1kXlByHlu8xHP*h7zGF0*%jG94`{flF*^U+Ig8 zm^Payj|3+9)Z!qp9J8!;crT*)8+P=Q>3=8cDjb8Y!?EDB>fctr`Dh7yU5$2Z4c0_Q z{mjN-PP^xW3Wgq4eh7H=S==aiC^fd6A;`qbjNm+dbj!)1Ve^JMh)qHPqItx)#LGq9 zwuxnu~dy=Mz;3o5SrY|yQ;O1k!n}4= zN>v5FJpZ~gZ{9KusWFR-vEK7U#V6L|!45UG#<$uifk#8hgNj!j43{?$wACM-h6NGN z@vv-0Q>w#am-r-8`XzmnMpzrDUun83KIm@y@qtsH6p@^2$DF$~L|Yt=^s5d~N=z-` zN~l;Wa97k)me~sit1_q!dVeQ}oGcRth$$}k=L!C)&$B>WqcquAkBd2uyG6(wax#o! zqlzAi-ca zq?4nMQKNB-^D#W5qiZFr0YFVDTZA{$kp)kMxa1A?Ene^VwI{+aBu+ODnKEK-x5f{E zr?G5-$FSnQgwdi>KO}qmkBa=~hUSU0Y+npK>yq_)!d%wfRkR2lxxK6sP z+55Q>L>b7FD}HO-hIXayEvcd9oUI@>!~u25&u~+BL66;37-V=M<*+wJ-%NDYPD2by z7lS479Azsv5c`N^RSJ|xw<_3!o}hosn}_nCdc!j0CZFsI^HCsX@R3p;>Fdd|Ra+}` zq(VjyVUF+aHUFC8x?L%CNA+cProXtK@S1ab@&2kT{JfnIkeLmbJpR--AB(y>`5!vW zNkaG{35E>wrDnRFr>%Z)UcCOOUm#=pr!MmBGwT9#)22qFcvPEaP4TxYC*4^ZwF+3^ zMOn>(Iubt@cRXD4m%KoY`Dq}uL~{aCh8yQ_z1aTnP}+7`cbb0U0}uFv+RTO}sQAl{ z7d05S^Yc%8H_7gF)i;s&u-C+>@Z#L$0<{r7``08%ii$|yGl>2I@YH5*^6VUu2{;!;&NPUvvp4L`q*ugmqjV3ka>~HybctY*vyQw}U0=?mD0at{^M?X0f*f+Z{ zPMNHR)e#+9py1vD`OLn93H3#7E~-3VaDxwC(lx$Y(bZ9Ya36edha0Qp=K?Awv;psg z`*{WhKMH#hzrBYNHoAja?c7W?ajn3Q2jq*!J|FRROpJaF?NFJ#I3=0LsnxcbnBmVN zz0;JcG5sDAVSsBlo75$h zswp>6$eC7AWUC|nJ+IGP7IB4wM6mcb=WWmPY>6w6fG)egUO5Ac_(L=>hAaB1>A$N$ z@uT3!dg2}ivNlLlvpI%zY1wA1!Vzs_tknh8xdSSVP_MUB3@0udZxkgIqbAAmavyM9 zqe#QsvJA`1nwv5NB2DmRu;NzgTC~#>uT?`aDhtTooRxZOzR7Q_KYiH3J&S<$O9SZi z%WifG3MWs@E|T?`AvEy?u-4sV)h^V36w2OsCBp>d+G-?_SVf;>Fw z5MzQNvPgBg)vYKHOW&NE$z(G}ZqCT$Uc2?x5|UpM1*gl^l&8~xOY$&lJIr|E>}HI3 zqDWU^%g9~ojn79`HSc{Kr}T5rHf-k~X$l)hsk5ij#dg7G0E@*){6MGQ`X>A!lPTK> zVa>(`xggMv^Xt&fj)t1%&)`17E}i7eF6TJ5X6#|g_|=ZqTgO5T_WH`&FFg;Cv7@+l z2=IU^M&?yJnjsW%;&103PcOJRMp)ekpS(`&KqNS5)vf<%%6PhR?r0Nvb za^6PK-fuTd)}vQZd>=9bnf7^1`JEDTAvLcXHS^BIgn`4ICSM9f=dbVaTh+dWQGuA? z#j%05dO$Zpx5U_~>cRH~XXY8H?VEFWpPk=P)#g>!NSw)X1chY<--dWToa)~<&4{?6 z`X3L!qLC^%>y^hm$t8WtMH=wFSzqYb;SYiooG}pUP~3?vSUbCRnv8mK78M+EOpJY7 zVVTGSOF00=4`AoU=}apV=6a8&wu6S)ha5|`K@}G2^+ckDL)7qkc*g&O8?*eB_ zlbpb#O@gEdl=^0j;e};M+L37{BcZtN=~C4K6_Dy_157H)o(~^2QFM`6p}sgnYDaqM zyWtZ!@Cd;fXzSBs4$_%1CF1!Y4=<})K3w_nf=GKr>;~rkh;z7uY?vj{x z9p%D~=$kC%C4S63VqG1rUh^TKWDmW%Qc>|;&})P=H1B)@Zg=%F+F9Y=wpQS661j-~ z{awM$dM8bWW=luDXuOfhhgzw^RFV^uLl1h2OlbhUvVxvfP9G~t>NMAp-aKIT@MRNZ zjj_Gt!vHFow?=w1roNw2Rm{p!9fce(pq}pwa2*EDUhLM|{TMIK(e-|yIU-fi-<370 zTRcnteGUMek05`X-4=;mt#2Lr#CRDN}^Fy#F>6rc{ax78qEo2~ngv zf@M+WZ-QnDlYm^4-Qc+1LZjURNfS;$CHjLlG2xCv7ISeuTifSgyAYAxcT9Ju zbVVbC-M1<^yVVJ7nMt$S*2K4})c7uszgAElA{gr&AP6XhYNJx4XlN$GCLJ@9M5$6Z zHf~y5E}e)4BW4Mf{3Iy8KslKf)ZIL6;pGG<7gwBwmA5QS;no7@9Re-92X2%l-0n7t zqvI|KLt0CVUZ;AC`g+MwM+_z0Xm|JbB>n_L9k!*7^MJ-Gd{=_4ft~)@vd(3kBYX0_ z`U!B9gVt#?eSh5;Gy&RUUaFr7s1on^>44&zc4$P!SF7@HrM?kmTU4AYqtgxxCvV$k zUxI#V)*kys`(MokHNL?=ZO^Y$EY`>~nUtVu{W5(+Icbx}ad7rNbi6G4EAx+eelIR! zQ1FbX6w;OtRFCdnb|$kdJcu9f^Xy*i{R{VrcZaHkVf8*9d91+)69IOXsF9xo!=x*; z2W8EHiuI;7!l^hpEbuj6B(hs@GEI*vdvj_-0{v=Io3}43=2d;Cs8-shS+EQFzv|Z& zJK6+|kxFXvC#S$beT5=2CKjpJmU>rp_qP49JG0N&gCaS%$FCe^zjJ=AVgav@8FMMr zFIi7Gp8uJdieOZ$jp!c+qgM!Ee0{Rawd;zdg7E@>x}WG+0X>Wt%SZ_YiTS>QE~6`% zA--HZA{}t=I^OS!sKyi~{q?JuC3gw&9lDS+YG~K1=oWoezxz6WLL?-l8*Bfgaw7)^ zbP1`0D&G(^Veg->a4$lLu~X1GMES2IoN{O2WuJV? zWawTc!rsk%ZccqjlNA&#>15Od+#{04p-5jZFSasLRS6sQr%sn zolDQq#BE(MEKDhy0@r!HUcI1D#J9=lSdEv^369Q2u(PNr(Tmxq>(O_&EVsx@P?T>J zfdGa{7hb(HydzO4H0tGn7gnN_Nb=`kGAl}6CWrdXn0&Z`1>p(u@>=ctGKEQ{WW(0t z+L#fR#R4IaG_xdkTl1$M2fIaDU?-C0vOS3!*%7>_vl#zKK)mKEkUBy`LrZ%W^E56Z zCfhpHcAMH3o}EpOPP{03#3)uV@b<^_1p7-l-`~^C=gXj>N+6pES}syA5ZM)NvN6ab zcHtARf5RE+NLx{z*zVO3n4{ff0xky=9ha1mea!4Woa1{adP~xfq_)7%+Dx~8wZTw^ z9fPVkegA8Lnf?9p+Yfg4{^H5zOz#P8D~GtNg?2~zDRMlVIbX<9hvt-H}}Ogr7`O4`ej9>1=5SQp()NKt&T-gsKPTK z2d&JdsHtg4o+war=)b{l9>`%Ohf7yWY?%D#>7b%@X&+x+7zN5ucRN-SrIF(Yu{@#D zFzNTVcT4DZ=_pJqO+g0(K}`y%V=wg6GurVpqf4Vubmxb2EH>jso1M&H)}`2cy!Zk# zSx$&f=;(~{r8-r>{b}MO%~iZVCrTXN!xw8;zsD;n%Skkpe!1E@PT#!d{i|_-;kErR zWk5mB>tUq`tf+b>hxt+qu0sa_qSK+tj|U!Ry80qCCKxa&o=nq%co(Sad2w71O_%1J zzSnq}1MNTSeYTfbXLn9f*WNWuGfFo%4eB$yO8vj{TsNrJ25oaeZp*FOrFLoQ8J&5e z<;XAWH>#(Z@jt0#Zy?l`pL-FpJMC5!g~+A98ZxX>NHkW4E{q}&L}i?EKU#QE6!2tFx=Sl~v>iL!!OW z{$qdJO&*R!fI%x#{%BS~!C0nqXS3J$!GStxNfav@sQytn6j1j z^eQg{Ow6GJAEOc=CvB!Qyxb0NilAc>R6n)jwHcD67`Nsj#glPm5wldF$*_@rpkbJN zw0-vObYqj3Mv}(JHt~%0*ao6iF{g(|NYGXE4J&VxL0G17J&mNJ`xkw)Vr#U5$}-Ii zb4{bqSgBoY%u4G!O5b`xJdcN+l%8F5>+|Lj==`2Ujt_8sQ{ee|t%n)=$JfyyT^5MD z-;Vp~xD+XV9^+^lAM_6sPvv{7VYZnV7KGDx3mS80OAR}x3!l(xfg0?rrE6xj3-shJ z%15A-OU58C70Pc&3zdy7IQI7Hk*luKI_{Rn*g(DL8%gFE|7vyMgX+~1Y`W1NN&`$AL5bK-QU;t;Tv9f0tdTPJstdiPAJoc!4{+q`JsJ7f(!doI zpR_f8`fx(D@--lTM+&K*$%b6z;Ho=&`qEuYZ#suN3FoNi#dTkk-BHKw_vO9Lo1d>} zNEsWMGIQQBr!2A-{Jg>96*tqESCU zN*jzMm`s~Oi5gX2T`WZT{&~(ccP@>#Xr<7r}I)0qc^SQCqaoxPfWpY1u(w~ zh0jwF2+}Ht6(FoTJ2|l>cmEkoXfD$FxI~{8KWYXz&@+L;{XPv1CzuPoPtyu29xh)a z%j#m|9p>v)B)3%Y-0E8N@*P%##Y$IU>jagvr`~drw%=&NkQcy)Umo1-slQf(2ug|z z+Kcw%c1Hv$rICWyf(T7Z^dHfGgO0{wjkP6h4I*5osxIMZ(onlgxC?Kqz@ztWn$55H zRq-PTe$8}c+vLo@rI^)klpNZQb%H;|rM-WHe%u99TNxRj_nIXog*U`r;j%+#Y)z!h zv?58jE41~+)8rXmtKYGp?)CPWqXrk_7rG+Z;B+?+L48Qu^^MReUyU1n#21&}TYe#C zSmK!4*E-CKIC2Z5Ek2U!_4BQvzB^(Zg5Q^IPqSz6{-^NI51Hn}+*clgWcv$BM~wT0;NrpqsN3i} zqsq++jgtYRpvi+zde+hob^dF|ct@ZbdGj&h(7Ml$KLJ66*Kbr|V1mC;z~A@?L{wGK zg?G+B&S0~#y0j=+c|oPk!g?QtMu`Q@%f*Svv?Q48JFUbhn|0(su2SB$0j6ZK{g#r~ z-1MeI4T;RA4i9{#Gto;S7#KlGe#68s6HV9ZNyEYG&O-soKFkq<}2x4F_Z0(&@^-ND{aBKWj62Qv4)X?6A@Fn>VJ`7@$|hD?kgpaF7>W|O3*Vy0*t*%H7i^Diq>fTN-Hg86=0H;jnaxWt z6Rq z4)!V>HExPe??1kT+SA1*bhQ8`*B|VLZ*+e$ST1R}UN4}1Y3)Z}X?B3)DC3`*Iav$6 zaHL|0k%OLKn!rEm<6#}%FzyaH97XhVI$}8%Qg5pbFy12{>?1YS4^Hxd0ETc6z@1iAs+gnx0I(c zt$eL{EUKuG&|Mk7d)l`Y^~P9yqH;PeSzcQ-1GkIgCPD?;L7n4fvs$Nq#YeGcV#X6E zrMd7pg3IM(;#T9dlpHr@i%7Vw?vwW-AB3aP8N!gZ@UEE)yCV;Oy6BLGPu`f`pv&`i zI8;m=uVR?8>?`su5pAeXn2ddm{;=_fyKPH*V!|GRd=o%5db*}x7TnydmO2#W!1?); zO{N@0fLBgnF%=ziC>Cp`Ka}$fjIeGqya{uMl{WL^|ID@lrOK4{^JnX+W1 z)cSY#mD;gCk4kUE`IA(qKJu|i~NPY+n|H4;*}(xh(-)VJXPUNv32P&W#w( z$F05}OiL6?Z8+CLOOJaeD~c8-_b;lNFc6QQO3f0QlGVEAO33;{E1Gm}vG}(u3AUYZlY(zNa0b41 zw7vI&4qyh&xiV3%sLLZ{We%mAAkj!pLQ?XAI9k}W?|a?lJ!cp{K6&}y__yU)5SUOlr+ zf(yMB0{SXWw0VL;Hd}>^|a!q(qN6Aq8~srCsPKG885*LHcDmQ;Q3= zs!1g7A%L4v^L*r)yu5NeP2zhKE=&8hh+sg*H(bkY?OgmF{CKQn=_)HTr5RvC@~ zf&(Ty-a%M#G+iCWC0OeYb`O6pzCg=Ix1dGz3!sXKG}jcpt!Dk!4TJx#nva1VES zZir2?XSlk!&`#{yXN<6ThOHbxjvOrqagyotDGgCHL}x{log)(+c<>VCFFfi4zeiiz zIU}Po>RwT+IoGl^r}7!A#*#HC3nb!rS{Hc;m9%)M`+c?At7G2+`vbDq>bpF+S((P1 zRW1^yF&1rjZ};*;Dm;e?*0*2URAEaUBQibT@|N`O8G@Qp2zSiJHZIrS=(6j2xLXr5 za$3H<1avzrWyde79oWxc+v&G3ra+C$!^| zHPQ7Vny=wF4~j46N5XL2H8vjwI;Di|i#0o1KB9_Kit45j_h?&VnJx!BWz)|eyJ2&j zr_;$079XeS^-OoaS_!*j6X1-{^+ggu5ul+BWm^aTNLcP(2E$*ZrK7?JPOR*2$%nCa zc&pXM@P`hm@l}5dlSl5_c+RTQGDKVkAdui-pYle<7O|k2SEFHQj61Paw z5+Y}eBJyy^l0?PZTG<#9Yl594ZhnS)XKi7{%?o}#G4R2M#>k>|LRf^CODKwOucS~* zymgJN^7rwkvk^{n=m-?%Ci|@+{NFAnoi~txItx_Ezma zmkkT^dZYL2ogC13%Lzr3rBX`O>yLBrs8qn+U;f(S8BfMyr|c;?p&`RSloH)C9oUJdV5<*ThY1Y zK$$BRJN()&!O}iC*88_jC5=;Jqf$iNuGZZAeB4Pfv_HiD0mUTW@m|^S2ZR@R5p*tC9=_&CnZ53YHQW<+_64N&(uQYiY2&h1(mDxZ zwRbL{F`7wfD$;u$fea3-Upz_-w3TKbC#Zd#1cDS$J=2W2qtOKB!MJ*KI~5d*S7k%d z)#FMJ4Y+WMabrrpw8bG*0H9-7P@!|0`hfpj5iR(p6U^jIUejXUMZv$si+!tTJrg&wU6CPe%%!seny&{p z1iDXz3eyps;}KdVsmStwN4j-$@L8Ljtxzjn-^J6AYWUhIb$v_`hipQ(_F2bEZrTS! z=awqTjoq6I_8NbKE6CVw=)NYZ*4W*1T6V$>Zcm;oBDy8x1Df8oJbf27B!iPAAJ(P zz^o2Myw|p#V-Z|YVo3xLu9{D-rGYlLd;9}ZAjt89tt#Rp9-0@1Ls3?9pq$c*v+hb? z>!@W;Lht2mzRS0GJVaRZgcmv5D(Mf%Nx77nCBm)gM^`W8qte9uf5+>KL7fbjlPhhA z3H`E>lapHm1lc^1r<%)Q(I>n|)>z@)aDEF9zpJke*s4tTeAn(**Yza5PGa1*K#f{J zwn^oKbP&E3r@Pq;1Z)S=TzFBkk1UsDVVE#2x}RRc2r0gV(Um5Wo=W7TD5qOds-8bI z(?~tvo+^S&dK0ds-p+8x#W{8!_Qx0SU#} z`h?JiswmrHdLFmgQ%DVAkwmd{fmsVSNeMHgU9VGq2O(nYJ^xmW``IM?yTd^twt3d{ zLnoH%J?z@(mAdD~oU+k?cG-zxp*(NJ3oS zBC(2-4#|S3zpAm)=7ik-yDb96hEJg-b1sIrfM;YV=I)uSNyE1h^2yvE%EL4c_RInB zEUXkzXq>k;>My54I0VC6z9~S?)U^r5goB0bS}Iem`1uPWQ4pH#4=qpc9$v{diD=M~ z8I;XEk!WPc#!KFQWy6uc!ngdkswHI2SX(UeqQH3cVoAOst>=7(V8&hO)vch@K2rZ6 zTXG7~d;Ro-OBj-qK{gQl22R9>F$JyKrmAYFV4KK8CSgz(Z=7h<8+EivO8zF6Aq@m< zQt7PI-J2J2S!l{BWSL_*j{C-oTa3mxy!c%WyFYy1(MO;R4Dq<@y@GoKDfpKDTBhAd|8h}n4RVRHGts5L$xDxpFC-uuMzqMgFF3dMz9cVS-Di}&sNvw>_@ ziz`1&9~Y&MlA?)EAkYYsP5h`LU)S8en*yWR#C$E4600;&XC)r5TVZ@$FVP7QeBQ|m z)?|3%K(YW;l`8r;{o)5?4;Q(bgND~Pf@1sTDlEydrq2p4A>XnujXTb9(DV3{;zIpI zmNu|a_bO0l{4EKBfjPEcQw%b!;*B#6!7P|@qBCZ>K! zI``kyrV6K!n`tz++38?5Z(pT&(P+xxfFK(KRMqvr+06i9Sy;SsC(dYIH#)9~xinhI z#{r4II+~WxIZ?!s0VeQ}c>2GY$m_K3h+7B29pkmRY)ve1V(H#u7RAd_cuK=*a6zuA`jOeA2O0^~&Oi+x(S1Ymz$C=o*XmHJS7wL#+e zMP*l{SY+v`XBOCX`84a>w(o%Ou|%RQgGEUzK4i)>aN5s+fVk%ny=_?>NP53ZtS%oJ z%N`?rG0r!>4=3Q%o+A$YfHnm1gp-~~UM(I*W-|6^_czb&9O!!3K!K&@qhmirb`Rl> zEDT`EnTL)3FQ(2rkgEUx{e!gGleV&i^(=I1F6UdPoyQSN>v$B^sPqy5NT|3h) zb<+XYjG#3kal&btV%+C?(Voy$!u8pxCwWIB1teYtDAcyhZUN<#G7t_UOY$fiJ`eQF->=Yu+^RWPSpY7%kQs)8|6?S~Uxx6;59SChj z2EP;th*!cJDvfU;#Aj#n^Yo#5>p)g-86OCEu)S8I$ezjOQc7>HazecnHXz{!KgCls zBLksuZ4E>X;xrh9%(GFu5jPG3x<@Aeew#Ek*gp$*8R&d_g|YkQ(!v1)Jymj8lm}Ts zp~T(4nSmBy*ro|7Se8Iy{%`gj{9~-xiZt~gqL@2FRl@`E-5=-(|J(M%f9O&(tzHR( zaG``X@jsjn5c#ss%;VA~2sQX$D`h>GT+aSSQM^>Y__eYAZ&#;n$!J*nUt{^5XXmxc zl7K7#YRs8*pi5~r@b^qq@0oXd##~7nyhK0B2EG1P13Gl`-;;?*(9|o?MinW-uf%Nt zb@5*2DQN=ZzxyH4tT9)2$z~LJZqR=GH}lHI5Y%1j{9f-$J?hz}*NI{ui2o~8IYqyf zul~{$eb-C61-?&@izNF<$>{x;8=4fb-t@^R>8LNOw;Zrp{^w8>`#7J(Y6X$fQZ$O- z`zfMRsTO~47nd6d0bTwhr1d2eFVWn=z>LiQwz2zV>w0%Y+y#Kaflt|YCGJ0uEuzF) zCOy)H;XW;J$fUHD5+9krVK!90G0^BoJT~ZXs7)zd@I@No`t%Qp2o$zmCPv$KIfDHP z22quXr4L4te90qb2VCxyibp4bdJ@ujH>wu%?>NQPF4#;(~Ycb)PWFKyaUpnyA1m0o}HP11jO$ z)28~m@U#!trC%o>fUpyJis6OAoQeIMrAULyj6a=|{paTH#dR6eg|4L@fqBCWq!JrW z?r*))v7#P99*L{=7pzx8{Qz|*wg;_?boEm?Su1>ghPhzvsx|^H#WzbA^EuxyU;5dP z8RP+s)U3sag8$oK8`}9o&3>b9-pI6lMwQ14$qRJtRpp!bB}(uYT7ELqQ(fxPffZ(r zK_ry&cco;%6mtVZjKCmlQ&X>|Mgg7bFJ)u*d32=xy5E`8+eg47+aQ{@q}aJvu{1c5 zEF(18J9g1pTOD!u6aHuC4ewsIc`pnhAYNYHacdcCSnjY-8PUM#Eu5`p^nxz`H&wqC z2Gu7;Hfnn{5D|~39rymYOQti(mo1jB8ii=srgucPpyJVG1{>i2&EAGVQ_x_<}~1O%6(j1B)bMM z(nSUgHNgpk8cD3tnX!N>2;DLpKe8Yo%VhNPy*iP!jr$e48Akfbm%I`fpltFoDp(pA zG}=&p_PiNRVFlj}$S9E+)@%P;Gd&2=K)Q$lT}HsAf!H?-_E+i%d0r0G|IrV)_S->~ zfX&8#^8-yg0;+z)N{Hdwg6Iakp5p&1OBIj00SeI6`%}sdP@KQoVg96qR@hzasw7!4 zhyEr?0w4bc?%K~{q*o=}C4loNN;r-djqd#mge%=M9RQRV(6QB0mEHdwGOOJKxXDhN zCKD3Sn?4di-qioLeNt$IUR)k51{fZIYJvGOgX?bv8E$>kbs4DnfcL22%mJ6H*r`7o zP`Z9u>ef>?LYV-PbUfCHopJ$2|Nm_d9*-jLJAbaKxhkC0eE#|ISh~cIBhRbq%}7{J zHx2(^&3p6m3pR!>4;ceA1DE2l!a$9pe{TdtheiOX6B!~vNSE>-qyHAF;a-a4?=6$6 zcnF|C5mvS+cE0yT}Kq%wy-lr*k0%XF31QP~?ua%XIC)l`D zjcn^?pr<};jDQ8hfeU z6@27*!ZZ@gHNIAKn?kj_DupX|jCbsWIr!a)siFrQLg5;U*1R^@o{G=n6%R@$t6V^q5e^4V)Jnu%|rhw zk~an6=eoDMoePIv0wjR+{%N2k1SkxQ?B6sqtI0Qg^A6@=2X~M5h_*l&I-*;cRHmWQ z5`7td{6awn`~xg;hMfd6$hEFjom0^O=GWB7bvCi3>EwQe zjVbBjkWz31C=CD$U1h_@ZrKxA`uUL6BATa8)`ZXMoWjyWfp@#*0}nykE#%AZaA&^I z<~%mbPYPzrSu*1b@JEbsiPbK$&r=q%pB2-@D^ZPVL+rar$!=9Uv~)}1S`5hN(PDJt zUe*qhNt+hq_Q0Z!Me~L3aM|c+B=g@*;o*D%r3%(|c(I&Xd&|g5mD+VExW!z4f;y-$ zvE7j+tn)5?w9*{g#w!`Vl0*$9hJ0Vi(hYLo4+0(Kl$L2=p>BPSp6ClDLgrYKac8Z* zb5i1`$-88+=!Vt^-|2;|v32${xzVE$OLCBYgojV!V-A!i9`syGJ`VYD>{U$llh0CN zgZoVPU%Zqxt&@no^9G;M--Ge+k-Y0|uSFHcL(Z+Ih;ncQ&<95tHEi+Yrh(33vr6A# zP|m;f*z6i}1;Mj|CAlSPOli(my}ugM`R;XR#u$^>EyGAqI2;Cbk=EQjE%b$ z3HIAN2u)l4C{ox{f9&X8|0d*o433F;gUKsk*qo%}GF6LGTk1ZB5eX2G$14~L5naZzd8Ed{uA6FuAoq;VD|eiPc%~r8 zO+BLWyEV3D4rNORj-;^kXxkJGIx}G!OeogRmNp$})MIIH!?0lX!+tvT;D%hC=P{k> z#^EC#HVi@b1BAQKjJq*z0uA;{8buVI$nraH55PVqt@8dgnL5hY0Lex*FVFS1*3bF0 zS7D)z*3=CAWPNcsArQ)CJi9vzGYMGfT*FY!n}9p(Ok}fpvlD*30nZ@4LCKZwC7EDE zv?_@Q)be3@EPZ)NMyUz@uNfW>?_YK0c@J-l>jjvTr+I*X>luu;VP}Cjm9QqxhVfJ@ z{FwE#BsP#T8zmtg+iTL$hpu6V^*(OPy$v zqop$LTfP`&($AVbthZC7{oP3Hm3ef=$%&$ulKO=W!^6IFc$RP@osUVyzD{C0$AcSo zIl@(4gzF?lqhPI!rUo}WjclZ0;j#MSRf#?Artz!sZHKfi9-hx|#;@bezE7NdlCQ0J zv>1f72x*!_e#D=%a7x1IYdHd&sUbAvX9nq2|8!0j0w25CoFh!i7 zTaCW()%NpSAL0EN1fdd!AE8bUt7fevFPEiTgb0gSAG~YB4zG)ECD&{d@(rn>$BUUs z@qC;NR4GG-EbL#CFqS!@bGDbJD+oN|r&%#sGIa;dNBB(^(0Cw7iHUzP^}su%^) zrmkJmrM?=)<;Dr3+0S8p#4J*IGPa!0yXnysFYWSS+^m6h*AxiY9!h?XH5ZM%thN%d zd*CbQvEE1BBuEvcm^`QPlRQFRkbXOXsT$9R=MXy`Z`Xe0bbjLI^Wv-7>qWMAci8gd zg?@;K$G@bqctdn~Fi55w;N+x9_rZW42(KYvC(_^PRwdk56cpyZDc?#^KO#3PGw?%# zjIm|6?_LvcG?QqMcye#&7RZtpj4-*c1tqvlVzx+F*GBu4dg}W=U><%vxGj$e)mp{E4czEDpei)VC;5L*#d8{ZPD1O@LoA?Htbe*$5qINHZy zUSn z3XEgyK2!8KZChixsr3CQJ!KWfyrjsSji^dA_C*z)F(fZKQMA5!L!x-heRjcVnoxbZ zA0Lnil?1a172$W%CP=$!290(*lPxT0#+!(*tlY*Vbt^}O&ML8^B?cMGWgv2X+CT1yO7pyuQ^l34!@f+hekGV(#72|oE9$_PUk##^*Wp72!(){0PoK- zR~#zUBh-mZl(2a##I*xn|4Uq8+I7b62PcSI5(wg?cKIx5Q{p6mACvhF>^6`iNRb&W zCLEli(D@KW=)c>3>LFfky1%sB{p%={1YIZ87~WnG3)>{NurMd%SN=}hd+%)imFN#q zg6vjCGq0bbO@ab3DezkoQLoI!r6MQq<~gN|cwCMiV$Yvb&$>V@4}|=V`$Viiu3#hZ zVr2J11;qHG`E5fWujV@ay(>c>*sgo}o_nARQZPv$9?h;XcE#Yu9Nq`RFFT&vK0J+o z3R!|@&mm3~pZK!vr`MLFRiT|8=m{JdU;iiXGe>&1vO6;Hcu0`3m;0!2Ze zI0=TYTO?1H=xOef7wPVWy(+B7t>9J3^S351P{TO{^`$01bc5d7({HQLP_Ft416{7q z^EJRGR5FmO$Zj5&)cvH&=6yo{BXivQC0bG**Dr^}`7A9h@x`?jr;MIxR5*ZpEpx@=2l7e6qPPsu{3gY!^e{+aA%VpMc*7ilRwZ8 zevKk{vxBM$*)aWWiQ4EtGP`{Wd*$=2FZk4uICgQQD;&?L)UffA%+E%ddY4b0;4bz{ zdI}8slG2QD8|_`sw!2047$+x~B*m#&uvjjxLb%wNAPQam`ozj zg|$1Vx$_WHh)L@{``K}t4AG*x#ihhsZ(A;#FLADxrCeN=9)}z|D2}hE)HE2?Ng6>E zpSkSU*r3<6BrPkdc6|I-u->kRY*WEI*Uz&3A3uk!VXl?)M_F_kw>6(Kq8op~ifxVr z#Jq9+u^XIx{A!l$2^|c@9KZlH&SU<3gP&290S0dGpS&&5sec#fOb^T*M8hP2363Kt zX#!!!I0Bb0K?<$Ov!o_GZ)jcZmsj}FNh}cdcM|Uc?DT?yM>Wl;_sunf+AVCKl{;>m zb7YPRXZbJ(4<)~GGTFEE5DD?K1$XMnOH*`?9FQfy`IKyY=RN@!_=RebSChbFe(J=& zI}Qxn=q!;i?G;RprC?-ICaE(Y4bDl?huko|b^@7I~(;R-wXZDT< z??;4x0x3WLqj*k%h@Z=qg){nd8ygz}`8h%*$y!gRa1HmEft;#$ok&IH#C+-9#&g=h z2yCc;5P3+&dqk#UI;Ds{w}g5Z_9cH<)a|YT2SSVj z`tSVa`h`Xpl=WLBRG&YeHL6*8r@3&V>2hmTYtMsR`-Np8)aeG`k5P&^{Jh!E{8iX` zI&G#s)+(L4mIjUT8~8O{XWVx?r6tqqe2;XR#kszk+YwbYy2|m$XrYHTtce;7*)->h zFLGlkfAFmRnW-En)f0dJY^tP0=W~BJDD*R0Upkb{?4Zl&gSRz zK5q-VGt=Jbj9!P4HWX~vq&zGcYayE8*r!c#SXjN8bLyzhkI#;|&%9sZU`ewBvC1*H z(|3c7EE#Lmbj?h2u->)f&`l2{^|W7{2Ra^WZK?=BnfmFgsTprsf!U4stO}#6h^172 z7OC`EU^#t_W65uky-ws7L<%Jpc_qUw6tVQe*BL)sQu0uqefbeplpHs-%^IJr(8^C7 z%Yg?jL$yLaiI(YfKJ?cFS(mIclZ~rVy84}A#316l>+xZm067M+y~f`E#PuS1nG=Cy zn#e<0sm_@3zKN+eMPbwVhE@e5Zr*N(KCo^e&bqd2$FSf4P>v_@ZLk*FDCk}jjs509 znQR5Rr27Ae`~_Qs*3N)X+_| z9em5dk=NqMgQ<@t5VA3cw@hJYB@&b5WTDuT0%g)Q7kX`&bY;;#Msm6f1ZN z#;4fpKUUt3j_2a)v(xb{LGN4cEm5EChOQ*3rChkBg9mq0*;yKWD1Ed8(M}Rk{>S{6 z?8(W=m_Np-t}eXr%_&ce;Kli^3jDqLw-3gNg`V^h{7izi(#UB~=vUkN=*td0=8Cow z3pBB%#r;50e8|XBb#Uhb3KitJJ`-QX)HEWal=@44nM&2dvzRDm!qgPJ;_W#r;_HtT#BDRxG} zFakmD{+HTfuNYj(Aa(4EKNc9{$hKJy3ZUYq;%rg|k{$2Ip4?uh&81A-S4L^k?yYAQ zl~)s>g zI~_baovDPnu*&*C#e20oh|MluQ^x%~&gkqUE3lv|OZ1c2lDI_rk5c3Xx!5s}Y!Vq? z^>Cc8v9%bK{bb}&7j$-VGyGhRFUR8GAKEki?4dkNj} z&*Yp>!w=1Vm~=4C%_uZouzp~UQhe-Gs*=$PB0*Ek&H!Jq4!wTl7!3MQb4j*$huQo> zRdU)q3mUmD-ozX$W00>(H&k!Z`8LVLfV1?p_lE!X4cvQ6hSB9-i3&Mr^FkJ~2(fG{ zCAP6})Qe;u7VBrXM80du8!f{ z+-QG2g{Y98+muS0tJj$QlU?Or9`?2MgQH=eFe&k{u)CNqY1chejEDxdbC0Jyp7QUM zYdl@j&Zg%gD&kWmfUVw%VVC$-z5%M^5ld7ae{8yO<2xf_W!N~`NP7NR_rr&s{j4ci z8yq4oN|8Nfg#Uq(k+DLCdn>5*|6SYV$b{x71E_l0ubinQH^T_#BRpI#_IlXJW-Rxtw*9?f;DOn zoWN_Cwt+P&>tMd-G%@mMb2hp*gci+l%;b$XU3mnpk;M^oFMV?Jv%GmS%bn+ZDQd@B z8;S|IjLLRi<-D7j9CEq;f@@Lyp}ed0L?>2+Ow#>ormzo~6IG?YXgmzp+{P9R!=0W=rcQKxr@5si@v7iO$(Z)9l z5wV*!y)VvOS!d=Lzi+>^D*m|_t%RXrd# z6^TU*(DDtMwzap5T`adfn}??jQ>cYBDKS8Dr9{H(dx-`Zq&uF8SADk8#PNI(>|tR+ z>-%+b3puugb9dR)aK@<9j!ynnaT|8$Y;b4I<#u)`UGov#{|J7h>c7q}=%i+|i0J8| zYH{_XgkDf4GtMIm;nqVlI?KhrxOqqQs=re~rJkWFn0CipTLDA2ZPQfauF_FCj5P>5 z_(P10?R(L)T^gE-MeU*wwuXaC!+D~cAMVRkXh@~cVU$*F{n;V{sFqEqzXHZExshVq zfmEjU?H1c62^vq!hJ3ZN>+{nD4Yuo1o>3fOHZDX>moIS}DHkWsC`{hJN4Rl;T3rPr z3xeSi3WrTu6iou+!PoP#(+6S@_TQXed>=j{6kkE^6Rbjzl$a+3PB7PN^t}vV*W<0M z-|U&w!xkpsNG7Yp**?i8pJNv58W`^mqd7l-Eb+Zuaj6%<|Kp2*3k4%KKQrgL0v1Gvy}tc%BcfUEA*`mPo@0J&1m0Y)&>&p{ z9iP6At)=MK%kjib&3caO{uIe>d$gIOV{cnBb=&jU_4@Upvbsj>AeM zKj@V;U$|T}2*UJ4xtk+my7lx#2Tx&cUizSG((OOr33L4_k(R9TFHTJS)!QcM=Dt3{ zeirPH0e%1EAQriBv9*9`+-WJ*cn;h8AxiFsEFg$(^>Mo%NwMhki=w6RcChG^DsCu` z*lw=^1reP;wvXK;zWTmzWsI!HXP%!Af5#4)HHB6Tko^L=`M!Q}lM!sCC1TT^N|%mM z4ZfLHvHU5GdjA_M)X_2}(>F3|X}sIBNdVk2Jc#RKBWdlUzd>8OVhw{@Ld^JoVs^G6 zN*vn)jsr0{*!TqG{EMb$vnTC7WPuvdZ1{HVPI?`9-Q#DSu6#P#_yW9`3>-u1Tb6-Rp4e-triT?^_5sGw%7h$20?4i5Q7n-7i$*+clG#+;@VF)G zQ5P{jrW{}(I=|FSxpFQORYWjRG`aW21XJI!c35o7OFZH(Nt9hu@ACS2eQ1Tcf|mLg zDwa_ifj0cj{E7SJS!BwJ0Hd>o+p&(%8&9(!1czIsVysN}fgM`sIX5SDzZY6Sr=;!Y z;Rb7XV8fb)86icswU4ik*c=U+g+(3cgEO6MQ)?g`K2cOw=hGKm9B5xBB^2?C=YIGL z1I&HdDUaL_Ch<_UJ`28|#77@jkO9b24N0v_y+NKwtyUvXx_)MC9z-wxqAX(AYt-z) z!|tb;3a|X!Kl;v?XlXdOQ0v$dplb=bL>&k+N8^LCO~oWsFPRAY$*IHHDgGF$QV`wb!OFE@pn30$g$n$7>u% z%WpYKO1Q#yF?b_AN%_IkG;t?EbavkFVjE97t6z`AbN+PF;>JgB81u$on>~h}EsnZ5 z7{;&5Q?jqCW&KJ=3@lUeEzBW0^E9wCvKVQj!Q^UfBnJAxtb-=cF-o~vuEE>F7lz;$ z3=a($BkGgf)Kl@o>I{A?sA>FgsM&XMq>hh07WxP*HCx)@Mf3bwTe`3>#*P|~h4l#N zs0y;hGqI1j%1ltqO~*e}Q}55lo1a~OJW7Mzk4&IFQ0AyhG+>123$Qa_g(qz=C9V=T zqz|#kbg>p}9Dg2df1Uut!hWE$M)#S(S zJq6MEpYKm%wey2IJ8zz0+F7O5G9c;+$=9~*gy2BgcN-pD=TFsHZuhXxH#|zs^1Ja9 zA-|}^>6gDf&ONre`4@RQP4kz4D5Fl~(I%$XCaKW`67_UoX|;maa>Te}qp$D?beQEZ zUhGMYF3_(LfLO8{wS8-0x=V0ZaN8^D`8aKI*kOkiUMGYmVqHS)>x)(elaWPT4a16# z=aRC=vOdo z|G5jD;=k$q^hV1F?V4Fzw(ma9N8cCVt$V1;*2K0dse~VuXlK{bE$Wpw;7c0u^z8c_|I6BV0^dxsv{3a{=wqm{^EHaQqEPr&nG4>7nrS$pBp=Pz~ z-r6f_pgmL)Og34cztd0_6j=Ne>dho!vFD1|1) z=5&~Yp}@F`^ne|*!Ke$ece#`Ig1*J>-KQewuO%rp3I({%y5uci}hsig|*eUjk^it);;PEbeE$#S(EvwkRa3v$sP0aGw=H^BH3 z3HiUUz!B#&i&tNc#W*6jZpZbhKFE^@sk%@m4Mk7&(+|%A0kGmHa!%BghDpDYOV}JH zgU4SL;paV2X zt!W33*8b-`w&X)lyqlt+PI~r-2%o!Q=RT0PnZBE3-=LPx5Ppupp?7!ig3w3x^?tTd z)>tR;x46{e2XLqJAF4+iFRvv%rt2r5JSxSr%H+6(le~+Ziob>mz>9A6vb`=$De)At zg_>*5<$WV0DPNG0goqT0kirw%ao9#hS3QX$eY%(wY3ozXA6?o5IMrwsdi2^mGw;nB zkgP-+YS;+U5ThGJ7QdD_TB(kByde;f6^&G77AIb3n^3vo>ZPxa^|*Ox$?Bm;wVyqz%!)*G51sy-R(O%YJV<$P^Uhb{P?_1Qa0AOh1Bol<;~N#c!TmW z92|jxk#cActo7}*FRarvH(3*0>SQQGYcmkRTd(qeTg=W7vFblSG?va#j)~qUD0#sa zBZirqk>owFH(ZcKVB3_C-&XoyK6Uh+i6)mc9}DBSb*nNzF-gJhh@Y7BQ@h)vJUcZi%l!yV z?_t{p&qx%lJsiaxu@~8^-!}QAg3;*P%ZmWj{5HUTJBGsR3r}c|Z={{5)5k zIEwV3qVN2^#?-=%)cPJ1H+p86(HiAh?@x9Js$@A%B-WTICs>~)JPD>HJS7)aOiRg^ zvy^p@%ExOP&CE(xk{@H>aqnJCy0Xt|?z6^}>K)9J4^Iya45E`BiDE{>0?x=tadp*Ti*G(Ki3Mspz(_;hYhY1CB(buyWAs)t}E+ ztJjK$(D2X3WslB0e}2q&fwrbi7!@`o^xh}T2e|IY|?UX`2~sMsgdBnlFX^ zdTp5Q$~DIFlH=.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 css` overflow-x: hidden; `; -const methodHeaderStyle = ({ colors }: Theme) => css` - margin-top: 10rem; - - font-size: 5rem; - color: ${colors.GRAY_500}; -`; - -const methodTextStyle = ({ colors }: Theme) => css` - font-size: 6rem; - line-height: 10rem; - color: ${colors.GRAY_500}; -`; - -const methodItemStyle = ({ flex }: Theme, isShowing: boolean, direction: 'left' | 'right') => css` - ${flex.row}; - - position: relative; - left: ${direction === 'left' ? '-100%' : '100%'}; - gap: 20rem; - - width: 70%; - height: 100%; - padding: 20rem; - - opacity: 0; - - @keyframes slideInFromLeft { - from { - opacity: 0; - left: -100%; - } - - to { - left: 0; - opacity: 1; - } - } - - @keyframes slideInFromRight { - from { - opacity: 0; - left: 100%; - } - - to { - left: 0; - opacity: 1; - } - } - - ${isShowing && - css` - animation-name: ${direction === 'left' ? 'slideInFromLeft' : 'slideInFromRight'}; - animation-duration: 0.7s; - animation-timing-function: ease-in-out; - animation-fill-mode: forwards; - `} -`; - -const imageStyle = css` - width: 120rem; - height: auto; -`; - -const refStyle = css` - height: 1rem; -`; - export { blackTextStyle, calendarStyle, @@ -271,14 +203,9 @@ export { firstSectionStyle, googleLoginButton, loginText, - imageStyle, introductionStyle, mainContentStyle, - methodHeaderStyle, - methodItemStyle, - methodTextStyle, pageStyle, - refStyle, secondItemStyle, secondSectionStyle, thirdItemStyle, diff --git a/frontend/src/pages/StartPage/StartPage.tsx b/frontend/src/pages/StartPage/StartPage.tsx index 49a41e18..10a46a01 100644 --- a/frontend/src/pages/StartPage/StartPage.tsx +++ b/frontend/src/pages/StartPage/StartPage.tsx @@ -1,9 +1,6 @@ import { useTheme } from '@emotion/react'; -import { useState } from 'react'; import { useQuery } from 'react-query'; -import useIntersect from '@/hooks/useIntersect'; - import Button from '@/components/@common/Button/Button'; import PageLayout from '@/components/@common/PageLayout/PageLayout'; import Footer from '@/components/Footer/Footer'; @@ -16,9 +13,6 @@ import loginApi from '@/api/login'; import { FcGoogle } from 'react-icons/fc'; -import howToUse1 from '../../assets/how_to_use_1.png'; -import howToUse2 from '../../assets/how_to_use_2.png'; -import howToUse3 from '../../assets/how_to_use_3.png'; import { blackTextStyle, calendarStyle, @@ -27,17 +21,11 @@ import { firstItemStyle, firstSectionStyle, googleLoginButton, - imageStyle, introductionStyle, loginText, mainContentStyle, - methodHeaderStyle, - methodItemStyle, - methodTextStyle, pageStyle, - refStyle, secondItemStyle, - secondSectionStyle, thirdItemStyle, whiteTextStyle, } from './StartPage.styles'; @@ -45,24 +33,6 @@ import { function StartPage() { const theme = useTheme(); - const [methodAnimation, setMethodAnimation] = useState([false, false, false]); - - const baseMethod = useIntersect(() => { - setMethodAnimation([false, false, false]); - }); - - const firstMethod = useIntersect(() => { - setMethodAnimation([true, false, false]); - }); - - const secondMethod = useIntersect(() => { - setMethodAnimation([true, true, false]); - }); - - const thirdMethod = useIntersect(() => { - setMethodAnimation([true, true, true]); - }); - const { error, refetch } = useQuery(CACHE_KEY.ENTER, loginApi.getUrl, { enabled: false, onSuccess: (data) => onSuccessGetLoginUrl(data), @@ -90,7 +60,6 @@ function StartPage() {
스터디 일정
동아리 일정

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%0Drj8v15zx@xG&-7-Pv1;~Qgte8>nW$BVrJ z0L{8%Lm`%MFTB@SJfe$(j%!%i6$_rjzSuUVbZ%+LnE($MnYvomFoo(4+b=J~4=9Z> zEm8~vFbFCm@s`e9$#$RA*B8u=`Ot7X+F4{s1^D2^6G&a=mq0id)P{Ym2E~OJIJ~Ye z3s#Fsa&%e}V`2cq{t76~3MN@hJ@BdSk)&iy8WfbYGIHZ4Jq$upX{nd53H{$O`4iAG zRq#+iw}VCw9oJXCck~wQo(!J(8Sz4}934*mv7Xj>)^LOIK(ao3?al2ABrG@(jBY&J zEDjuf5Y6at#y}zWT8Z>uItc9G-GsH0(BPTJ^fLcDug5WUw&4H;F?5|6Uuas=O9EI2>mI zOU=$^+X@f_nBq*qR$hj+_kuTPd~E~{IoI*LrMcaiReU~chBKPyem2n4#ke-_5fvr+ z{*xxnYR8I3ujV zX!pvF56_h|0lR*T-PxbTv>;%dp+|mA% z|MUN)*6ixsp}H=dw_K|5$J)#<@%S{gA+eh(-i)OB@mL3@Zu9=^KA7L{@vAz$E`J=alk9(l+8~IO2 zBKU^p_)YuMInMDqm7aWp!Wo@C{0xb@VjMG3y>G|3|4|H7wIFW>s}zr#RO7BmYy$hX z;_9srgHi!svTtMTRyJ+x1%2L&_$B_g%(Dk__56#yN+LZDkYbOp&gql(4z4HL+0Skr z_7t%i@hs1&=VI#eYG=3MCCy{T^H^ZRCh(l(`}GIE!r3iAYhGSJWA62nI~@92cUEY; zU?TGx?a~yrno@;geE%VDcBHlpO zj3Q|P=35`m7{41ow=A!70~C`U)p=3*_#oj*?2~?TedKlD1~9SXUavW56a-jbhCCNu zV!Jn6hOV8%;MH|?&NU2`vm6oYb@?Xt$7Zo+(>%TS?j8%w&guG@%kG9DrXBZ^WZ)(< zWP2fyeO3e)_Q<3n2l(V`tV>+mpH&j=j#q$u%&oK^CC@U?%L{R#?r-n`GkMYwAN`Nn zlo4;?d&C~>yVklr8{_10j}}fkd=B3u)*8R}E$}@0FMftX8GC`4w{kRMMemXLv;2d9 z_z(V?LnQ6lBaK&1oeZOs22Ly++K&-&FTj7cApW`)JMC%)W>mtNf|Ky6wJswL_D< zV$@(N-`ozzM$4OWg%TryL_!{7OF>YPFYqBA>Q$g#CUs`qmO+{fCQNFd0`Opwrp5xR z@}|nbUR9&zf>^jVw5H{XsokmFOMzxp#c`zqRx=x5T;!|_S?`4aJ_eDsG+NgRBJkjm ztik}B2cWf#jf1=6cx_XIIe?Zlm9%;8!vxo1&|Mkhc^bgRvy!y;)ChXZ{WKaUs#-U< zII$UF62c}M55vv?!+kGsH1(=I-~u@d4xj>Lwc_v|I`n-?IEDq*8_wc5x=h6~IJdm# zMH;?1U4Uyb)5LNlfPA=BP%M|T3IJJs3c&ZSp#eCb&mJ;%99^?I&xVuA)RTRzl>lOJ z8SsSzi`RWq{aEXhIMezk^0N`*{QnqBu z@Dx609~>i$&R7Su;>1dBRW)W!D0syl%r+%EQY0{X6;cCuGC`f@k|Rm>b2EFI39mSp zNx?PAcYy#7k`HUT$N7pEEc$dNsReZ{aHA6Sp>jVusPQZ$2z?%IX>2cR4%0OBUZw9n zxeD8*FUraKeCA%eN?R_OqsJ>#aR6IDq`zc6=Xu{dc+c;WkZn31&rD+7CyEssYJPAK3ZAwcDt>r!xgzOb^m)>tP3ptM<^#R^38SP7y7hx05c z9-bMtgn(AFytkD<=$Hx3v4-H^Y?*2Ud}qF+4}nYjDPVj z{zV0(Xp;lVr&hhs=h#Ze-Y0480QOP7?{jG!zA~Z!tBGkU(ev{geFVu^{F}Bo_TdO1 z189y(7uQrwE1zlDJKus<@qM(sxyP{>ID$Esf$fDKALTRnI?sarIkITSq>#@a0kjby zjsK#rJ_h$=g30scjB(b-!Uk*fi}y3v$>$SiV2j4@!Oj|=EuVS0ybhhhj=^vl$pie% z`0U4Pk82A}L*4^cOZ@rX!<^W|6gkkZu2$Q#YxfaP@^#ie{_yhvE`a0X06*Rfe;NO# z$P)hzsft*a_-}M9{_p?&|DgT5|L*^y5=(|4KFrG3KJIKdb(YI3XfpkxTYM zOwTj(`CgV9QqBOoye=2Zk*Hu`UJYd5Um4key?}ezqv~XFD>-MJ&mZmKXbUrUHmJi) z3xH|zOZE-+opTNQdjx&TkNFx$TQ>rO*(GQe{e@25;rg&KW6nSh@X7a=0JoPd_EZc? zRNl2~aO1#=BW|qX$Jx7{M#2eTY68d0{Nfis{GKC-B2dgMz`1u&o_>FxBfc-<30TBm zzJ4tN6~1!Jz4ielmTvBn=M^BYh(ePJ>3>AfxTvG@VF0l5s!P}C9oH5RDd%wvvy)hN z4H;BSLVO!9?7`@dITx~9(s3m>i~q;(x8`~ zLQZq+w%RwVnA->C28O}U^8yDOZG&=UcL@2sbV6lkO$&Ql>e@2iw~y<^5s?eytr$km zplsp%$w z?(%12L2d+u$Gr917cty;?s*2Vh{w-mFCjrl{J_*3qgh9L?!_*l3*xFS4g<%yP$P~DM<5b(sA}0N; z!Un?T=h2yv8T9U&0v&s6WJjTKj9`2QK}~`FX@iq8Gl#T_AC`w`Hs&K6NyFW4K7UKO zip56#W@UD+e(sQzNpn?BP}89|I{~#d z#q$qa)ofF*2HEAzcCYW6PCX~b-qo@OFopK8*qql4Kvle1oxHrV5kre8!JLP*>%y*u z6hxfYm%$v?dXDL`IfZ-cj`iZA!sr~a5rE^h7jHLLn3d4SX`)3shqw5iOSk&ky8`0h zshMPudj!TR**ZJh8wnzO#5UN`_?#~1*Zg0iL z{^a&B3a7b7E?jZ1@jK|5#rM&4=4@ra>;addyBrBW0d!X2a}eh0fa%^e8N2J%v)7Xu zyl?4aQq#YkE;^~JXK{jno&NnFexJjYw3jO9Vskj9yac63;FsUJ5lIgC``!=omXkI7!V&kxpN zBzY51cq?0Z^)OYgaRlJv^96*+j>efn`)kW2@0Q@&SZq}bdjx)4uA%m>KnmCs45kgx zCO-S$`FH;3=|BFvfB7$KB0Bbggcu10;*@bn9(HJqo5GgIU>Cr(@eIe$P?Q7EBO&Sg zMsddbFj#~kYn&B|i}81CJ4r$OVW112MUe$5DB=rj7H36k{PqHv$2|ak;-B%!#1OG| z<9on@Wqn2vC0=oD$I&%6`P>{1HC!`^7>N-KYxWIt5WE+kNs@#{=xFzlz92b<`8+1v z@%#XJH0fLe)8hLior<47<=%`v#h|ls0(bd{Vc(>kw5`L=@+{-M(}qu7dlB%r3edN% zy*vWsB;{#8JmVy;dDJqd>x1}i)HDSguEcrmlj4bMB9@B#4Uib%IQ|`fVj$B0_TT&a z?LYdD{!isw0gc-!_G-pEH|eo{{qogX?Eo^8uVQ~|0MMm;ar`~5hXH<^O`cU;11;s8 z5A5Rg+GP)}_A#4Vhly2^K#)=s9?*O3Ec?uVY)>mrZ&LxM$}>J{%>nU<%WEG{bg?ZY zR_J+%3T;0;?=hAko-ber>l3jI#GkNl0lcWdI%S-KP2#+1->R)TfQfex4v*OqvZtE2 z!E+>*i8wB<^ScDFXUCFr07WB|f%Oj0>#Sj|^74!hRAH zb1p~b|HvRFX^(g-;()l{w8hu1U(Y^4LS!t`#AoHX83vyNx-sMm+n47*I(5%hgDUZ_ zfP5pHAJA!>4Mfod@GW9P?lbMfy1jrhV=*pX;g7@SX1=|4NvnIZ(Oa$u1IV~;>?uAh zUIo&JeJRPxxr#2>%ysB)t>U(e)~t)-IQi=wO!l~!)^85PeE<4KoelUp6%_hlc8CP( zYU;7|2(B!b-nL#ugXi@za8zBaQ=PwfMZO6TlQu2#wIJBV^Lef_MG~Hiz)q1lCa>kE zZ#3xX=H(1$ocT#{X6%L8&ogXUkU8Rg83GR>CgpE@&-fvV|M47m1I_UskmF*n7Z7G& z#6FF+P6;8eK@MyGuIuwlBYrMS0Pt=EU(-(d-%(ilws>%J2UlDRvCr5scMKrpSlEtS zhv9t`S>in-Kcx7KEs4KtO*9-Wi9!bEV}Ii(8Q4aLQYRbjy$c|(p%=^fL%tjDO&rB} zjhLAE0OZj5oNm{8HhoX_Sx|uuV1MxMU=oTWzSi?-COaLrgVGX~e*H9c36OZj8m4^- ze=;z{LuN)fn@Eeu1=8ILxu%%ryM{rsR+5hB5)0TyB%dB0v=;kKKatHSmy;{1LW|m^TLj z+O{Y8E{5YpWvW*fJEIM>J)L_7yPwjiFugoJFeiIvHSSA-0U7ML8VMN6r1;Spp|uzn zObZ&DHYz9Kj6;v?mtVdNjGM~~z?W@CE5OIaEk&;GPF-$8z_qI?*XvNFlJ;_fRJ7ul zB@ox#wR=>3N{z7Vtqz(tyOM4ht@WFB(`#vS@!|jgsz&glg?}^+MFT*e%^dFRu*o1p zZk*#tLp?QVut{s{d>Zf{>ly2J*i_P?WR2LU7@7<`CV}iEus{9ua~{llY6opEY$t%# zc+Ys2VN}t$re&F6B@fZ_8NzBAbwx8M*m`T??CMcG!(eA{;6h|SG<*`6v7yvnyS0%S z`sU{VFr0d84^<;Is8M?WBS>Nu!7vArgUb|p0t|+=;K1{J-6}31=13Mu1~PUEV?|PH zE}{McB@KW^FsTH<5+a9&9xylv_7M)?R~hK{HuMPYNsa*?0BK18etdpkpYw1##%o(S z;{P~uf_m7M(+Lpg@GVJA|%%WTu0h;^3#AD+R*=Cvl#BRB_G*!DinmH}#E z*TO~>Q`(6Fwrp%ghIti}Jnix6{2ZCtWCSwHvde>f$wW$D8sGhmxlaOFYnu3D913YD4u7WC(b+oec0l#Vew0ELs0TC zc(%A;rxrBtMT6S-4fZwQWUOJ>gbZFz@S|o#*i1 zIq=4T4ioJG6;2Od5?gYua~MCHcJ1v}>;Zx-?uEY#6o0Qt^YMO2OaK%`a+GnD&wUmw z_a3N)OTq8{#b5l}dG7wzfB)aVu*^tq2(q!iBxoeovEPYJ*sl~SNK|88nYzxI0Yo9G z$F~sBlf=S&2)i;eIAFuXA3h&D8rKwd0Np}{r*Zv%`?r6a?+1VrzXuor7!6Pw*B9#( zz6xIo`xW4=*o964Miwo@WdMCRpYeWx!xUcuUgyDP!vd!W8Gq+zMmF(lT&7|Ju>!UQ zKNxEo_e*lhz>#>5cz=r2+zWmUjYgyy;&cD>Pye(4Y4!v~kobB00lx#QJl247MjS?$ z05JyV13wV!fKBIK;{5|C0AOYxay?^wNg^HB7yE+(G=Ld#SbPre&)JRNp^Y;321D@T zKH|UstN-=?rv3fD|G%rfMxp2T_T}2EZyB4!zdw6#G8nTLpg-K5vlKuN@W*==BhG#y ze*RDXBWb?j1U%?`y}3RO{bY8Ii&eiJkJe)!c7 zxsUKIDq5&JF!$CxS7+w_jDg}A&$a{X;fyP6A0Y{-h}erE0bH^+#H|re1Ot~*}N$U$77q+5O>irj#|bW(B# z;`3uIvB%ht@m{bUBEOjUvTjwpik~GWq^JWKCEsUL@dFU9&Y$O2a2`gTOTe0V^6`=lg&m+za5&n4iSYhAoIXX;hAFhQyya9T>R)#hH-{|{875_W0&T|`7?G0 z>8$T;%PyA5L3)B`1dywIv_IeQtr=f1$fB|9pG3u>=1+K38@1;t zM277oCI`fWKsB-@BjyY{MxhtYVHodWrzmtPUOQ{gBXyS@bB@dPY!nj&VFO?^VUcD!@b>n|bF=`rh##6wYK%Z-qhSzXJrn z^v)z=CdFhQ_Tz7L&KYbs>I9Q3;0NzD72TKglylE{7P0Mah$ zbA+W zzstjdmSAclCu2zqq7DC1!Jwv5&K=x6rC;|o+dMACyo!w>MvTBiO+|mV{-oMLVc(L3 zv$(X*GnxId_9^gGYvX-VI$__-&~@2`cLxrg#l(X=%yO0ky;Q4qI<~0~GeFaY&1p-R zU*|JumEzD-l^WY#*--I{XMolwn$Z~qItjsWP~C~j4d6JNUqFli3O)}YJ2rVZ9fD|8 zv8I<~En6cDLN%{J7+5ND1cYfAw)Lgj0@x9Mhf$;F)!Rp7-NONd6FOZj&THO}ntV%Z z${0Y7Ixn-=KLKSkf8QNokB67%HwGmHDhfVjcwyG2R%axpd!PQEOuFZD_^|UEHT_7A zzBjXo;Yu)_nS0pD_);2Pk~CbO6sBz#~B!?;ThN zTMau9W0uM;%mBcS5da_vAh;&+(?FFz1c-Aswa!*bFxqx-o~By6Jb$eZemrff=wt{7 zESA%wBv7jR;p}ZI>|2aC>zQmMcPyx#XJlchQp(Q*00Csq4yxGesWoqhMS(2s@9nK5 zB48UbAsdi|iY|7Dpaa%gG8n)17Al+%XFOi8&v1M=BS5ZjRDbyWAKLdnC@@ThhyjlH z8nyvj7a%%}IY3c>*(g~4>7V^+p1oq*mtL5HK%Vz*`K58s$DX4?PRle-S1hYH2U}N5 z(abJJJP(`de`8fda>U0{;H;m{vgT;AY-h&}+@oRUpl>hL(MX*gmHL^bd7n{%jXmq3HHWd6ebt;;OC!z%zZ&14~r+;7r+0$&Is|;pa1!vC$sK%zx&%-+fjOffN(qu zBM65?(P;N#{Qj113`lCNUvVj{nBBjEPY^g8*^@ zgo?k%J;v|;dr!dl@BO{MS3XY+Bqzvz{<;2}$S_C`MGhgBYS)7fW>I|o`)8~bq!f`LWnl!zEES{t}eX^H3l7>kVYzTbZ@i3pxA`$YcR5=S0Z zMIOSL4A44!IyN4Aq1fzGX~UvLPtrZcAE_sNy;ebxA{ut?2z}t0`4d}7&BY#g_}(g3 zc9EL1n{x@=jgdoc?~h`r(LKYt#4m|OHD#<8u}{R!3_j)@B?xZYLve#=CRW^6$yp4o z?Z^uPs(p0_Yp%_3cIe6gNKDd+nR5k@&r=nnutj5$2r|PmcR(BPGP{nVxMZ@^$V|g- zrY&q!bw%>t855W|lev$Xenl=A0Os^tmrH?JX=_`1=?bVb@JWUCu}dLAxTd&Ww*9D_ zGK&)iz~W3KF~I(cvmE>Si*xz3R|&=`r!V_$+m{5AN4EjQQ^XI~OCM-WTqHXu9DxEr z7Shk7{L`D`a!4Pa%}`HJIezX#1@_VZ0E{kkFKo24OMH-DLKeeMd@(Qt0FKRs-Iztk z<0!c~NKx3`jIVZevl4S882$YD4+t3EySySd4zL%Hjt*w!p&uDr5u2rNow^Jog-LP+ z_UA9zx!aNq6E-awo6eJv6(ml0Vb@=wN1o0W}-Bc0a7~D&y)F0@$FcjU%AVo3S^dk8HsHbXE$LY@%z;+ zX#0kbd`~9-Tv!RV43a?y{PlUU_b%7Y@Z8p3BmlUktD9%cJu~zWoFcv-pF<~z>`9jZ zBzM}@KC|dMOK`@65}3TSb?ul8GNRyl_@&)L0wrOf`6B_!zWJFDC-Yv&Nvbkg!?A8#sBg9ygxQJu7P3(oi?1AA}h8&aFTeI z>mKK?5T8WR=TRLjmZAFx|L`BC#&H;Q9iWRBK&_qo=0Ob+e8xq@&2N3U6S4{U^GmN{ z()KRE>XJ#O9-M|$gprGCL%h`T;x;%3_4&I|)f$<=XV=4#V-q-?LBrH_SD5h1V58Vv zSv@uI!+w3qglTu8X0d)Y;5;*^ONUa7$S%WhTC)ZQ8Py1Z2Ks?{7|fL5#aq`J zyLu9Gyz+TRF!phG1*B`EV3^je>-@dl`e!ARbm{`zY&az_OH(^uuSDdgGgyV7`<7Zc z)=+hIrr)Ow+GG$Q+w@zPrXXp`AoJMk^Hr^KrQ09|&mt;=qYhp?SoTjiKQxR<)H7(k z%7DIC+XO7@E|`z=iR&>Nxgdb%X&oaP0l=mmhl=Rr>!#QtCiGH) zB+f90kH}lf^Gf4Q&Hxh}X#69t$ePu$$&;i38Y*FC~}}!Ph5AxAYdG(MH(-+it&rT=j~+A=j9ssnDm)4d-P5;@NxufqZkYs)Yp%VaZmd81vp;YD z*1z?4^XFi&bUB?~lm~EyO-A5HfXY2^R>qUNY>1Tpo9g=T-Aa4N&yY^nPo0l$Kl3iur z6p1*$oPP)s*kO?v-fFMs+4vA(1Y?)BdzBgBS}Lg?Q{$+GpF) zWMQ_m&PEg^h>bs+jmw&kfXWqOSrh2^a4bz*yYjgX1`@mEOqzAIw&q^o*XA7TGzd+* zeA=5~_}R^SD}k~@vNOxv2bd~8?=c@k@Q4?X=3+g^{C6afL6AV3`0jP>F~;;_huhf) zY07U6gVqNkP300^>#6-QaUk|)?5oT-YN(|^xx?g$$UQ#!`j9Xqj^MiZlZCH$6+q2{ z)!H~0TrOP`me_>cHKnZvo1wUcIO$sc6MsTnGx~|#OU`SVQ2`5=xhEqYV~>>YpSI8) z8JVNC-d*}#>X2_mE}16D_J9+RZ_00~{~a&)I)zrg^OA-;*tg%nH0Ro7l&~J{Y<7L zQg+e-1;XZhQ}e+T+iv?-*9hp0w35z8K%=RgH0``SiJZ&u&c6O+`}%vO3FUw1_m<9nne5n@ zQj09dybMWD>jR!<13xge((qoTUD_O&mGP^MHCMaBG_~QqmY2f>Fo@AO+gSkMXuOVN zub&?s8P9dOTneY7qvi7^c@tvA7w!aheg5TGp2JAjnl>tE2|Ql7PXwmd38m z-dj3Nj0kH8z&A3N04lqjNuTgZ+BFW|x?DOlwL8dO!S@Evzu#TW&3iwNZ|-01Y?V}< zXE6=(IUZd+9!CbdVYyshzz5hKXn`ky>7vzG@x!?1UUOCOZ0)6Lm;vb42YG5T^I7dV z8L+LR2AIwhvNz~?!`jn6cEILmmCx~du{=#5a23ymXO=_T94HV7vKMd!)%KUeKp#$C z<;3YENe33inRzL@#(UsczD-Iacp9Bt?yYrmus9bppMu)u0y|w`^T-BFKQJpOO2K{G z4JzDmR?(W*b6bl^%C!Ob(;;y;V|OxeuA-NMk-G!>5wHoL_sNA&hNR^4v%@}9yK8qs zeW?zbDr_xiNU8V%+g`z_1}jnY_%JY;8iSmb*==(M;+y@VwDitmmf_m~x7o8Kx6{-* z^DS8`ohh3WotLG8+nTUGb#}=y0Hc{`9%7+qaFrmlw0h&|q{L4KrAzs|9z z_8<~Ft|VYBIhHY&Ai?J>GclS(O4o4~7(%kEdl$1$cD4`M3NYgD{@wpc`?Ej)I~A}3 z7=8=t0xpcLZhUs00m}_>o=J+hcCL%PKA!U)Cmi)zBmpCHg5M{w#Bal#7=drDgG76L zFM?NW3ch1}FFu2x8M}chd0t*M;<$c-Yy2BP8#WzqlzX10S0)C<5?E8bCu@KXGoQ~h zi`V#^$3%ZjBCusVpKp^`z!lhUT<7=<#{wI6mFr+n@Oiv1zsDMmEatI*JOXK#!MBb; z2*n2yJl2Z)=h?9SW8s188TT@4!28F~2FQ_Gd(W8afA&B9i}vS#=kFNkzqWk`5brX3 z{k~V9b%w2~BTmo!G;pb(4ffLamd`5t5#1VZ{dw+Nmnd1y0&cAjsnPm&mK=6~^-Vd1 z*^})DzpI=FDKJm49}?8odR%v&olud5lJ}2_wYXNU6LNgkNI3vf zoc529XjIZ`1_38XyawUBCUfrn=VxboyVpEG*-X~Gm(uTAPk;7(FmNmfeEHqerMssz^s%t z^IEgtDFX4Va(`YKFzPA|0_8>B3@Nwjut; zMzKb+dCkvsaq+nW=oEa@jzWSshoR2bt|dzBwT*uNeco>)pMU(bw)ot zTPUO=B*UmP8Fsgp@Aq(LLSi(2 z@7(i_wXnrtN%+1t#3Um&$7kWglcAjP?^`2x=d&x`T>C63Y%kC7<-UhAf;}ByT*p{! zkPb( zUS*Aqfibw`wAiJLq5$PBS(v(WA<#^sC+WIpX(Y zBdA@uI~@!z52b?EL!jEFHl_l%+>A?aD~M}q(_|;H48|M>rGRp?PE>+=4xDEvkOXA_ z;k+_fcyv&GcG3;FejEY-_@06q$y-RYe0tf8I@}SA2@u99P*DVg!Kg6I4fYz0Ym(Wh zvcU*xcD|bfgxp{T`~*7njmR^lf=!?^vVabMFGc$ucqQnw_XKKe{o$GB;e?rMHftUT zSIQa4kk0Bj8#>aHS#pO5E@+^WnJs3(_9R(BR9vSRnqpqaaP`3LJljjc1d~EC0jgd` zxToP9)(f<8?UhlnIHK)-Gz=JrNM0D=r2Qw5oCBPC$8Hs5Ku7`c`zuQrV0QxSonP5c z1w0Nx3*gDAGT=+X8G&vkEH;#POSBXN(#ZpfHO7?BFmYre`#C|E+hrF(( z?2jfFdpPDUmI*=S$b_Rng45Oe?;b?9YuKg>I0?9U-NyST;W!<*!q6+(1_zX}k7B=O zd)`H?AvzTJosroPFPK}&Zt&M)?ccp;H~Wjlz~PBK;>@&HJB`gvaCGaevkZ>T_R<=j zMaE4G0HjJrFy`t6DLxOqbA}M{nOVW!`;_uAxvM1D`&WA1vVn(nYW1^ACgxO`u^ae2 z5>N^?VV_5x^GxQ_wrN6p1`B(iDXwB$rdsG0pE9@y*;&^CY zdSMXRmu*`Mw85`WX0!8N1eLO_w1q3Fm?{CCibW8zM9Jcwm+F4V84#(Z{b&sJ$^yo5 zHng6R%*vLsRY_$0eAvxch$sLsu^X^0?lFFb7UTzkVXy}^7pyYh5FSqx^enj*PG z=XzBu7P40itP!}XcFkYDd{u$*Y&GPTJq2L4u0GrIY%u7^=M=yH^UuHJbtbDe zK4aSoC{BI+l%V@6o7ChFv19T5kJM-VQeTVvO?kSUZSZ}zOVdG zKUa6hLXzC30%sq06^|)~$Lr^xe=47L4l#YsGcnuwku19{uOYv7c19C1BmVm|sF1A1 zhmuUR?Exb4zUH~~RD02ED+Xsgk{|`m)ASPmWgKCDFF;!HJmC4|+BIL}^X_I718gyv zjiM?90lizWiv0Q9mM%je7W!_M_ffuvd*QiMk=X#`W&j@h9`O_M9@u?~NsvF3 zw^+M+-`rW-Wvx`8?GhdQy9!FqH|5`~;kyX@Y6g9grJ$e;kOQfLcqR+!FS)H^Gl)9{ zz#pA0muK|<38VpZs#OEwJswO*CHKw zfU>D(ZH##pKFIY=U`Cd6){5Q1T(O1?QpJfL`Xq3Gs*3z)ISC%RF${x#n z$Aur(7oT{XomiDc)=Tfu7`uSD*CI>qefS)57350{j0bPxi~^W)o)ue*q>;JvkiTtU z4`v7p{GN)qFczVeXUOKYsr#>E-(Nc4eEfVa6>Cty8o}MTHae{)+qBaR=y{r6d^ztG z&p;gxBE@kp+|PJU*pK?1YheQTtOBNe#zzJ-=al#1tn?yqdjYkvudF$HWEpG_`6}<>u0;Aru?HUH}9-4=8i6H0YMVN5sb*H=T$mX@3}!c7y+X}m2`kiCFJwm zHzpu9VD3493K1Y?`@%C(h|h@0m9!YnvleqYV$t{oQh^t3eR~fcLSlhnI9}+e0FGj)&%Ie!vrnas{ifhfdnwn# ztxY*pAT+?973)YUN(a+m@6W&d-2U`W{?yv3T?dSTd|^mC8|YHtz{Lq~F|@LUTvb;E zS)NGRs4{jMzxHLX*u?uHi!fGv2xoM+U@PkCK$$ng$4)?YBdFYcm*x z!@E0y#p%ah3&zIuN%w%>IQ#!*MsetB0^ppjw-QDIcd>z?1q{GY;MZ2cA%-Wl7Ux&p zNp#}s>=67NN435(2!t~RTP~fwf$irnXiZ{oH!&qTd&P;wP`Ty5*s5f7S_?|f>}hxd z>bV=K{I6~VIrMVvu0njJ_5Kp8Ryv2UVXPVJ-@KMPf2*{g5f z)9ElSm(l>|d{7;~VEiLMzJK+8=)qhDerBOvef{^;X_Hfo;?MPROV-HhBG32U1Aw1m z$6np;Hc(#|}?2E;s_xWs{TWp*pZaSNcGoA&PoBUUTT#q;O+I~VLwiiS!!dVDd;OFUP+g-1zkz}8Ct5W6+9j1!dE_H1(xdmn9zv#a#U z)=9s8v2SR-PEq{Irr#89!Uw?H@BSU`G`5wRSm=XL!} z0WtA&6sOdMaWmliXrmr#d`^I4d`3J668lQ>mZjT6`vCOhNIJ5e_@}pOk5;$G&w0O+ zkl9nB<$4txl|i6kQ|hyAPf}m+UGeGf-v3bX2b$XqD-&STidh)H7I6pBZEhArcsdI?ef|UIRQ7hOVl|*Z#xt#%M4|y??;Z-t ze1~{1o@>t6*mk#P3xs|=UTqh|hp#Sv#DGB9+*t%Sz!k&P$OF*jl@C|!h@Z=0m~XvH ziWuzULt6gC7`MSk2KXGzIm3=coDgGdt0Bk*q|9?;Aak5?hO9v_h%@s255G$LIu@TU zX5@-3{#G%}HD#gBifdmJ;cJgEBL0Z|0BC6&2-8mlpL@HW(lw_bOR^lF^;p9$z2|=H zL%7Bcrn8Fl%-LYTcwBSD5$o0q!HfO!V}A&s*pdBk{Nb38!B7k*(=h5i3~AY#K4{cK zf^N)85uhr4{IxWnW=rN}2|%7low*O^qm`q%IG1Ozij|TPS)bRMdf)4#jh#MY zS7V+Hvo+%t&nxo8+|v(9ux;~dGFK~*3UDoaD-s_0{_|@GEYfJmP#z{}uwPEwXe2zA zq3hvhKdzYnp6B6my;MGbo*lqOzdq*>oMJ(4>ss;^-^!F^mzN9N+S8WNiqAgJ7oawN zPA&UO+E&0Wo;kTn*c3Fy84?&jOYBFS5%0;Fr0#U4EY$6U)Jax(6qW}(W1PLN-g>i(!Zv6Px zp__HgIpeu!XP-fQ&KxZCjL&8Pv#Y_bl>InILd94ZIbOPHI-%_F@3}~LnTI0JqlU$e zKrd(CJO}6IniGYOV{b0!Qly#eEdTN$dv$wz0q(K`)$!j_fVbiYurvxnah~}-eSK;S zd6WFYT5BFJY**M!0f!$M0|G2cNBC>LKbRMi5_(o5FC=Nw@=%GYIB2=D#EHtdl25_1tQ3w4v`)-3<%KmAYtIt`+e6jemkhLsmSIFh`{`^u;WCq=-NFq-6Y(&nE*42PHWaZXTv*=2UDY zrhwh0c@@9^{!#C>+wrmH7_qonC8}LdI+@dpTGHe9-_7E=3{E?p)xc^fpvQ+zgBzv- z;b4oxED*DupuK}NU9U~aC5c&FSM2*>@6^BzxAqZySLN{ey`P>y35Oi81S1%qANobG z7gSB3D{vZzVvP)15-$QHk^&4j;^_<#yW23UShSasWhjQgg`ekppuiVGYEQr(IOS=y$|ro#L`Yf?Qm~(4YBrd2I5*mTfGJX9!Irc{QLyScQXZO zRRcItf+Z%FG~T7p8iCJZKpH*KcrNkvcm~sG^q>EYU{QKs4;SC0;!B$3QmQj=QygW6#EWFjVMUhJXgaT`g15QW8S%vkvX~X8pIWfwfv{h5Z&^ zCjmIznkEfjhC9tUQ96CsReL^)An`kK-oDkgC4oZ2Kc4TyiB1B1ADXBf=aF{9*#)UD zIi;KS*4|dA0DpB0T~Dea+5F{M7|TH@0Nv-|7UMp|hxcKS1(G2eKyqY4380#t0S4?; z*t+Bz<(^pPVyY3GlPFdp%Qia)yTo9a+nbq9Vw-+q@an*y(57jDhJ!x2UT0v)LEZ#zpz_1jO7VZynm^+PQQGC zNhbDhCX0?4NcJ@-4sCwL9l7`I6UM;qV%FK0YSTN50i6XM4+{*&B6!S=d-ejO zI=wXX3XNM$X1+L?{>TAi*7whS@vu~i*q72WZ?%VFAIAPY-942FrCausN&9j4pryBJ zGai`=UEA>LU{ipI3McBO5rC%73}PjLhVP|?OJIvXi?4;hqsU6qEIrt*+Dn(Yw~pr! z)d2f84Q;yT^OZal!=PYoqDTI{2NY_^@_X*L1mesD>@1L@3KoC}qrM_BQ+2={eTD$B z!)pETc{n4n{=~d6#fT*VD&u-Iss413FIys@;luiXVoEVfpt&0>u2JW9PMy+R1KaRV zE+(VD3xFXBrnQD6W6K!|6@wjfVC->JVQclEY7z^bX?4=5yF&gU&Tp%I(`t{0%^TTf zasAo$ess2IOF-r=ZCDl%-n!N^0I!&y>J(%MA-M#dcd>tRsPgAel}!Hr55LcUhdqz| z8uo(0LnEV*=QEyl>|}g?_!T8v?~S4ff8uLQv!C%zr;3Y3!rA1%)ZwFqF5Z*Qpw$vS ze|UnH@qK`Hh6~H*Yfb4I$vt`;?gkRO`?Aaiq~s4^6+MPoCop{h)`&k+kK3RYg|82;(ZHwA8S9Qrt-}%k z>?Ne5vfyBQuTHVE3V(484CEB~s3-ifFCRYB6i_n0aMwVrfnp3xpZQt zFTxtac2OV%D5H?6>pvP@Gw}?p@_i+3l?-Rps$sgL;HSFWLgjPGi682_{lf%EGt(}0*^H0R) zKR-WHgW5mk`H1(5fuvtO9|zzk&Ucaxy#9*|3-hF5cd`By`4ia7A%Uy*=l9>aGf}f( zV(owa>F4}=TvP1TuhnVPi~H%9Jj6Wqss_gXkk@uP|0E{ZOoG9FXb|T0#SY`UVWTsv zYGhaQeVD>%0Y`itxlY)qxOQd_@T^A=y(xHX5v z`F_9-!v4h9<+oJWELL}GWfS)yXAi#-FN*&Z(a#|cRzY48ZDW^5JX35Axv28}E~}X! z;L!^hlXDN@b(bP5a25m}c0ZfBwTTdPmc`FSjvm(>{~dLj6)W6o?GMY|kt;B>LOw!R zRs22P3;9mY2>H6e?W624OlfR9I0-Q(7#Q(9cm{D-{9Z{wPQuh6g`uqQEeH|dW-<2^iTy}%duF9+Z&Vz(AwEXEoC{@ z{nd#3^Q&sxR7_gE0q@qn72D}*r}c1~oDI}64*wtO7 znno2`>=XrjN-o|Da2wZ1E%$v`AbT2i)s!t~w@O;RvvfgB`rfzI&Z8A$nyR4M1lD{X zf`5j8Og?XM$A>GU@qY1K0i+2INjONlvrRaM=X@?B!dfM;VrO)*9KzIwwtQE$Npcw^ zl!@bAOv-qTnm4f@bAXel%Eq;ZgUA7IRM3vY6+eNH=tGXJAje?jRqTNQ^O4R?`(~+q zE=Q`f9<~Jrbo_37K2?1M0h0rukKBh*4Ee+JdB(n$Zs5fM&wBmty-I|#ql(1e!H_lIY2$N(5xST%S`;KF+bhH|u@Jdqw7r~#XD@)~y1J2qgKKC+Ej zOl^VZUF$9fMVf7qt(<1^aCQJK5+rp}7Dcy68hR`z(?yFbF--)>zmO>En(iYj3n5&*VFdDxm{8eP?f8Vui@{Jw2fXi0xC zX__{G{nrGodkfdKfVs7Rx$M?)A@L8t|3hQA8*G4&lpT1_=lq?YPgUg49<+w-{MjrX z+Tm_yTE;%@LC4*}(%##pW--W^(EXDKTVkK$Il{z>VSfo~EQ{F_W^^>I`DEWE@KN$M z#3ZtvkF)~-+4zjrtmq6F&FEslW)_2=7xUvEeoP>`e5=n~fF7|X0JAW0CP~ z?{d`?_IS_O({Ue*+nALYtW^Yx#86sqJ!p@l1`W}8*71yf`swF77gx9VQY641#GYp0 zSL)<_ymzL80n6A=0eZzXhrQG5Y+YVNT|}ID5+W527-{&a$6Znf2m0i1);w%Z zVpPzea7D7leMCGE3{e&JdvON|rtF-G{p2(HVa;@|3lJpk9VwyBc3mvW_XJBR+^EQT z*KoAwe}8h~ox^@-4cdzH8SjY}X!ybST^O+OzQ6z7?_5y(l=qxn8gDnBue}N~6mH|X ze|W}Pm&;N>czkA@S%Iide7!fLn~bv^@1JCYt+#n6fa4e$uq?rh_xB=Xs3#hG>L; zgSnUTafhBm4L6;;Js{k(_xoqzLh{&n87$Ran}V-1T}0jSH_ z>6}@ThL&L$ivSj}0d%}mFrNJj$N!%six6!N-^ z-BiT+kas?Yj$sH|NLc|eCKhn<&)-$V{LOEFTh|Q;6K5{$19AWA!dBR-%*o8m4jdte zA%|bTX{Hin6p_-_S~tzMxE334eP|X4Kn}Tu81en*M96qlkxE6EYk{+OGsW4*;d3$u z^?rc0*@+7u6`uk+r*xDF48(lIvlP}~bSCiseCEIZ{qL$%C;i6rnFnACdo+h-&WnA` zz2<@t=aHW!DE{1s;b)g2HjK}T_j@?6qcBfl0tTdPp6srM$i0;=F+-%|8X_l(a}}?M z^N9aBs}!0pCTD&2z~GP+GPix?yi{&*}=F;W&I13TCqY+PIzROb7dXL?^GcE$m^r? z_G*b!aD;JSCIwxX%Y_e~y&EZsJkd=7;ik1Z*Q104MG)d$;%W*45E1zK5%eC<0yyJ& zo-cqjILXNV&tmC${t-cgv&_#EuX7y~LFv$d{FgFzmXZQb$n*G5{>eZ2Ybq)q){|2} zHp(q@w(TW@GC78;7YA)isvHDz7=H%tOx6qo07+v{g>9I+DQ#oxtJ+x-m}DUH{!xiO zEg-kjuRTkXB{!}klo6z?wk4Sw1t$(DF@&@?oc>)lJdDh2AI+|asTPGwPE{@<06zOQ z5-8_^@Gw1ThfPjqEV@6vws#+cUUB>^j4&O{)9e{-z)xnXuyGxN)`80;e5TcQHKW)< z-1Dgaayr4jwTFXV0q9*+K7)*9sUpF*TDl0;i!a=&Dwx_itE!vv{irg(C9BqYtxXf@ zr}ZMhB5H5%1FWgGJPaZ-(XSdoi+ zjX=NIKx13@>_|4x0i?}VVh3$GpzgvHQ{P_`s}p7vc7f-?v!a?C=Z|wSc7I@pyJNL( zrkRD3i#Ud0tWVe`pqstt;*<(B{M=ym6Ex-!n3%-;=xut*`uce9cHJx$Id_2?nq2He z^U|IDF9~qDT?Q8DB!(D+aPICr5j$#azj`J*O7dKxZVY*wGY~y?bmb@ej=30<>1@LR zM{r<(71oG42(~u1_NB(7zt>r+ll8afI_I&6@_r(*0kAb=F^dVd1*^{=!yPr9c+gU` zZ3^Hve|Kud?8~l;x3>bjDD=RN{KeIU0yE zHRO|;yP=riepo{@J7CPVNOp$Nka@V@O9q68?VtQuELg-wI%g_|Ws$*to(_#>&~#al z1Nc&mVG6BEs7@CzEHTphFxv#;lil5Q?X&AE7&~r>T&YGBXcB+9>D|}I@|zE=r$o>rLd5R)X@iwFnvAF=od5Kc#gC$!wL$21%QQ5;Tg!r zdRVTtHc3|aZvrL<%&9J45_=KZ5rX@aQE|I&{5{qo;sEwKHi1s6c;&tBM-_+yc-W7l zk}28uGeP@LHIt&bRs7PB{u%dsf)hO8Ee0CtRo4cg)Ad|rSIR+r9T7-827GJH8oN$R`3H>sEaqT4$fZ!Bw}dt-7yXJJAo3jAd@9_sB3`t zeptq4GI$#~L&jOw(Z74JI{C&YGqKqNbOmJG`u3?nXaSx#$LE)ZCbk%-%DDs@YoBi( zP|e&Q02vD2k4I!;gVFvKlXtf7kxi<2+Od9T4J$Te4hsMeIW@9K$VI^R zPc8UutL_dMrNm7tIQD|darAjIaj(p?$@6~p;luAqz?d4Ha*!H04?H)>GhtI1@TW7| zht!L}_EzK{3h#&G5ZNT@#i!4A{w^t=cayTZ_Hvx70{6U5xyR43^RLf2AdXQ%wac@h zJ6i;iL!H@lsKhgm=W{hVaWw%8@RMf&_!iwSbPmq8zkpit{m4;7oa)c8&g5NR$6ihI zTryAV$~=TuDUqAy7l_9%F8ZH76Y(r5rqTVOm}#oP-`EjyT-f;WOan}1FRf$o_vrfb zt+f|$H)5i&UC0-tU)Z)P6v;-2C>9SW$zJbYD^E`xp*6?cW0)Dg#%m+j2go|7p zAMthKG>XZiU+0YSeXfLO>~BjXo5Y9z7X^U29aa_Fc{%yTd7aGGAa z!g+nRpQ4CE0UX&jY{Xb7;jCf1^=#z#v*W0+nyz+`#g_n%S@61v)(YT&8QC|Dz`C=Y zZb_UB04gEQ0cvRS8UV!LacD+>1-2B^`_R|}NUhfOlDyc9P27X&qXs7AgIW;Wmf?-f zs#c?;s@YWQ7*wEEh{eO5UbRQGTHPS!CONx!z8t#+wq5lkPzGG?T1BtJ7>+{#3!q%y ztK0i=sC*7!FKlCEF9J04ywKjr4o%`qYc15Ask= zt%gM5*w8d$s&gEM9GYR!UOG8@gW-z?r-u97>v{okZt2sgX z=l9<;!Ml3s)7fB?F&Q<1qKgjG-EH5gB{?y`(F6BvL~hq>3E1yue)W4(pvIY!K@o$Q zz$*>nRa(OjCs+XLa=u5sKg^rm!w|0q9Q0heZV!VRx4jRZAPLDpFM_7Gu6L<5om_Rh z)eywl8m<=yg(NWoMUG;%Wr1aC#k5|X4Bx6P@-ehQtW^Qbz};XcK!Y?A#q_3|fT3)F z`~b9o)T+dvHEe74`Nt-7!`Jm4WpG=v9$>&rJNiw7K`j`iVxDQZ;gaAf#VOeHXzc>x>_e4U&UrTbPwcXbnM1jsveK{hL|5!GI34YS zRyKSra&4|0v)*IiNnzkUlODwPWg|7gdrFP`HrOn@@V8Bn2H@W9O(rDG`&x{}y9cGP zehlq74GuI1t0b4Xj}{G-TFolTWW z$ISNeS;%as?atJLDws6}74I&(5m@tC@%!&)W-G~E4AP24s(@`T?QaU@6qc%kWfzDH zunT+UYCHeMEZ9r7xb{Fc&YL=BTznk@#M|37*X;eH@3r0%-wJFZCh4)5I;Ut6PF6)z zR|u`$$g;pL#?Ne{M)$GTutgQ68NAhMxS@1@dyBt7-L7l*u%6!a+9i%s-_`A$fEA02 zpsRtOu_$x7UJN)MbxzStqh(Th*^>dfJ_HCk2(pzbber9ak0mCkmQ33n*#Nq3&PL}F z@r_~)K)8KK82XSZVjtoL03V9W#E}$xI@58fYgOW{sJaj3nyO%x?U%FUZ$3WmE^;ge zT_zVYi?w=VE*~&b18NuU(FmqNhEMpia?=U{G4HP9azID;*80<8G z4zVnx4?w&G*Q=$vgI^S+lKE%*M$V=q4zo~ z&d4r3ljm5!lnng1H`v(t97sgn?(Y>_@eCpQWM}w1bDLMERfo+=*?;(>L2$VrOCo{1^75wb!nL zcB;GUJ*EA&@?{xooc*33_ufewFXU?2FTydvwl0qGyY%1bd1R;0jF|7exGtYN44kgw z?NLR8^L!l=x9^?RK2NU%F77Aa%K0QV9xxdEjQA(JFZ*7*lYA}dNQ#uP$QcWm zd~l(fAclrPgSd$Z03soZ3qQL?2h5B4P_iOBF*P*rlq9Z|Y?D+xtcg5LXuO;ibRB1B z*=cjcqV>hw4U}wVv(ffRa7dM==q6rZK+)?(NsDn@GccUEG#r_L4Gzy-V5;wR78&L`R}(W4K9FaceFBMOxK<5JemVL z90ZP98*` zwi+N&vieLgHUX@6C*%M}y@Dhs$rMwZtE%af1CX^027oEdh8%2WM{x1f>ulY+eP7Hq zfb1tUT+f1`UowJy5RKSRQw40fMrO}4!5Sl?)_^zf;hwadzK^kDH$4o*w6mV=bON)< z*wO;37Kk3mG5Pg$pgJ-Gw+Ms@$SUDHUAgSC6$0M-er^UUj$Qg=%WbI&ukLrRHsP=% zw0w@qA%;qNyhkyMFp`Ja2boM)u}R=q+JhR@DC3S$}`zKXv7I){9K%=OM!r`+R;>%*8ei7j^xP0v(kRf3Rw2@ ztUy~2aknqR#)%P`3H`Q~Z6uh^b)GaxHLpFcw;43HO@gg*9Q^mFB1ZxmF3=nXGI$j@ ze7}2|_Vb#~vxbT0Gg&>z%J)uy`0=5ENb{#9Zq-cNwe;b?t<;muVjCzvR)8|e?k59c zlZNUtuXV1ocrx{STh~62EfcZ35|4{DtEUB!V=6t_=TAM?!%3Sv8KfWoeD(nS`zt#q zLBL52=Sm130z_f1RW$JTB|rFS^f#e=&bWOZ*PF@w!g6__SZs- zo)he%B~GhbRiE$aL^7Ga`>mPmM*x9>r~s;-z+_Q=h!H7Zks!{?Y#^_>D5?bgdR_2gv*dhaqw{7_Qv2hD$mrnImE8k_m@n4gi1%x<~d1Y;+Zjjyk)qMf(mq zECo_1;SE+&79E?4n7bLd0I7=2&H@gJ=WgyeLbe6at{5?o!)J4Lp(cL6$4PlsBQ`q+ zNu+hFgcA~nhLO?za9TRa#o*Q%RPXJ*Kov=*xU2B_rD&ah_Z*Y`=2|0%gmkB(~efW5Sofj7)9M|mjR~c8hh;qFs zj@x6t!7LdwSf@LFtO+#BXEb&4O5`*dKZGpJ(luD|qpfvdI0pAl2DDO#|Fg&o$KJIv zC&zloNMV;QjxF>Y0UBZ*o4fSZ(j6to5KB=pYZZM?*^`hpV_+d&R;P2bSSRAXx0HT4 zOJg);gDw{{2AhGRy*jJ(CEcISvB($GG01qFXR#&$Qu6GD?>qM#;;2Hx;u$Ar`QdGc zA>C@9=lvcU0+_`ZiWFB%jd%}^c9hsn&v73JFY-y3iw3Vks30Cc7wv@)gPj7k1iETw zLBWb}A3!}7&^!!0^p78F z=`uHezGN=G^y_$j_3eB!nD|~YO?GH6XKQy6Z5}$!a*ujeLyr%RD&}Kv$3h$O9L?Cl zzE4&7;v4~}jet@D>p^&!`T*z4WqSd{dU2t5y|!tpymNI?>ceEQcI_?vvgaLSl@AoL zLFfJP(lt$-c-^n%=Qxid_-%ETDX8APUc_Wb%IvmF=n0SKMl9E@ionDZ&&bjkiCFxH z^}Ola$e1P8;M@x|c8F5(QP=4|GKZo&1Ck}fA>;o0P(#`D?7C{Lp_AMJYQ@LdNfE4T zhzD&NivJ!G7dA95ZSBn4%qtp2W`VJ@ZBNK6C6Ur{FFc=z$tVI7RTRzQpJxSh5Ogdg z&mKzZ;i<#^0npU50yqV!YcKZ;V9K*4F6hPntzy0c@Si_?w~XuvQZMCqUhO7wy_~TT zkUh^%Uclc&6p9x>F6({_zM-z&uPPXtkxkqP=EVE(vz%AJ>1mo?fNx&kGSUH&_rX^{ zh5)3e!wzz82fycHi6`#s!te4||M_42byn;Q3No-L4eHjbBqY&PyTmYA%E(Y{)y0an zgy9DizFHd(BSQj`15RL=I+&0Kao5;ogCMki^@MDatJcqHEhOwfCP%|Ws!ifW5CF3j zc)FXVnj2vrCK4Zq!SywtOZ*Njw9^3tRUfX4^`s(FfX*x?$MvFT!sa;5;^BU&FxTes z#!kk?)T^EZW&l(@u}BU6XKZ459np~kD zGEo=S^$qWRTm+`)u1bcH( z)MX-i8`ROdzVg=Fj_r(_uYomJ>*G-XGAyGs)NZN37}fLKrQa0}D8BzSTk-lt&8wN7 z2`)LAi1mSa+!}^%)i5eM$E>W`xlxtM1owb#EkNw%Dcl&7!wF0pb!!-A+oOZ*Twlv| zBm*hIqKBVNt22KLMzsv|K9nJuCh z4X0^mXVs}%-+6<(4S46xpX}nhEe>fWuk z&jlulMbAXxtJzW>@&F5SW2j`eh53KSQG;5dz$J#Tf@A66zcnTfD>)$&(Gn4PG58`Z>sE>Kf!@a3oorM|H(W{zKZl_-2wjweCyX z@NV{2oP*dK>qcTuQERPXNN9&5bHZn%pIC83JKa90&sN*yqhf`d0V$F{8>sxeyBSA0 zxbOa`;v0clvTb=y0uJ-D2IhML2u`RdE&vL1Upb3l^0aURTvvCXAfM406SCJCGgu-4 z#xV@vWL&$QmS-K;9|IHxu5DH9Oi*i{J<#T6!1!zwTM7g}dtp9)kKuFJjggf@A_RLw z9WuwjsND+)A6eXh6cmyHNIS!>RhuiZmaYvMqRt?W`|T2G`%BWlT7Unv4@y0|`{N}M zj?ZJ*TmoR%OASckGnEWKl$cFsbJ{M#)`OG)>r3memUR39L;>P~Z27Lex|K$x2o_so zxUrI+OOI)6myqp>=lXEh0_-0ZW3SdL*EP|>l8M#*VVf_Yc$_Dpv)$Whl@M-2SKvL* zFcL@6fub0My{2K$7q{qF%=Wz2V2p-+VExp4yySF-S?W z#jM@y{0XR9s_^z_~k{v^@8)x7wAg+ozt(E*C(nYi=Pc}f3huB=C-mB#t zyD07WqxL%**LlXhuKW&?cofr#sqtSUuz;k9iZyd-yhCP+qDPTc{QJ5ehK5W217a2N z;P;-RVFyy6S?%*_aHMJM}YK00F)>DN~kZx;{ZBj@h^SD!&_O0nxOJ|XSqhi!KUj9b3{M?*uG&eQlT zW@H_785wKK84$;}o!;OL2A*AvjhOa}b^X&{zkgKDnYMZDIa$x$g}+Gh>sG~ay4jOJ zGYdPZxa8=PaO9j0@G4K7+y&fBgeloJ`_+`lnP;B`-4Dcz(>@Bq7I6UaGN4`-UY?)v zQC+cf>zKgSUZoudDHr1@;A^B z47%LM*p&FB$9kP8%is3_@E*Zj>;Z}gW2Z#mWIW$d!WG6dpNBjJghhaBa;QwiLiRFehSVN+of%hp<{MeibrZbs*-vog0 z@Z)#lAac=~I4oi9Ja9#+W&lLj!bPn6ZihsbKc}J|R9obfgZ%&_FJ5Tp#9@R%rnbCG zG4i6~j1C?w&M{eT4njz%LEBmIW8Ytls49O`0oykNI7+tl8QeE=50%ucTVonI+P_p5 z1)6E0T-V?AAQ^^#55A+E=rkYrM zbm;=BMcRP9MJuMoQVCoKP%1_3Q)l^2hPw177Y7=5x8w>`p|ur8dg`kUmFoyjQ?Jc{ zyVe~Iye|T*eWs{1PmKg!ta^$iah76OlMJs1IyUWPSdU`&ZpFfAcKUH$%R<6q>4r)G zl$@jGx0@*(15&7VqDKdKMgQ zK!v!z)TDLGW&v|eNm%N84TY5F2`=V*PW$#bhMAu>*f#rOIh(kSxCauOdFq`Vai2Nt zAy^uIM%Yf0s2qHHlMZ1#Y_%$TD!79x2B8QLTB=|eKcD+^-+EwbwjqNpawysJK4_(l zz*Q{2rGV#;``y)h`KxB9j+_Y->ajWT%zymDA8PMpkTEU!99Ex)1P*pds9dbO3|Owj zaVfcz)Y-Pb+6HF8E%ood8eE<%DKO<{s*v0Vm=ciY(8)_@YG&Z&HHnxZ+IGW_#Ako@ zpri0TVZ+gIU9II#5erF~WB@cJ4tf483lPsAnTNQZ_PV;Xv`SxSetDAlwLWGAQk0 zE&MP3-DLD_TGL~f#YuUb$)Ml!nWS&u4+D5A)~ANx?drXB6mUjYLCEq%+|*9mS*_7% zmH`@?@$arBx;nn=T{3D-*TnT4UbAjx3*zTu@5i9qhugJ@8N}vZOWX41;!iy5I1j1g zd+go=W~^^Li5Z8Bjx3?$Jc>C%ku2G4E+EoI8=u9zip9Pz3zp~c()qy8Z`P})n}_`` z37z+fv({lQN7`Sr>3_C7QMMXhda*Ii!{&jX(Ka5P2$|4@ZgT*Gw&MWa2>|cCh#A+i zI97}2&!92nneusUz4f@+=d9@`zBY7zv!$3pT6Q)^YNz84L6&GWd34I6$Z z_5wqK7?^{66WsvQG?%}OxF&~Pg~sAV1F04d5R4Y+utx%bq_dj?T-HYsPR34>_!>iN zksu=`h~J6dr3gaz)6IYt1!W4*Ok)4)g86RQml$4#L>l%!{nhS-BFidG4R{EMScNm(OTbAs5ZzU_8e#JGueJj;Q8yQKYF; zz+D2l263wZeze}b>jF?cPqXjh17^>>pi`A0Yo|#o!85pz~i<)&wAx7tKx2*t(XIL)7r9x1SN$$#TeJdP)B0x$wi~M#&~Y9 zqA65l&Sc>KUUB1`nzymf(stPg<~e`bhPbxftY3y!*ZUO!*8wtV#=qxY5C(HC+oo%N zPOYVCRr(zky1T@|4?H-G?uy4X8932P9UrHd$t&(5z@Uof_saXrruL#aF*@G^4_oH>rIRAo3SHt6ET*7^ zraJEjXbl)m{>wFS?byeW$qc^s?Y+j=a`0>G*cIO|^6%E=RVd)nMG`jaD|2?9&madX zd+)`wdmDz1PXF)!^1uB3bF==I391p0WTm=I1rnuhYD^tg#q69dOd zhv9gB?@8_v)B#{dLeEWNkP&58u=i*T+(GM)wk-^a7UJThMr|B9!PFarz_e4v8Cr6+ z)7guarJsf_epG-5J3ye|TOCLOf#{va@V~v?>~lX!?Nn!TCx0K^u_!9 z<~P4C<4}q5MeXjfezdV`P{_qY_8wK8Bh5coTpv07IXf?P1U77hqblJKt^vxJBD~Yv*9p6-1T+yn3@~@uXfB(3bu6W!>w5e%efZADc?C}g^8hTdA zr@7c^@$8fwNdAKf>*#kTaQn4`3%A?VXS7w}B(+?d92gVS^$g#N)j%;So->Y{!c6>Z z7~NnMNTx&J((HsQrmPAI1JiVf?ALucfW{BnL~kfQIIsC6rHr1v@6K=v_?90BzlVtO~cl z6%E*IDNg~zb)KhMX9{T#4|n^`Z+=_JB@@^09_Ek%{nYZA04$(^4d`?@=>Sw#ka8)O z;jf?PU4T>{-lgR8qm5PVc#%ki+yLMj?1*?J+qel-KhI%^rmO5>lT-e{fS#CUlKqJF zQKfdND)5IB3P5<;BiWk-4ru0{2BbADd~Y=v>iHaElJn>9K1)0C`T0l$#eMvcgWUQc ziNoN~cODq>Fc^6)TR53LDki9kB})y%iihy!7Y|0TKK$Z9_G zR}N;yXq;S77h~_c8Y~NQ3(1;1hdG1e@VUB{|A{>q_WfeeY}>nTV4GSE^m$>eS^t*j z5Z4peuA*U=8=>7#?Ea-#mK6h@9W>#da;o~z@>R*?FjMh9#BQ0W?_w~kxTI$qvC?;+ zzi;T!ORID%A2S)SkFQ~0!yctZ-B|8C`{3jPqt4SYq*(vRXHb}oYhajGo+TGpc&|9~ zFlt~B3dD#az6L9iD8>E9=f&A6mfQ39-fkKQ8sCpI8NjjtqV10c$7BL^DwdD6`tip< ze+aN{6hG<-GFn_#06i(vz0d-;O(eXn3!{qcR?Bw7Z#}C;a?X_cIn0)2tnAijy zDmX4!6E9wK2FSe%p)u$%zxU~ri=}%NpJNZkd&8vb!?@4#ubJe#SP;)NV(GZQ=B}== zLA#|Gl)T=Gz`!uIDD36H?eOte-8cRm&C__rb%c$M_)e_5RyLYWgZR9kp1^FzWxfuE z&L-&nd=G(?(rV88H$eaJp4r^-7xx!?>i56^Pw&;;?}f71EVYU|8IW%8xbuI z<&8Jze)~?BfCvMrBf1^Rz0RZNwGcO9%YXSIB1rBD7ckSmw>~V693`jGyDJ7jKyk?? zLW%#LaoMDzbNB-^S}B46R!CZDPdo7bE_$*#rwTw-lSO%6ii_168CNP^~iuEEA&)X5g21L8? z83V0F8hf)Gjn?wJr>`qk<2)A;0oF4hD0V&0L&R(h^MWCKy6^!x>Pk050JG0Mt)bfA zY2aF62)g&M2LjZ^pd}|i5A9Wo{VFQ0`OH`QBLUY(kqhH%>QK5AIR{PhxDUyQokdQ% z4qDk_ghNPLGajkyHr|7F|6BS?0it)hJ#*b|MLZBIU${bCQ+#$G=(}3-OaS?BpT4cx z+<&+z#@UbO2k{HsBR>1*pMRR z$dhr_;@SWc=b^j)5AiwAwS#?{?=$tmlhZWS8A@_m6!^lwc<%sAawdP{XP}NNNdyAq zllKAGf-ll#S>3$0eRM|815#Rt$rvd7pf~45zle zrXvYJy7pZYI3CYF<7xx7lU~W1KA<){A3bB?4|FYn9dJBXx}~U;UA{|>c9H`{QsXp*c(5O$>s&Qr6#q8Zf_MR%ScWdAa)!Bc+fR#kuG?c-31642k)zyNMgK+QAn#& zG7T@wKxIG6ki`wcWN5CM>Y+|)pkkqu9pgluL`X@F+u96PzkQN+kt!U$T_l+-^JEs0 zz&Bc0!idA5h8d0q@<;_OjSHa4y=}?;phmP4MREBK?t60ai|S zVpLRDS499iRZSEqni3#Bu<=>3_UTYRf3_CuUI&ZeCj?Zm)EVxEA2B2(FHF%y&Y9;nU zfKu_SwAN?tx%iz3q;|9Ms2)c!*<3t86OOi;WbHoBk)Cs(DBP>-vyJN3t8Hf7%x3Gt zI()N%3eX5ZH?EbyMQtmOa{3r+Fk%shp^X4w^k6 z!thxIC4n6ta=5%!<9fTEKzA`jkAdl<_f59a(82=%_BoC5(a;p6O*Ebji7Z7k+P?r6 zVCP(HLt|Y7D9?cl2A|LvdaV>PP(u!~2FsO$J)Z#E_l@bjI$M_lSV*`sNi;BvvkaS_ zx@nhdt$DD`uWf0*hKB@!DbH7GhnC$Tg<`uFC7fr zAJ03kFXS;YK{_@w7o_z?Q4mRnny74BKsz_>M*y`X>`Fj>8Cq`jKD5v8w(q68=_%@O zJTu#-F*uC<(VRT3W~L{r&@4sl7)j#BN;l_} za|Kfr2E}S$_h#?|rVQW{9W(Ph4p7w@6Z;=+(e`q_^z6NF{hZ~qmMS)hiFvrV(si>H z7kGfw)6Tx#p4Yrg#j2%UJFYVmKeu($hE2f~fGgHN?cpJ?Q$YFAXd~RsNRQxalmb9E zoOlJlsepQ_cJUv6^{WaBVV-^U^y+qYO+#WoN%rW z22Y6z9D5z06qYHidc}yd?VJowvM)dG8juEymINNZJ^8xAcExkf#L5NGhlAeOYuVa1 zcNQ|q`X^u84R(wZ2Lk$J|2TmRFmtzj5QU#y9^cbZ2}Eb+!ibqI^OZI_&bzhzQYPRc zjsG0n<};8WZGhxveI>EME!oTmh-VYmt>SF6J^UhhG6(J?dj#~IB|TZXqbE1`J!0wf zSuW7aRvzUa!uRfOM{N_apNIGzdlf&>zWrn{CEK`dZ|Qs~XS{H`<@+o^V0 zfZK=_F3wxp)H@sx$5x!390+!IV*GrMc+Wfw5tka^jrij0moE+3Fl@wTe*hzyV)xbL z0hmk8%=@qppB$@h6#GNc!ow2<0`~!&vlB-K>glX#__F{u*{6$bfp&~aMXtL!Ps28lUzH!4w6E8d z!s=47EP#KKYmjSd&05i7CC1*>OpC2L=kRa$lBiPtCSc=J>83Xm3NqPltrv5Rq{qI9 z?g9af8g6%SM^KVW407YYAqb(>pNucSH^)*XwPJR%Z2qU*Z#AQnlEdl*h8A<@H%LVT!{QIyAoaSi+hjH zzq@D-z^-`pa}lrJjPJlgk9*MI=&4w$<2(Y_yM|)H*2ZDB_`bLwK&nwHGw0x#fQU?8 z><=;3)g>gb?}vumL17R0$yW99${zri89w(ZNpl(|M}ENX1W?9cbXc^?+uqf!zRYH) zc5pO1!WU`v(wRDeWqI#Va95|vSpcDAYBpm`2JIu?&aSGX3bO#%vTquG4pB{v(l3ee zj8am}IQVS9a54cbo@LmxxX0*{p|j>rho%cDt+j>}A5uZeLzj$eGe&V2LGN^!YTwfl z(+p(lMI0nOdohn6W!tj5))*Cg1AGUcg~Ua3j1938Z18m9@jS(r4XmaQfLm&x!XgIv z*0w!cm+9z|T-Y-(M{;`Nx_$<%9^kGbz79CdS-?*fq3Y44G{)S5I2u0>Mgvfc?TA;b z89xusB&Phudv$rapLj0p``HDg<^npM3lN=U%Rm0h|Li|} ze(`?^19Nsu{>t=XU&zI61+<~&Lp!^;IU@Zy1u6$TDA0J?)#C-=8^BNk5vzpBw_-># zDDP2$Ho755Oz?0C5Hg@UdJ?4#z85e{I|CqAvO{c;+l00$0pe1JL<`>&VL7X*9=hIR zwgVfrQfF83_4``5uIUpSo>nH_w+#k__u*M4;AobQS}>*p)atpOt)2(K`u*NDBl$X0 ztO29YEhJIH08GQd;82h(@pH64a2UqlnGAwgmHc1`lc)K%u0d6w(WYwOd@vNtuN@uJ zvl|BPljlU6CPM~tADDd`4+1Ug{Jm4{rGT;Vthg3hanX0CePS{wN8nzqsfPsm^Lfv- zWOQ+NS|+Qid@2!usU}^kt-*K}%cRS1oM$WJqUWe*^00my=Pa2`W;&OC`9aGb`_a#H z?}Z)C6Kh3Klh+Z!&`KA=RNy78YP7)hfTHWG?Y_`#t6$eaAH5r6t|7r`J5QKo6tUu2 zbUpObZ9A=6Y^a3l8LU+?K3oabS=w!vrn*l4Et!Nzv755ZWzg}HGdRs|Szp>x04uZA z&|st5(>4R_ePGAfE_1kTAD#Gdmvb&{_ zme^(+i!LY>b%fG}S!(!g8h1T%l*A{(6L((o& zz-g`QJw^cEg_H@l?7v1I(Hy71>czILUdIH<48D}~ zN+o)|a8m%=*<~(>UpC7-u;)21xhEfMW0-BuSGa$4TX#AxkMp^$rQ6$VpI~;geoR3> z4B$kPwpzx7Ybnh~`~1zZBHHPx5$|{?_unqyn>aweWvkE1_p$zC{?6IYG`53%j!agU zC762B>9|Qnu+5*}_h)uqzJC;emd|(H4D79z4r=!MXT=#+2!r|V=Z2ko@w<<%>8w4u zS78n2^y+yXyW?Dy^RzEAvD_Hqbo!pA^24k3vUlR;v&#Z(BJEOz@UsKO?tsg#M|a`m9x(`ozbqiDX|NPU$u{+2y{mmJ zpr~SA#jBAwB4u*toEpH60?pl$4$2kUpt-GrBy)8n*|HWxR6`Sw$}u1zDDP8O@yGkU zWGsmpwVqpbkVNc}*JpBsq@j9@N8XwC`R@Ebu4$U8fJ!{P8W%uA8Na0XuvOvg!!tkF zV`!%rSmm`Az}gEu@dCeyR~6k1j%NJ0^`ZxRyMsY%fh=Mx_j8E_yg#LFbSO_&X#?Y0sbC|E5oGw!3Hfo^iGk z^T2rC3;;<$w!igc?c}TXiaDj{?&51>{rYSM;z>v%3|8liv2MgKvj5inKl)i`jg>dG zDs&LP$nR{mkBHL&Cc%_OZEMyAznujY6RFrA9aQ(wVn}fl5|xj7Hs9vuvNMnFlh(EH z3uaq^>rhyN=tIF{%u)GTJNtk>>;e9E+ymt4s`wly1CcS z0z?gATHy9;}Z6*o%TM07#73t5>2)|;wu#x9*MV_@ zN%aEImW3G)1sj0P#s&iHm2uxoOELplCsBG%VW1C5Im}BL7T6ZC0gf8b)DJbu0l@rg zRb_Q@*2)ozsdTUB(G<&7n7S zeU_NLlUp7?s(nQJf#=xj9%>!DXGuJB&9|<#hM}P9lO}yg-o^^hG{w&>6-4IG&N4bA zXAB0rUM{uY;zwb6jV)Tzm`0oJv@UXQ3DjZjlV>sT7?POOe4gh^rD!j}k!hWsmCLzYb%0bWf5O)@Atg6T0Afd802tRpqo zGi6XmT>N6d6LvQaJcBwCpg4nQHWk3Tmywk->qdWBVm;?N6Y<|K&{7K6?fSQ&i`C3pVQRWbK^`krE`5Bs`|K_SxnGTMG2B!Gcn?(LW2}W(zNd{ z+Lr{3>pIu(2fH9XlV_R9$-M{I(nD1jMHbkmsfP10NQ!4qu+sc*G(jb+axDiGjA1dN z_G)L#ZW1$_WV`&OBoNLyg?DW1A8%aR$UV-P`U`|T1#JhVy9#!QW%CKxD zC()CU?sOH<#b=(LGX0v!-%quXp65~Ef>_drLEg1|&v|wm$vK0`l>x%hhbJ(x8Q=SG z5(b-&)?sYY{qa#giv5>CfOX90rB8s)_p_d2Y8%=H26J;QS*Kgu<~|fRhrwDp1dwYs zdys_b9WvhmahIN#NO8vdlF|b2i%}6ZS_3 z%QHstH4~tt9l2QIg7Yn=KyN<=_}2b(XNp@ZDL580;7}5u0GnB@6iH|MHEj4i-%4sn zt@1~)ywNuyG z2QbQ1U=4-G%c3DYNN$`>8nQ;dQ~>o}zf|YeSpjVBM;DvKf^n?V&MKC~_1&Co#VZTr z3&kcem8Q5R7oQXCHJcQtAA2eGzwEI5Y8+)GBF?kfkF?I@jn7|QEDt8en-fq=+*Cps zdw<_+J^=Qbf}aZ59yGeQ#zgl$0Hkg6&zmWBbe+{{4tp(|vo34-gvI)z39>iE2K(OU z0>CgPKFpXn_VS@_o83Mf#&7P=m}6k((&pzIk7Dzg+?*WBc0bQv_zBy`j3KtOnEzSK z_uwUxt=U6~)_rwhQh)?3!z=*ge1s0PBC{eO%Iv4zvtr~tc{gx-A2 zm763{;1E!QzA((hRx#wgF^F{X&oSti;zFCI0$Grvz^-Axt6=0a5YI2V(G)v%5Idig z{XsCekldk|5$o{1rN=TJI(lLG%lakP;vkpUI}i}w?cwu24l#1*@{EOK@2{7x&&m*6 zeB4e9;+Y+f$9wC5s@Sqf2VUJd$hnJsaJ%-|008Lfwwx;efTTbNLM60YFW`KbY1*PS z+0Pm&l=Y(Fp^Bx}D#*lNQhuv>R>IK}eRR$NlVpokJVdId8PJfQP*=seZA0Sy+{2Gc zEh>|b%x~B_ca5!r}F!=L01M$jd;dvd5LSWO6j!A`(G`4Fm)Yj z-T$ZdEe3P5)XTT?s1iq*v^T=nft5%$vJV5xv$oQqd@DTVG#fPYr`zec@LDkf&r|mk z|L*oD_KKLH-{=LO9Z;(n+wlw?%qjoUT3s&%EJo$X#`^5+$5h`FIPZbi19P5v55)Dvq8*fN z?pke$oe0nALplL&d4_aK3ZHuV#daOm1pBwuY^m^>8Dm{~e6Ht75m7dWc{2B{69Oy# zPEe>-o}#nx7;qQmy~`r02YfU-<&zn~#ER$HWmEu=MP%++L|oU+-7$qDT{^p&I9}M{ zS;_)MK0WC$m`nufo!85v+-V<*ND$GOi^8)+k6iJE&Odvd=LpW1=V50MUMK&N6QU(Q zVmQJ8`yfi$oXOp^d!2sd&4zR3}rB8?7&PI!QstQALuJdb?@XSlA5m z&F7W;x6ioi`p=*L`hTk3M3s|E>Jx_y!}Fod3jB5_)tN9vn`tC~&rLb+Q!ytrX}C{~ z)^Qx2 za5S+U5JHf1^CA;ea6cG9Dt>5)=5?Rb00wkM@8trh7aNO%(JEnzWMppL0dW0`HC1^Z z1?JlTy2sCVJtIIZ4i-`wt?d3f=}qzI0e=gH=;Hgi9R>FCuwbUw#5CBWq{7l>zLS8F$Q&A2FVTrW2R zvb}OhMTT}hfv#tLo*Ymwc1#t(Zm(gyhz0-^ZC#+fp~^>$x^suH$Q(k}PL?af>9$`g)v|*gQP> z9v$nc*Ozig48cCnBm%z#tsQ96B6B#g(a?-IXOqvQhWcQf^Sunj10v^IMsib(Ay*0) zF}WXR*<8#{I*TpdHQm%wB=8z=y*WUmaP{%=-e~_315sxu&TX>n+>&`7U8^Xr)TxA@;sonLoZ7P@6J=n5OQWb^|N00obDoG`rKhBVTe3 zqW`CKDl@1#PY+|XuUO~CM&Jf_9TNO&thrxjtz7| z^W5ikt=T4;5+;^BK~EVlh$0UxT<$;h?rs;gg91QrYV(%9+*Yotw9fXPC<9!|=6E*1 zz;H@k*i9PnbR(qwIBu0FQGEv>NmfC$@0lT-XZ@HOVa+G>^<5}Su+e}b1&6pOlFu

-
@@ -117,33 +86,6 @@ function StartPage() {
-
- 달록은 이렇습니다 -
-
- 달록 사용법 1 - - 공유 받고 싶은 카테고리를 구독하여
내 달력에서 일정을 볼 수 있습니다. -
-
-
-
- - 구글 캘린더에서 원하는 캘린더를 -
가져올 수 있습니다. -
- 달록 사용법 2 -
-
-
- 달록 사용법 3 - - 카테고리 내에서 팀원들과
- 겹치지 않는 시간을
- 확인할 수 있습니다. -
-
-
From 7b56935de50623a4a10d98840241080b5ddc039e Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 12:29:19 +0900 Subject: [PATCH 087/148] =?UTF-8?q?style:=20=EC=8B=9C=EC=9E=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20overflow-x=20=EC=86=8D=EC=84=B1=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/StartPage/StartPage.styles.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/frontend/src/pages/StartPage/StartPage.styles.ts b/frontend/src/pages/StartPage/StartPage.styles.ts index ddb06ee2..a5d83158 100644 --- a/frontend/src/pages/StartPage/StartPage.styles.ts +++ b/frontend/src/pages/StartPage/StartPage.styles.ts @@ -190,8 +190,6 @@ const secondSectionStyle = ({ flex, colors }: Theme) => css` const pageStyle = ({ flex }: Theme) => css` ${flex.column}; - - overflow-x: hidden; `; export { From a82f213aef4681613e4605dc9e5de74e2207116e Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 13:38:15 +0900 Subject: [PATCH 088/148] =?UTF-8?q?style:=20=EC=8B=9C=EC=9E=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/StartPage/StartPage.styles.ts | 4 ++-- frontend/src/pages/StartPage/StartPage.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/StartPage/StartPage.styles.ts b/frontend/src/pages/StartPage/StartPage.styles.ts index a5d83158..b6e161cd 100644 --- a/frontend/src/pages/StartPage/StartPage.styles.ts +++ b/frontend/src/pages/StartPage/StartPage.styles.ts @@ -167,7 +167,7 @@ const mainContentStyle = ({ flex }: Theme) => css` gap: 6rem; `; -const firstSectionStyle = ({ flex }: Theme) => css` +const sectionStyle = ({ flex }: Theme) => css` ${flex.row}; align-items: flex-start; @@ -198,7 +198,6 @@ export { detailTextStyle, dateItemStyle, firstItemStyle, - firstSectionStyle, googleLoginButton, loginText, introductionStyle, @@ -206,6 +205,7 @@ export { pageStyle, secondItemStyle, secondSectionStyle, + sectionStyle, thirdItemStyle, whiteTextStyle, }; diff --git a/frontend/src/pages/StartPage/StartPage.tsx b/frontend/src/pages/StartPage/StartPage.tsx index 10a46a01..50bcf76b 100644 --- a/frontend/src/pages/StartPage/StartPage.tsx +++ b/frontend/src/pages/StartPage/StartPage.tsx @@ -19,13 +19,13 @@ import { dateItemStyle, detailTextStyle, firstItemStyle, - firstSectionStyle, googleLoginButton, introductionStyle, loginText, mainContentStyle, pageStyle, secondItemStyle, + sectionStyle, thirdItemStyle, whiteTextStyle, } from './StartPage.styles'; @@ -53,7 +53,7 @@ function StartPage() { return (
-
+
{getThisDate()}
운동 일정
From 4ac41123a7844d5dc4ac3bfc3333b8b6c721140d Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 00:15:26 +0900 Subject: [PATCH 089/148] =?UTF-8?q?refactor:=20=EC=9B=94=EB=B3=84=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=20=EC=A1=B0=ED=9A=8C=20=EC=8B=9C,=EB=8B=AC?= =?UTF-8?q?=EB=A1=9D=20=EC=9D=BC=EC=A0=95=EA=B3=BC=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EC=9D=84=20=EA=B0=80=EC=A0=B8=EC=98=A4?= =?UTF-8?q?=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExternalCategoryDetailService.java | 30 +++++ .../ExternalCategoryDetailRepository.java | 13 +- .../application/CalendarService.java | 19 ++- .../domain/IntegrationSchedule.java | 10 ++ .../schedule/application/ScheduleService.java | 29 ++++- .../SubscribingSchedulesFinder.java | 95 ++++++++++++++ .../schedule/domain/ScheduleRepository.java | 23 ++++ .../subscription/domain/Subscription.java | 10 +- .../subscription/domain/Subscriptions.java | 14 ++- .../presentation/ScheduleController.java | 14 ++- .../ExternalCategoryDetailRepositoryTest.java | 5 +- .../application/CalendarServiceTest.java | 67 ---------- .../SubscribingSchedulesFinderTest.java | 118 ++++++++++++++++++ .../domain/SubscriptionsTest.java | 4 +- .../presentation/ScheduleControllerTest.java | 6 +- 15 files changed, 358 insertions(+), 99 deletions(-) create mode 100644 backend/src/main/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailService.java create mode 100644 backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java create mode 100644 backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java diff --git a/backend/src/main/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailService.java b/backend/src/main/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailService.java new file mode 100644 index 00000000..818cceff --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailService.java @@ -0,0 +1,30 @@ +package com.allog.dallog.domain.category.application; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; +import com.allog.dallog.domain.subscription.domain.Subscriptions; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class ExternalCategoryDetailService { + + private final ExternalCategoryDetailRepository externalCategoryDetailRepository; + private final SubscriptionRepository subscriptionRepository; + + public ExternalCategoryDetailService(final ExternalCategoryDetailRepository externalCategoryDetailRepository, + final SubscriptionRepository subscriptionRepository) { + this.externalCategoryDetailRepository = externalCategoryDetailRepository; + this.subscriptionRepository = subscriptionRepository; + } + + public List findByMemberId(final Long memberId) { + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); + List categories = subscriptions.findExternalCategory(); + return externalCategoryDetailRepository.findByCategoryIn(categories); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java index 158be786..873b47a6 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java @@ -1,25 +1,28 @@ package com.allog.dallog.domain.category.domain; -import com.allog.dallog.domain.category.exception.NoSuchExternalCategoryDetailException; import com.allog.dallog.domain.category.exception.ExistExternalCategoryException; +import com.allog.dallog.domain.category.exception.NoSuchExternalCategoryDetailException; import java.util.List; import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface ExternalCategoryDetailRepository extends JpaRepository { - Optional findByCategoryId(final Long categoryId); + Optional findByCategory(final Category category); + + List findByCategoryIn(final List categories); boolean existsByExternalIdAndCategoryIn(final String externalId, final List categories); void deleteByCategoryId(final Long categoryId); - default ExternalCategoryDetail getByCategoryId(final Long categoryId) { - return this.findByCategoryId(categoryId) + default ExternalCategoryDetail getByCategory(final Category category) { + return this.findByCategory(category) .orElseThrow(NoSuchExternalCategoryDetailException::new); } - default void validateExistByExternalIdAndCategoryIn(final String externalId, final List externalCategories) { + default void validateExistByExternalIdAndCategoryIn(final String externalId, + final List externalCategories) { if (existsByExternalIdAndCategoryIn(externalId, externalCategories)) { throw new ExistExternalCategoryException(); } diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index 43dced0a..0472b30d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -8,9 +8,7 @@ import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; -import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.time.format.DateTimeFormatter; @@ -54,16 +52,15 @@ public List getSchedulesBySubscriberIds(final List su .collect(Collectors.toList()); } - public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, final DateRangeRequest dateRange) { - Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); - List integrationSchedules = getIntegrationSchedulesByMemberId(memberId, dateRange); - return new MemberScheduleResponses(subscriptions, new TypedSchedules(integrationSchedules)); - } - private List getIntegrationSchedulesByMemberId(final Long memberId, final DateRangeRequest dateRange) { Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); - List internalCategoryIds = subscriptions.findCheckedCategoryIdsBy(Category::isInternal); + List internalCategory = subscriptions.findInternalCategory(); + + // todo : SchedulerService를 리팩터링할때 제거 될 진행할 예정입니다. + List internalCategoryIds = internalCategory.stream() + .map(Category::getId) + .collect(Collectors.toList()); List integrationSchedules = integrationScheduleDao.findByCategoryIdInAndBetween( internalCategoryIds, dateRange.getStartDateTime(), dateRange.getEndDateTime()); @@ -77,9 +74,9 @@ private List getIntegrationSchedulesByMemberId(final Long m } private List findCheckedExternalCategoryDetails(final Subscriptions subscriptions) { - return subscriptions.findCheckedCategoryIdsBy(Category::isExternal) + return subscriptions.findExternalCategory() .stream() - .map(externalCategoryDetailRepository::getByCategoryId) + .map(externalCategoryDetailRepository::getByCategory) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java index 362914de..86be874e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java @@ -2,6 +2,7 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryType; +import com.allog.dallog.domain.schedule.domain.Schedule; import com.allog.dallog.domain.subscription.domain.Subscription; import java.time.LocalDateTime; import java.util.Objects; @@ -19,6 +20,15 @@ public class IntegrationSchedule { private final String memo; private final CategoryType categoryType; + public IntegrationSchedule(final Schedule schedule, final CategoryType categoryType) { + this.id = String.valueOf(schedule.getId()); + this.categoryId = schedule.getCategory().getId(); + this.title = schedule.getTitle(); + this.period = new Period(schedule.getStartDateTime(), schedule.getEndDateTime()); + this.memo = schedule.getMemo(); + this.categoryType = categoryType; + } + public IntegrationSchedule(final String id, final Long categoryId, final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, final String memo, final CategoryType categoryType) { diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index 8c784381..ad18b190 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -2,13 +2,19 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; import com.allog.dallog.domain.schedule.domain.Schedule; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; +import com.allog.dallog.domain.subscription.domain.Subscriptions; +import java.util.ArrayList; +import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -19,12 +25,15 @@ public class ScheduleService { private final ScheduleRepository scheduleRepository; private final CategoryRepository categoryRepository; private final MemberRepository memberRepository; + private final SubscriptionRepository subscriptionRepository; public ScheduleService(final ScheduleRepository scheduleRepository, final CategoryRepository categoryRepository, - final MemberRepository memberRepository) { + final MemberRepository memberRepository, + final SubscriptionRepository subscriptionRepository) { this.scheduleRepository = scheduleRepository; this.categoryRepository = categoryRepository; this.memberRepository = memberRepository; + this.subscriptionRepository = subscriptionRepository; } @Transactional @@ -41,6 +50,24 @@ public ScheduleResponse findById(final Long id) { return new ScheduleResponse(schedule); } + public List findByMemberIdAndDateRange(final Long memberId, final DateRangeRequest dateRange) { + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); + return integrationSchedules(dateRange, subscriptions); + } + + private List integrationSchedules(final DateRangeRequest dateRange, + final Subscriptions subscriptions) { + List integrationSchedules = new ArrayList<>(); + + List categories = subscriptions.findInternalCategory(); + for (Category category : categories) { + List schedules = scheduleRepository.createByCategoryAndBetween( + category, dateRange.getStartDateTime(), dateRange.getEndDateTime()); + integrationSchedules.addAll(schedules); + } + return integrationSchedules; + } + @Transactional public void update(final Long id, final Long memberId, final ScheduleUpdateRequest request) { Schedule schedule = scheduleRepository.getById(id); diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java new file mode 100644 index 00000000..661f811f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java @@ -0,0 +1,95 @@ +package com.allog.dallog.domain.schedule.application; + +import com.allog.dallog.domain.auth.application.OAuthClient; +import com.allog.dallog.domain.auth.domain.OAuthToken; +import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +import com.allog.dallog.domain.auth.dto.response.OAuthAccessTokenResponse; +import com.allog.dallog.domain.category.application.ExternalCategoryDetailService; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; +import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; +import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; +import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; +import com.allog.dallog.domain.subscription.domain.Subscriptions; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.List; +import org.springframework.stereotype.Service; + +@Service +public class SubscribingSchedulesFinder { + + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + + private final ScheduleService scheduleService; + private final SubscriptionRepository subscriptionRepository; + private final ExternalCategoryDetailService externalCategoryDetailService; + private final ExternalCalendarClient externalCalendarClient; + private final OAuthTokenRepository oAuthTokenRepository; + private final OAuthClient oAuthClient; + + public SubscribingSchedulesFinder(final ScheduleService scheduleService, + final SubscriptionRepository subscriptionRepository, + final ExternalCategoryDetailService externalCategoryDetailService, + final ExternalCalendarClient externalCalendarClient, + final OAuthTokenRepository oAuthTokenRepository, final OAuthClient oAuthClient) { + this.scheduleService = scheduleService; + this.subscriptionRepository = subscriptionRepository; + this.externalCategoryDetailService = externalCategoryDetailService; + this.externalCalendarClient = externalCalendarClient; + this.oAuthTokenRepository = oAuthTokenRepository; + this.oAuthClient = oAuthClient; + } + + public MemberScheduleResponses findMySubscribingSchedules(final Long memberId, final DateRangeRequest dateRange) { + List schedules = new ArrayList<>(); + + List externalSchedules = findExternalSchedules(memberId, dateRange); + List internalSchedules = scheduleService.findByMemberIdAndDateRange(memberId, dateRange); + + schedules.addAll(externalSchedules); + schedules.addAll(internalSchedules); + + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); + return new MemberScheduleResponses(subscriptions, new TypedSchedules(schedules)); + } + + private List findExternalSchedules(final Long memberId, final DateRangeRequest dateRange) { + List schedules = new ArrayList<>(); + + List details = externalCategoryDetailService.findByMemberId(memberId); + if (details.isEmpty()) { + return schedules; + } + + String accessToken = getGoogleAccessToken(memberId); + for (ExternalCategoryDetail detail : details) { + List googleSchedules = findGoogleSchedules(dateRange, accessToken, detail); + schedules.addAll(googleSchedules); + } + return schedules; + } + + private String getGoogleAccessToken(final Long memberId) { + OAuthToken oAuthToken = oAuthTokenRepository.getByMemberId(memberId); + String refreshToken = oAuthToken.getRefreshToken(); + + OAuthAccessTokenResponse accessTokenResponse = oAuthClient.getAccessToken(refreshToken); + return accessTokenResponse.getValue(); + } + + private List findGoogleSchedules(final DateRangeRequest dateRange, final String accessToken, + final ExternalCategoryDetail detail) { + LocalDateTime startDateTime = dateRange.getStartDateTime(); + LocalDateTime endDateTime = dateRange.getEndDateTime(); + + String startTime = startDateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + String endTime = endDateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + + return externalCalendarClient.getExternalCalendarSchedules( + accessToken, detail.getCategory().getId(), detail.getExternalId(), startTime, endTime); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java index c7ec86d0..1de4ee38 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -1,15 +1,38 @@ package com.allog.dallog.domain.schedule.domain; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; +import java.time.LocalDateTime; import java.util.List; +import java.util.stream.Collectors; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface ScheduleRepository extends JpaRepository { void deleteByCategoryIdIn(final List categoryIds); + @Query("SELECT s " + + "FROM Schedule s " + + "JOIN s.category c " + + "WHERE c = :category " + + "AND s.startDateTime <= :endDate " + + "AND s.endDateTime >= :startDate") + List findByCategoryAndBetween(final Category category, final LocalDateTime startDate, + final LocalDateTime endDate); + default Schedule getById(final Long id) { return this.findById(id) .orElseThrow(NoSuchScheduleException::new); } + + default List createByCategoryAndBetween( + final Category category, final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + List schedules = findByCategoryAndBetween(category, startDateTime, endDateTime); + + return schedules.stream() + .map(schedule -> new IntegrationSchedule(schedule, category.getCategoryType())) + .collect(Collectors.toList()); + } } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java index b067c78d..a324ca8d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -19,7 +19,7 @@ @Table(name = "subscriptions") @Entity public class Subscription extends BaseEntity { - + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @@ -61,6 +61,14 @@ public void validateDeletePossible(final Long memberId) { } } + public boolean hasInternalCategory() { + return category.isInternal(); + } + + public boolean hasExternalCategory() { + return category.isExternal(); + } + public Long getId() { return id; } diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java index 022f9b21..2cef1610 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java @@ -4,7 +4,6 @@ import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import java.util.List; -import java.util.function.Predicate; import java.util.stream.Collectors; public class Subscriptions { @@ -15,12 +14,19 @@ public Subscriptions(final List subscriptions) { this.subscriptions = subscriptions; } - public List findCheckedCategoryIdsBy(final Predicate predicate) { + public List findInternalCategory() { return subscriptions.stream() .filter(Subscription::isChecked) + .filter(Subscription::hasInternalCategory) + .map(Subscription::getCategory) + .collect(Collectors.toList()); + } + + public List findExternalCategory() { + return subscriptions.stream() + .filter(Subscription::isChecked) + .filter(Subscription::hasExternalCategory) .map(Subscription::getCategory) - .filter(predicate) - .map(Category::getId) .collect(Collectors.toList()); } diff --git a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java index ce474055..db8cf495 100644 --- a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java +++ b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java @@ -1,8 +1,8 @@ package com.allog.dallog.presentation; import com.allog.dallog.domain.auth.dto.LoginMember; -import com.allog.dallog.domain.composition.application.CalendarService; import com.allog.dallog.domain.schedule.application.ScheduleService; +import com.allog.dallog.domain.schedule.application.SubscribingSchedulesFinder; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; @@ -27,11 +27,12 @@ public class ScheduleController { private final ScheduleService scheduleService; - private final CalendarService calendarService; + private final SubscribingSchedulesFinder subscribingSchedulesFinder; - public ScheduleController(final ScheduleService scheduleService, final CalendarService calendarService) { + public ScheduleController(final ScheduleService scheduleService, + final SubscribingSchedulesFinder subscribingSchedulesFinder) { this.scheduleService = scheduleService; - this.calendarService = calendarService; + this.subscribingSchedulesFinder = subscribingSchedulesFinder; } @PostMapping("/categories/{categoryId}/schedules") @@ -43,9 +44,10 @@ public ResponseEntity save(@AuthenticationPrincipal final Logi } @GetMapping("/members/me/schedules") - public ResponseEntity findByMemberId( + public ResponseEntity findMySubscribingSchedules( @AuthenticationPrincipal final LoginMember loginMember, @ModelAttribute DateRangeRequest request) { - MemberScheduleResponses response = calendarService.findSchedulesByMemberId(loginMember.getId(), request); + MemberScheduleResponses response = subscribingSchedulesFinder + .findMySubscribingSchedules(loginMember.getId(), request); return ResponseEntity.ok(response); } diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java index 82b654bf..c002fd43 100644 --- a/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepositoryTest.java @@ -1,5 +1,6 @@ package com.allog.dallog.domain.category.domain; +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_일정; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -37,8 +38,10 @@ public class ExternalCategoryDetailRepositoryTest extends RepositoryTest { externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스_일정, "externalId")); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + // when & then - assertThatThrownBy(() -> externalCategoryDetailRepository.getByCategoryId(0L)) + assertThatThrownBy(() -> externalCategoryDetailRepository.getByCategory(공통_일정)) .isInstanceOf(NoSuchExternalCategoryDetailException.class); } diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 980d51b5..df7a0c6f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -1,6 +1,5 @@ package com.allog.dallog.domain.composition.application; -import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.관리자_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.매트_인증_코드_토큰_요청; @@ -9,7 +8,6 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; -import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_외부_일정_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; @@ -17,7 +15,6 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_1분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_18시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_20시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_0시_0분; @@ -25,23 +22,17 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_17시_0분; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.category.application.CategoryService; -import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; -import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; -import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponse; -import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; import com.allog.dallog.domain.subscription.application.SubscriptionService; import java.util.List; import org.junit.jupiter.api.DisplayName; @@ -68,64 +59,6 @@ class CalendarServiceTest extends ServiceTest { @Autowired private CategoryRepository categoryRepository; - @DisplayName("시작일시와 종료일시로 유저의 캘린더를 일정 유형에 따라 분류하고 정렬하여 반환한다.") - @Test - void 시작일시와_종료일시로_유저의_캘린더를_일정_유형에_따라_분류하고_정렬하여_반환한다() { - // given - Long memberId = parseMemberId(MEMBER_인증_코드_토큰_요청()); - - CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); - Category BE_일정 = categoryRepository.getById(BE_일정_응답.getId()); - - /* 장기간 일정 */ - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("장기간 첫번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("장기간 두번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("장기간 세번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("장기간 네번째", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_15일_16시_0분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("장기간 다섯번째", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); - - /* 종일 일정 */ - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); - - /* 몇시간 일정 */ - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("몇시간 첫번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("몇시간 두번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("몇시간 세번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("몇시간 네번째", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - - CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(memberId, 우아한테크코스_외부_일정_생성_요청); - Category 우아한테크코스 = categoryRepository.getById(우아한테크코스_외부_일정_응답.getId()); - externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); - - // when - MemberScheduleResponses memberScheduleResponses = calendarService.findSchedulesByMemberId(memberId, - new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); - - // then - assertAll(() -> { - assertThat(memberScheduleResponses.getLongTerms()).extracting(MemberScheduleResponse::getTitle) - .contains("장기간 첫번째", "장기간 두번째", "장기간 세번째", "장기간 네번째", "장기간 다섯번째"); - assertThat(memberScheduleResponses.getAllDays()).extracting(MemberScheduleResponse::getTitle) - .contains("종일 첫번째", "종일 두번째", "종일 세번째"); - assertThat(memberScheduleResponses.getFewHours()).extracting(MemberScheduleResponse::getTitle) - .contains("몇시간 첫번째", "몇시간 두번째", "몇시간 세번째", "몇시간 네번째"); - }); - } - // TODO: 외부 일정을 잘 가져오는지를 테스트 해야함 @DisplayName("카테고리를 구독하는 유저들의 모든 내부 일정을 가져온다.") @Test diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java new file mode 100644 index 00000000..57d60285 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java @@ -0,0 +1,118 @@ +package com.allog.dallog.domain.schedule.application; + +import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_외부_일정_생성_요청; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_1분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_18시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_20시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_17시_0분; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.domain.category.application.CategoryService; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; +import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; +import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponse; +import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class SubscribingSchedulesFinderTest extends ServiceTest { + + @Autowired + private SubscribingSchedulesFinder subscribingSchedulesFinder; + + @Autowired + private CategoryService categoryService; + + @Autowired + private ExternalCategoryDetailRepository externalCategoryDetailRepository; + + @Autowired + private SubscriptionService subscriptionService; + + @Autowired + private ScheduleService scheduleService; + + @Autowired + private CategoryRepository categoryRepository; + + @DisplayName("시작일시와 종료일시로 유저의 달력을 일정 유형에 따라 분류하고 정렬하여 반환한다.") + @Test + void 시작일시와_종료일시로_유저의_달력을_일정_유형에_따라_분류하고_정렬하여_반환한다() { + // given + Long memberId = parseMemberId(MEMBER_인증_코드_토큰_요청()); + + CategoryResponse BE_일정_응답 = categoryService.save(memberId, BE_일정_생성_요청); + Category BE_일정 = categoryRepository.getById(BE_일정_응답.getId()); + + /* 장기간 일정 */ + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("장기간 첫번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("장기간 두번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("장기간 세번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("장기간 네번째", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_15일_16시_0분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("장기간 다섯번째", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); + + /* 종일 일정 */ + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); + + /* 몇시간 일정 */ + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("몇시간 첫번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("몇시간 두번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("몇시간 세번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(memberId, BE_일정.getId(), + new ScheduleCreateRequest("몇시간 네번째", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + + CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(memberId, 우아한테크코스_외부_일정_생성_요청); + Category 우아한테크코스 = categoryRepository.getById(우아한테크코스_외부_일정_응답.getId()); + externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); + + // when + MemberScheduleResponses memberScheduleResponses = subscribingSchedulesFinder.findMySubscribingSchedules( + memberId, new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); + + // then + assertAll(() -> { + assertThat(memberScheduleResponses.getLongTerms()).extracting(MemberScheduleResponse::getTitle) + .contains("장기간 첫번째", "장기간 두번째", "장기간 세번째", "장기간 네번째", "장기간 다섯번째"); + assertThat(memberScheduleResponses.getAllDays()).extracting(MemberScheduleResponse::getTitle) + .contains("종일 첫번째", "종일 두번째", "종일 세번째"); + assertThat(memberScheduleResponses.getFewHours()).extracting(MemberScheduleResponse::getTitle) + .contains("몇시간 첫번째", "몇시간 두번째", "몇시간 세번째", "몇시간 네번째"); + }); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java index a768cebd..a341f705 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java @@ -44,7 +44,7 @@ class SubscriptionsTest { new Subscriptions(List.of(공통_일정_구독, BE_일정_구독, 내_일정_구독, 우아한테크코스_일정_구독)); // when & then - assertThat(subscriptions.findCheckedCategoryIdsBy(Category::isInternal)).isEqualTo(List.of(1L, 3L)); + assertThat(subscriptions.findInternalCategory()).isEqualTo(List.of(공통_일정, 내_일정)); } @DisplayName("체크된 카테고리 중 외부 카테고리의 아이디를 찾는다.") @@ -66,7 +66,7 @@ class SubscriptionsTest { new Subscriptions(List.of(공통_일정_구독, BE_일정_구독, 내_일정_구독, 우아한테크코스_일정_구독)); // when & then - assertThat(subscriptions.findCheckedCategoryIdsBy(Category::isExternal)).isEqualTo(List.of(4L)); + assertThat(subscriptions.findExternalCategory()).isEqualTo(List.of(우아한테크코스_일정)); } @DisplayName("특정 스케줄의 구독 색상을 찾는다.") diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index f148fee0..0b53d97f 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -35,6 +35,7 @@ import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; import com.allog.dallog.domain.schedule.application.ScheduleService; +import com.allog.dallog.domain.schedule.application.SubscribingSchedulesFinder; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponse; @@ -68,6 +69,9 @@ class ScheduleControllerTest extends ControllerTest { @MockBean private CalendarService calendarService; + @MockBean + private SubscribingSchedulesFinder subscribingSchedulesFinder; + @MockBean private IntegrationScheduleDao integrationScheduleDao; @@ -347,7 +351,7 @@ class ScheduleControllerTest extends ControllerTest { MemberScheduleResponses memberScheduleResponses = new MemberScheduleResponses(List.of(장기간_일정_1, 장기간_일정_2), List.of(종일_일정_1, 종일_일정_2), List.of(짧은_일정_1, 짧은_일정_2)); - given(calendarService.findSchedulesByMemberId(any(), any())) + given(subscribingSchedulesFinder.findMySubscribingSchedules(any(), any())) .willReturn(memberScheduleResponses); // when & then From 6a72c796c23d4e4c90e89592c2ed5bb056556c8a Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 00:50:23 +0900 Subject: [PATCH 090/148] =?UTF-8?q?test:=20=EC=9B=94=EB=B3=84=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=A1=B0=ED=9A=8C=20=EB=A1=9C=EC=A7=81=EC=97=90=20?= =?UTF-8?q?=ED=95=84=EC=9A=94=ED=95=9C=20=EB=88=84=EB=9D=BD=EB=90=9C=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/fixtures/CategoryFixtures.java | 2 + .../ExternalCategoryDetailServiceTest.java | 56 +++++++++++++++++++ .../application/ScheduleServiceTest.java | 31 ++++++++++ 3 files changed, 89 insertions(+) create mode 100644 backend/src/test/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailServiceTest.java diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java index 9fbc05e7..3e2b8496 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java @@ -21,10 +21,12 @@ public class CategoryFixtures { /* BE 일정 카테고리 */ public static final String BE_일정_이름 = "BE 일정"; public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, NORMAL); + public static final CategoryCreateRequest 외부_BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, GOOGLE); /* FE 일정 카테고리 */ public static final String FE_일정_이름 = "FE 일정"; public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, NORMAL); + public static final CategoryCreateRequest 외부_FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, GOOGLE); /* 매트 아고라 카테고리 */ public static final String 매트_아고라_이름 = "매트 아고라"; diff --git a/backend/src/test/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailServiceTest.java new file mode 100644 index 00000000..07c58429 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/ExternalCategoryDetailServiceTest.java @@ -0,0 +1,56 @@ +package com.allog.dallog.domain.category.application; + +import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.외부_BE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.외부_FE_일정_생성_요청; +import static org.assertj.core.api.Assertions.assertThat; + +import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class ExternalCategoryDetailServiceTest extends ServiceTest { + + @Autowired + private ExternalCategoryDetailService externalCategoryDetailService; + + @Autowired + private CategoryService categoryService; + + @Autowired + private ExternalCategoryDetailRepository externalCategoryDetailRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @DisplayName("월별 일정 조회 시, 유저 ID로 해당하는 외부 연동 카테고리 전체를 조회한다.") + @Test + void 월별_일정_조회_시_유저_ID로_해당하는_외부_연동_카테고리의_전체를_조회한다() { + // given + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + + CategoryResponse 외부_BE_일정_응답 = categoryService.save(리버_id, 외부_BE_일정_생성_요청); + Category 외부_BE_일정 = categoryRepository.getById(외부_BE_일정_응답.getId()); + + CategoryResponse 외부_FE_일정_응답 = categoryService.save(리버_id, 외부_FE_일정_생성_요청); + Category 외부_FE_일정 = categoryRepository.getById(외부_FE_일정_응답.getId()); + + externalCategoryDetailRepository.save(new ExternalCategoryDetail(외부_BE_일정, "1111111")); + externalCategoryDetailRepository.save(new ExternalCategoryDetail(외부_FE_일정, "2222222")); + + // when + List details = externalCategoryDetailService.findByMemberId(리버_id); + + // then + assertThat(details).hasSize(2); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index 9158b058..4066612f 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -11,11 +11,13 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_시작일시; import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_제목; import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_종료일시; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_메모; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_시작일시; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_제목; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_종료일시; +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; @@ -23,14 +25,18 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.application.CategoryService; +import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import java.time.LocalDateTime; +import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -185,6 +191,31 @@ class ScheduleServiceTest extends ServiceTest { assertThatThrownBy(() -> scheduleService.findById(잘못된_아이디)); } + @DisplayName("월별 일정 조회 시, 통합일정 정보를 반환한다.") + @Test + void 월별_일정_조회_시_통합일정_정보를_반환한다() { + // given + Long 리버_id = parseMemberId(리버_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(리버_id, BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(리버_id, BE_일정.getId(), 알록달록_회의_생성_요청); + ScheduleResponse 알록달록_회식 = scheduleService.save(리버_id, BE_일정.getId(), 알록달록_회식_생성_요청); + + // when + List schedules = scheduleService.findByMemberIdAndDateRange(리버_id, + new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); + + // then + assertThat(schedules).hasSize(2); + assertAll( + () -> { + assertThat(schedules.get(0).getId()).isEqualTo(String.valueOf(알록달록_회의.getId())); + assertThat(schedules.get(0).getCategoryType()).isEqualTo(NORMAL); + assertThat(schedules.get(1).getId()).isEqualTo(String.valueOf(알록달록_회식.getId())); + assertThat(schedules.get(1).getCategoryType()).isEqualTo(NORMAL); + } + ); + } + @DisplayName("일정을 수정한다.") @Test void 일정을_수정한다() { From 60b39e96b3d06e9b8e551cc528224f1db796eb95 Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 01:08:30 +0900 Subject: [PATCH 091/148] =?UTF-8?q?refactor:=20ScheduleService.findInterna?= =?UTF-8?q?lByMemberIdAndDateRange=EB=A1=9C=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dallog/domain/schedule/application/ScheduleService.java | 2 +- .../schedule/application/SubscribingSchedulesFinder.java | 3 ++- .../domain/schedule/application/ScheduleServiceTest.java | 3 +-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index ad18b190..b1f8d81b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -50,7 +50,7 @@ public ScheduleResponse findById(final Long id) { return new ScheduleResponse(schedule); } - public List findByMemberIdAndDateRange(final Long memberId, final DateRangeRequest dateRange) { + public List findInternalByMemberIdAndDateRange(final Long memberId, final DateRangeRequest dateRange) { Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); return integrationSchedules(dateRange, subscriptions); } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java index 661f811f..def83076 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java @@ -48,7 +48,8 @@ public MemberScheduleResponses findMySubscribingSchedules(final Long memberId, f List schedules = new ArrayList<>(); List externalSchedules = findExternalSchedules(memberId, dateRange); - List internalSchedules = scheduleService.findByMemberIdAndDateRange(memberId, dateRange); + List internalSchedules = scheduleService.findInternalByMemberIdAndDateRange(memberId, + dateRange); schedules.addAll(externalSchedules); schedules.addAll(internalSchedules); diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index 4066612f..73c2a018 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -25,7 +25,6 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.application.CategoryService; -import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; @@ -201,7 +200,7 @@ class ScheduleServiceTest extends ServiceTest { ScheduleResponse 알록달록_회식 = scheduleService.save(리버_id, BE_일정.getId(), 알록달록_회식_생성_요청); // when - List schedules = scheduleService.findByMemberIdAndDateRange(리버_id, + List schedules = scheduleService.findInternalByMemberIdAndDateRange(리버_id, new DateRangeRequest("2022-07-01T00:00", "2022-08-15T23:59")); // then From 01c31f5087eade238715879cae3dcf6f0888fa1d Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 01:51:28 +0900 Subject: [PATCH 092/148] =?UTF-8?q?refactor:=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=BB=A4=EB=B2=84=EB=A6=AC=EC=A7=80=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/CalendarService.java | 21 +++++----- .../application/CalendarServiceTest.java | 17 ++++++++- .../domain/SubscriptionsTest.java | 38 +++++++++++++++++++ 3 files changed, 64 insertions(+), 12 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index 0472b30d..2bb08d60 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -2,16 +2,17 @@ import com.allog.dallog.domain.auth.application.OAuthClient; import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; -import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -25,18 +26,20 @@ public class CalendarService { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; private final IntegrationScheduleDao integrationScheduleDao; + private final ScheduleService scheduleService; private final SubscriptionRepository subscriptionRepository; private final ExternalCategoryDetailRepository externalCategoryDetailRepository; private final OAuthTokenRepository oAuthTokenRepository; private final OAuthClient oAuthClient; private final ExternalCalendarClient externalCalendarClient; - public CalendarService(final IntegrationScheduleDao integrationScheduleDao, + public CalendarService(final IntegrationScheduleDao integrationScheduleDao, final ScheduleService scheduleService, final SubscriptionRepository subscriptionRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final OAuthTokenRepository oAuthTokenRepository, final OAuthClient oAuthClient, final ExternalCalendarClient externalCalendarClient) { this.integrationScheduleDao = integrationScheduleDao; + this.scheduleService = scheduleService; this.subscriptionRepository = subscriptionRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; this.oAuthTokenRepository = oAuthTokenRepository; @@ -52,19 +55,17 @@ public List getSchedulesBySubscriberIds(final List su .collect(Collectors.toList()); } + // TODO: 코드 커버리지를 위해서 불필요하게 변경하였습니다. 다음 issue에서 처리할 예정입니다😊 private List getIntegrationSchedulesByMemberId(final Long memberId, final DateRangeRequest dateRange) { - Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); - List internalCategory = subscriptions.findInternalCategory(); + List integrationSchedules = new ArrayList<>(); - // todo : SchedulerService를 리팩터링할때 제거 될 진행할 예정입니다. - List internalCategoryIds = internalCategory.stream() - .map(Category::getId) - .collect(Collectors.toList()); + List internalSchedules = scheduleService.findInternalByMemberIdAndDateRange( + memberId, dateRange); - List integrationSchedules = integrationScheduleDao.findByCategoryIdInAndBetween( - internalCategoryIds, dateRange.getStartDateTime(), dateRange.getEndDateTime()); + integrationSchedules.addAll(internalSchedules); + Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); List externalCategoryDetails = findCheckedExternalCategoryDetails(subscriptions); if (!externalCategoryDetails.isEmpty()) { integrationSchedules.addAll(getExternalSchedules(memberId, dateRange, externalCategoryDetails)); diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index df7a0c6f..01c0692c 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -8,6 +8,7 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.외부_BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; @@ -26,7 +27,9 @@ import com.allog.dallog.common.annotation.ServiceTest; import com.allog.dallog.domain.category.application.CategoryService; +import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; @@ -70,6 +73,11 @@ class CalendarServiceTest extends ServiceTest { CategoryResponse BE_일정 = categoryService.save(관리자_id, BE_일정_생성_요청); CategoryResponse FE_일정 = categoryService.save(관리자_id, FE_일정_생성_요청); + CategoryResponse 외부_BE_일정_응답 = categoryService.save(관리자_id, 외부_BE_일정_생성_요청); + Category 외부_BE_일정 = categoryRepository.getById(외부_BE_일정_응답.getId()); + + externalCategoryDetailRepository.save(new ExternalCategoryDetail(외부_BE_일정, "11111111")); + /* 카테고리에 일정 추가 */ scheduleService.save(관리자_id, 공통_일정.getId(), new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); @@ -103,13 +111,18 @@ class CalendarServiceTest extends ServiceTest { subscriptionService.save(매트_id, FE_일정.getId()); subscriptionService.save(리버_id, FE_일정.getId()); + subscriptionService.save(후디_id, 외부_BE_일정.getId()); + subscriptionService.save(파랑_id, 외부_BE_일정.getId()); + subscriptionService.save(매트_id, 외부_BE_일정.getId()); + subscriptionService.save(리버_id, 외부_BE_일정.getId()); + // when List actual = calendarService.getSchedulesBySubscriberIds( List.of(후디_id, 파랑_id, 매트_id, 리버_id), new DateRangeRequest("2022-07-07T16:00", "2022-08-15T14:00")); // then assertThat(actual.stream().map(IntegrationSchedule::getTitle)) - .containsExactly("공통 일정 1", "공통 일정 2", "공통 일정 3", "백엔드 일정 1", "백엔드 일정 2", "백엔드 일정 3", "프론트엔드 일정 1", - "프론트엔드 일정 2"); + .contains("공통 일정 1", "공통 일정 2", "공통 일정 3", "백엔드 일정 1", "백엔드 일정 2", "백엔드 일정 3", "프론트엔드 일정 1", + "포수타", "레벨3 방학", "프론트엔드 일정 2"); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java index a341f705..8aed8cff 100644 --- a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionsTest.java @@ -106,4 +106,42 @@ class SubscriptionsTest { // when & then assertThatThrownBy(() -> subscriptions.findColor(달록_여행)).isInstanceOf(NoSuchCategoryException.class); } + + @DisplayName("구독한 카테고리중 내부 카테고리를 찾아 반환한다.") + @Test + void 구독한_카테고리중_내부_카테고리를_찾아_반환한다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + setId(BE_일정(파랑), 2L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + + Subscriptions subscriptions = new Subscriptions(List.of(공통_일정_구독)); + + // when + List categories = subscriptions.findInternalCategory(); + + // then + assertThat(categories).hasSize(1); + } + + @DisplayName("구독한 카테고리중 외부 카테고리를 찾아 반환한다.") + @Test + void 구독한_카테고리중_외부_카테고리를_찾아_반환한다() { + // given + Member 파랑 = 파랑(); + Category 공통_일정 = setId(공통_일정(파랑), 1L); + setId(BE_일정(파랑), 2L); + + Subscription 공통_일정_구독 = new Subscription(파랑, 공통_일정, COLOR_1); + + Subscriptions subscriptions = new Subscriptions(List.of(공통_일정_구독)); + + // when + List categories = subscriptions.findExternalCategory(); + + // then + assertThat(categories).hasSize(0); + } } From c4b716a9f67b90a0af7c5679e5208bb6150aaafa Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 16:36:55 +0900 Subject: [PATCH 093/148] =?UTF-8?q?refactor:=20PR=EC=97=90=20=EB=8C=80?= =?UTF-8?q?=ED=95=9C=20=EB=A6=AC=EB=B7=B0=20=EC=82=AC=ED=95=AD=20=EB=B0=98?= =?UTF-8?q?=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../composition/application/CalendarService.java | 7 ++----- .../application/SubscribingSchedulesFinder.java | 12 ++++++------ .../domain/schedule/domain/ScheduleRepository.java | 5 +++-- 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index 2bb08d60..c53123bb 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -5,9 +5,8 @@ import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; -import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.application.ScheduleService; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; @@ -25,7 +24,6 @@ public class CalendarService { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; - private final IntegrationScheduleDao integrationScheduleDao; private final ScheduleService scheduleService; private final SubscriptionRepository subscriptionRepository; private final ExternalCategoryDetailRepository externalCategoryDetailRepository; @@ -33,12 +31,11 @@ public class CalendarService { private final OAuthClient oAuthClient; private final ExternalCalendarClient externalCalendarClient; - public CalendarService(final IntegrationScheduleDao integrationScheduleDao, final ScheduleService scheduleService, + public CalendarService(final ScheduleService scheduleService, final SubscriptionRepository subscriptionRepository, final ExternalCategoryDetailRepository externalCategoryDetailRepository, final OAuthTokenRepository oAuthTokenRepository, final OAuthClient oAuthClient, final ExternalCalendarClient externalCalendarClient) { - this.integrationScheduleDao = integrationScheduleDao; this.scheduleService = scheduleService; this.subscriptionRepository = subscriptionRepository; this.externalCategoryDetailRepository = externalCategoryDetailRepository; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java index def83076..3712e4cd 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java @@ -66,15 +66,15 @@ private List findExternalSchedules(final Long memberId, fin return schedules; } - String accessToken = getGoogleAccessToken(memberId); + String accessToken = getExternalAccessToken(memberId); for (ExternalCategoryDetail detail : details) { - List googleSchedules = findGoogleSchedules(dateRange, accessToken, detail); - schedules.addAll(googleSchedules); + List externalSchedules = findExternalSchedules(dateRange, accessToken, detail); + schedules.addAll(externalSchedules); } return schedules; } - private String getGoogleAccessToken(final Long memberId) { + private String getExternalAccessToken(final Long memberId) { OAuthToken oAuthToken = oAuthTokenRepository.getByMemberId(memberId); String refreshToken = oAuthToken.getRefreshToken(); @@ -82,8 +82,8 @@ private String getGoogleAccessToken(final Long memberId) { return accessTokenResponse.getValue(); } - private List findGoogleSchedules(final DateRangeRequest dateRange, final String accessToken, - final ExternalCategoryDetail detail) { + private List findExternalSchedules(final DateRangeRequest dateRange, final String accessToken, + final ExternalCategoryDetail detail) { LocalDateTime startDateTime = dateRange.getStartDateTime(); LocalDateTime endDateTime = dateRange.getEndDateTime(); diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java index 1de4ee38..1fd22bf6 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -27,8 +27,9 @@ default Schedule getById(final Long id) { .orElseThrow(NoSuchScheduleException::new); } - default List createByCategoryAndBetween( - final Category category, final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + default List createByCategoryAndBetween(final Category category, + final LocalDateTime startDateTime, + final LocalDateTime endDateTime) { List schedules = findByCategoryAndBetween(category, startDateTime, endDateTime); return schedules.stream() From f1d938914ce4cacb16069149bf17c95d3232fe8c Mon Sep 17 00:00:00 2001 From: hyeonic Date: Mon, 19 Sep 2022 21:53:07 +0900 Subject: [PATCH 094/148] =?UTF-8?q?fix:=20=EB=A7=A4=ED=95=91=EB=90=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=80=20=EC=A0=95=EB=B3=B4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/response/OAuthAccessTokenResponse.java | 10 +++++----- .../composition/application/CalendarService.java | 4 ++-- .../application/ExternalCalendarService.java | 2 +- .../application/SubscribingSchedulesFinder.java | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java index 5cb88730..41d3ab64 100644 --- a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java @@ -6,16 +6,16 @@ @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) public class OAuthAccessTokenResponse { - private String value; + private String accessToken; private OAuthAccessTokenResponse() { } - public OAuthAccessTokenResponse(final String value) { - this.value = value; + public OAuthAccessTokenResponse(final String accessToken) { + this.accessToken = accessToken; } - public String getValue() { - return value; + public String getAccessToken() { + return accessToken; } } diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index c53123bb..5b53d15b 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -5,8 +5,8 @@ import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; -import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; @@ -83,7 +83,7 @@ private List getExternalSchedules(final Long memberId, fina String refreshToken = oAuthTokenRepository.getByMemberId(memberId) .getRefreshToken(); String accessToken = oAuthClient.getAccessToken(refreshToken) - .getValue(); + .getAccessToken(); return externalCategories.stream() .flatMap(externalCategory -> flatIntegrationSchedules(request, accessToken, externalCategory)) diff --git a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java index 3f1fa44e..267b826f 100644 --- a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java @@ -30,7 +30,7 @@ public ExternalCalendarsResponse findByMemberId(final Long memberId) { .orElseThrow(NoSuchOAuthTokenException::new); String oAuthAccessToken = oAuthClient.getAccessToken(oAuthToken.getRefreshToken()) - .getValue(); + .getAccessToken(); List externalCalendars = externalCalendarClient.getExternalCalendars(oAuthAccessToken); diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java index 3712e4cd..2bce1e35 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java @@ -79,7 +79,7 @@ private String getExternalAccessToken(final Long memberId) { String refreshToken = oAuthToken.getRefreshToken(); OAuthAccessTokenResponse accessTokenResponse = oAuthClient.getAccessToken(refreshToken); - return accessTokenResponse.getValue(); + return accessTokenResponse.getAccessToken(); } private List findExternalSchedules(final DateRangeRequest dateRange, final String accessToken, From 9dfc749e7b206f558b452d13aac308e8d2246966 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Mon, 19 Sep 2022 00:46:31 +0900 Subject: [PATCH 095/148] =?UTF-8?q?test:=20test=20profile=20=EC=A7=80?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/logback-spring.xml | 12 ++++++++++++ .../com/allog/dallog/acceptance/AcceptanceTest.java | 2 ++ .../dallog/common/annotation/RepositoryTest.java | 2 ++ .../allog/dallog/common/annotation/ServiceTest.java | 2 ++ .../dao/IntegrationScheduleDaoTest.java | 2 ++ .../allog/dallog/presentation/ControllerTest.java | 2 ++ 6 files changed, 22 insertions(+) diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml index cfcc2772..b7676f92 100644 --- a/backend/src/main/resources/logback-spring.xml +++ b/backend/src/main/resources/logback-spring.xml @@ -36,6 +36,18 @@ + + + + + + + + + + + + diff --git a/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java index 77baa342..e37c3138 100644 --- a/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java +++ b/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java @@ -7,8 +7,10 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExternalApiConfig.class) +@ActiveProfiles("test") abstract class AcceptanceTest { @LocalServerPort diff --git a/backend/src/test/java/com/allog/dallog/common/annotation/RepositoryTest.java b/backend/src/test/java/com/allog/dallog/common/annotation/RepositoryTest.java index d2370dcd..0ba9fef3 100644 --- a/backend/src/test/java/com/allog/dallog/common/annotation/RepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/common/annotation/RepositoryTest.java @@ -3,8 +3,10 @@ import com.allog.dallog.global.config.JpaConfig; import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; @DataJpaTest @Import(JpaConfig.class) +@ActiveProfiles("test") public class RepositoryTest { } diff --git a/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java b/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java index 529b758d..53be6d1f 100644 --- a/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java @@ -8,8 +8,10 @@ import org.junit.jupiter.api.BeforeEach; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest(classes = ExternalApiConfig.class) +@ActiveProfiles("test") public class ServiceTest { @Autowired diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java index 56ccf579..034bace6 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java @@ -50,9 +50,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; @SpringBootTest @Import(ExternalApiConfig.class) +@ActiveProfiles("test") class IntegrationScheduleDaoTest { @Autowired diff --git a/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java index fac37efe..8a389d49 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java @@ -5,10 +5,12 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs; import org.springframework.context.annotation.Import; +import org.springframework.test.context.ActiveProfiles; import org.springframework.test.web.servlet.MockMvc; @AutoConfigureRestDocs @Import(ExternalApiConfig.class) +@ActiveProfiles("test") abstract class ControllerTest { @Autowired From eb42f2c0a168ecbae9ad5523301044b07bbfb838 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Mon, 19 Sep 2022 00:46:44 +0900 Subject: [PATCH 096/148] =?UTF-8?q?test:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/test/resources/application.yml | 50 ---------------------- 1 file changed, 50 deletions(-) delete mode 100644 backend/src/test/resources/application.yml diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml deleted file mode 100644 index a00ea450..00000000 --- a/backend/src/test/resources/application.yml +++ /dev/null @@ -1,50 +0,0 @@ -spring: - main: - allow-bean-definition-overriding: true - - datasource: - url: jdbc:h2:~/dallog;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE - username: sa - - sql: - init: - mode: NEVER - - jpa: - properties: - hibernate: - format_sql: true - show-sql: true - - hibernate: - ddl-auto: create - - data: - web: - pageable: - max-page-size: 100 - -logging: - level: - org.hibernate.type.descriptor.sql.BasicBinder: TRACE - -oauth: - google: - client-id: hyeonic - client-secret: 123 - oauth-end-point: https://accounts.google.com/o/oauth2/v2/auth - response-type: code - scopes: - - https://www.googleapis.com/auth/userinfo.profile - - https://www.googleapis.com/auth/userinfo.email - token-uri: https://oauth2.googleapis.com/token - -cors: - allow-origin: - urls: http://localhost:3000 - -security: - jwt: - token: - secret-key: fsmjgbdafmjgbasmfgadbsgmadfhgbfamjghbvmssdgsdfgdf - expire-length: 3600000 From 2a4b9f447ab34deb873ef37a9ea6f392c7c266aa Mon Sep 17 00:00:00 2001 From: hyeonic Date: Mon, 19 Sep 2022 00:46:50 +0900 Subject: [PATCH 097/148] =?UTF-8?q?feat:=20=EC=84=9C=EB=B8=8C=EB=AA=A8?= =?UTF-8?q?=EB=93=88=20=EC=B5=9C=EC=8B=A0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/main/resources/config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index 553c1765..6c3579eb 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit 553c17653bab46e2ebd0ff2afd45e05518ba337d +Subproject commit 6c3579eb524ce2a0f9ffc8d601c861a7254da7ee From d0ded34837c6a6f73b24a32d88de5c3a66d49fc5 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Mon, 19 Sep 2022 07:46:36 +0900 Subject: [PATCH 098/148] =?UTF-8?q?feat:=20test=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=84=9C=EB=B8=8C=EB=AA=A8=EB=93=88=20=EC=99=B8=EB=B6=80?= =?UTF-8?q?=EB=A1=9C=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-test.yml | 46 +++++++++++++++++++ backend/src/main/resources/config | 2 +- backend/src/test/resources/application.yml | 3 ++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 backend/src/main/resources/application-test.yml create mode 100644 backend/src/test/resources/application.yml diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml new file mode 100644 index 00000000..7e34287b --- /dev/null +++ b/backend/src/main/resources/application-test.yml @@ -0,0 +1,46 @@ +spring: + main: + allow-bean-definition-overriding: true + + datasource: + url: jdbc:h2:~/dallog;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE + username: sa + + sql: + init: + mode: NEVER + + jpa: + properties: + hibernate: + format_sql: true + show-sql: true + + hibernate: + ddl-auto: create + + data: + web: + pageable: + max-page-size: 100 + +oauth: + google: + client-id: hyeonic + client-secret: 123 + oauth-end-point: https://accounts.google.com/o/oauth2/v2/auth + response-type: code + scopes: + - https://www.googleapis.com/auth/userinfo.profile + - https://www.googleapis.com/auth/userinfo.email + token-uri: https://oauth2.googleapis.com/token + +cors: + allow-origin: + urls: http://localhost:3000 + +security: + jwt: + token: + secret-key: fsmjgbdafmjgbasmfgadbsgmadfhgbfamjghbvmssdgsdfgdf + expire-length: 3600000 diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index 6c3579eb..553c1765 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit 6c3579eb524ce2a0f9ffc8d601c861a7254da7ee +Subproject commit 553c17653bab46e2ebd0ff2afd45e05518ba337d diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 00000000..03c30d37 --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: test From a9f8512c66a092533e6375f87a725e508c795798 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 20 Sep 2022 01:12:38 +0900 Subject: [PATCH 099/148] =?UTF-8?q?refactor:=20integrationSchedule=20?= =?UTF-8?q?=EB=B6=88=ED=95=84=EC=9A=94=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/build.gradle | 1 - .../dao/IntegrationScheduleDao.java | 62 --- .../dao/IntegrationScheduleDaoTest.java | 363 ------------------ .../presentation/ScheduleControllerTest.java | 20 - 4 files changed, 446 deletions(-) delete mode 100644 backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java delete mode 100644 backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java diff --git a/backend/build.gradle b/backend/build.gradle index a58d4cc9..940d613a 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -24,7 +24,6 @@ repositories { } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-jdbc' 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' diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java deleted file mode 100644 index 9eb50780..00000000 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.allog.dallog.domain.integrationschedule.dao; - -import com.allog.dallog.domain.category.domain.CategoryType; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.Period; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.List; -import org.springframework.jdbc.core.RowMapper; -import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository -public class IntegrationScheduleDao { - - private final NamedParameterJdbcTemplate jdbcTemplate; - - public IntegrationScheduleDao(final NamedParameterJdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } - - public List findByCategoryIdInAndBetween(final List categoryIds, - final LocalDateTime startDateTime, - final LocalDateTime endDateTime) { - if (categoryIds.isEmpty()) { - return new ArrayList<>(); - } - - String sql = "SELECT s.id as id, c.id as categoryId, s.title as title, " - + "s.start_date_time as startDateTime, s.end_date_time as endDateTime, " - + "s.memo as memo, c.category_type as categoryType " - + "FROM schedules s " - + "JOIN categories c ON s.categories_id = c.id " - + "WHERE c.id IN (:categoryIds) " - + "AND s.start_date_time <= :endDateTime " - + "AND s.end_date_time >= :startDateTime "; - - MapSqlParameterSource parameterSource = new MapSqlParameterSource() - .addValue("categoryIds", categoryIds) - .addValue("startDateTime", startDateTime.toString()) - .addValue("endDateTime", endDateTime.toString()); - - return jdbcTemplate.query(sql, parameterSource, generateIntegrationSchedule()); - } - - private RowMapper generateIntegrationSchedule() { - return (resultSet, rowNum) -> { - Long id = resultSet.getLong("id"); - Long categoryId = resultSet.getLong("categoryId"); - String title = resultSet.getString("title"); - LocalDateTime startDateTime = resultSet.getTimestamp("startDateTime").toLocalDateTime(); - LocalDateTime endDateTime = resultSet.getTimestamp("endDateTime").toLocalDateTime(); - String memo = resultSet.getString("memo"); - CategoryType categoryType = CategoryType.from((resultSet.getString("categoryType"))); - - Period period = new Period(startDateTime, endDateTime); - - return new IntegrationSchedule(String.valueOf(id), categoryId, title, period, memo, categoryType); - }; - } -} diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java deleted file mode 100644 index 034bace6..00000000 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java +++ /dev/null @@ -1,363 +0,0 @@ -package com.allog.dallog.domain.integrationschedule.dao; - -import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; -import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정; -import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; -import static com.allog.dallog.common.fixtures.CategoryFixtures.매트_아고라; -import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; -import static com.allog.dallog.common.fixtures.MemberFixtures.후디; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_1분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_18시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_20시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_17일_23시_59분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_11시_59분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_11시_59분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_17시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_23시_59분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_메모; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_제목; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.매고라_메모; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.매고라_제목; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_메모; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_제목; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_메모; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_제목; -import static org.assertj.core.api.Assertions.assertThat; - -import com.allog.dallog.common.config.ExternalApiConfig; -import com.allog.dallog.domain.category.domain.Category; -import com.allog.dallog.domain.category.domain.CategoryRepository; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.member.domain.Member; -import com.allog.dallog.domain.member.domain.MemberRepository; -import com.allog.dallog.domain.schedule.domain.Schedule; -import com.allog.dallog.domain.schedule.domain.ScheduleRepository; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.List; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.Import; -import org.springframework.test.context.ActiveProfiles; - -@SpringBootTest -@Import(ExternalApiConfig.class) -@ActiveProfiles("test") -class IntegrationScheduleDaoTest { - - @Autowired - private MemberRepository memberRepository; - - @Autowired - private ScheduleRepository scheduleRepository; - - @Autowired - private CategoryRepository categoryRepository; - - @Autowired - private IntegrationScheduleDao integrationScheduleDao; - - @DisplayName("카테코리와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") - @Test - void 카테고리와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(List.of(BE_일정.getId()), - 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분); - - // then - assertThat(actual).hasSize(1); - } - - @DisplayName("조회하기 위한 category id의 크기가 0인 경우 빈 리스트를 반환한다.") - @Test - void 조회하기_위한_category_id의_크기가_0인_경우_빈_리스트를_반환한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - List categoryIds = Collections.emptyList(); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_31일_0시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(0); - } - - @DisplayName("카테고리가 여러 개 일 때, 카테고리와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") - @Test - void 카테고리가_여러_개_일_때_카테고리와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - Category FE_일정 = categoryRepository.save(FE_일정(관리자)); - Category 매트_아고라 = categoryRepository.save(매트_아고라(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - Schedule 레벨_인터뷰 = new Schedule(FE_일정, 레벨_인터뷰_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 레벨_인터뷰_메모); - - Schedule 매고라 = new Schedule(매트_아고라, 매고라_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 매고라_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - scheduleRepository.save(레벨_인터뷰); - scheduleRepository.save(매고라); - - List categoryIds = List.of(BE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_31일_0시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(1); - } - - @DisplayName("카테고리가 여러 개 일 때, 카테고리 id 리스트와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") - @Test - void 카테고리가_여러_개_일_때_카테고리_id_리스트와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - Category FE_일정 = categoryRepository.save(FE_일정(관리자)); - Category 매트_아고라 = categoryRepository.save(매트_아고라(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - Schedule 레벨_인터뷰 = new Schedule(FE_일정, 레벨_인터뷰_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 레벨_인터뷰_메모); - - Schedule 매고라 = new Schedule(매트_아고라, 매고라_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 매고라_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - scheduleRepository.save(레벨_인터뷰); - scheduleRepository.save(매고라); - - List categoryIds = List.of(BE_일정.getId(), FE_일정.getId(), 매트_아고라.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_31일_0시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(3); - } - - @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 시작날짜가 종료일시와 같으면 조회한다.") - @Test - void 시작일시와_종료일시를_전달할_때_일정의_시작일시와_같으면_조회된다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - List categoryIds = List.of(BE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_15일_16시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(1); - } - - @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 시작날짜가 종료일시 이후이면 조회되지 않는다.") - @Test - void 카테고리와_시작일시_종료일시를_전달할_때_일정의_시작날짜가_종료일시_이후이면_조회되지_않는다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - List categoryIds = List.of(BE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_7일_16시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(0); - } - - @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 종료날짜가 시작일시와 같으면 조회된다.") - @Test - void 카테고리와_시작일시와_종료일시를_전달할_때_일정의_종료날짜가_시작일시와_같으면_조회된다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - List categoryIds = List.of(BE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_16일_16시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_31일_0시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(1); - } - - @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 종료날짜가 시작일시 이전이면 조회되지 않는다.") - @Test - void 카테고리와_시작일시와_종료일시를_전달할_때_일정의_종료날짜가_시작일시_이전이면_조회되지_않는다() { - // given - Member 관리자 = memberRepository.save(관리자()); - - Category BE_일정 = categoryRepository.save(BE_일정(관리자)); - - Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); - Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); - - scheduleRepository.save(알록달록_회의); - scheduleRepository.save(알록달록_회식); - - List categoryIds = List.of(BE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_7일_16시_0분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).hasSize(0); - } - - @DisplayName("시작일시와 종료일시로 특정 카테고리의 일정을 조회한다.") - @Test - void 시작일시와_종료일시로_특정_카테고리의_일정을_조회한다() { - // given - Member 후디 = memberRepository.save(후디()); - - Category BE_일정 = categoryRepository.save(BE_일정(후디)); - Category FE_일정 = categoryRepository.save(FE_일정(후디)); - Category 공통_일정 = categoryRepository.save(공통_일정(후디)); - - /* BE 일정 */ - scheduleRepository.save(new Schedule(BE_일정, "BE 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); - scheduleRepository.save(new Schedule(BE_일정, "BE 2", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); - scheduleRepository.save(new Schedule(BE_일정, "BE 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - - /* FE 일정 */ - scheduleRepository.save(new Schedule(FE_일정, "FE 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleRepository.save(new Schedule(FE_일정, "FE 2", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); - scheduleRepository.save(new Schedule(FE_일정, "FE 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - - /* 공통 일정 */ - scheduleRepository.save(new Schedule(공통_일정, "공통 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - scheduleRepository.save(new Schedule(공통_일정, "공통 2", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); - scheduleRepository.save(new Schedule(공통_일정, "공통 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); - - List categoryIds = List.of(BE_일정.getId(), FE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_8월_15일_23시_59분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual) - .extracting(IntegrationSchedule::getTitle) - .containsOnly("BE 1", "BE 2", "BE 3", "FE 1", "FE 2", "FE 3"); - } - - @DisplayName("시작일시와 종료일시로 특정 카테고리의 일정을 조회할 때 범위 밖의 일정은 제외된다.") - @Test - void 시작일시와_종료일시로_특정_카테고리의_일정을_조회할_때_범위_밖의_일정은_제외된다() { - // given - Member 후디 = memberRepository.save(후디()); - - Category BE_일정 = categoryRepository.save(BE_일정(후디)); - Category FE_일정 = categoryRepository.save(FE_일정(후디)); - - /* BE 일정 */ - scheduleRepository.save(new Schedule(BE_일정, "BE 1 포함", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); - scheduleRepository.save(new Schedule(BE_일정, "BE 2 포함", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); - scheduleRepository.save(new Schedule(BE_일정, "BE 3 포함", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); - scheduleRepository.save(new Schedule(BE_일정, "BE 3 미포함", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); - - /* FE 일정 */ - scheduleRepository.save(new Schedule(FE_일정, "FE 1 포함", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); - scheduleRepository.save(new Schedule(FE_일정, "FE 2 포함", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); - scheduleRepository.save(new Schedule(FE_일정, "FE 3 미포함", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); - - List categoryIds = List.of(BE_일정.getId(), FE_일정.getId()); - LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; - LocalDateTime endDateTime = 날짜_2022년_7월_17일_23시_59분; - - // when - List actual = integrationScheduleDao.findByCategoryIdInAndBetween(categoryIds, - startDateTime, endDateTime); - - // then - assertThat(actual).extracting(IntegrationSchedule::getTitle) - .containsOnly("BE 1 포함", "BE 2 포함", "BE 3 포함", "FE 1 포함", "FE 2 포함"); - } -} diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index 0b53d97f..c56ac772 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -27,13 +27,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.composition.application.CalendarService; -import com.allog.dallog.domain.composition.application.SchedulerService; -import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; -import com.allog.dallog.domain.integrationschedule.dao.IntegrationScheduleDao; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.application.SubscribingSchedulesFinder; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; @@ -57,27 +52,12 @@ class ScheduleControllerTest extends ControllerTest { private static final String AUTHORIZATION_HEADER_NAME = "Authorization"; private static final String AUTHORIZATION_HEADER_VALUE = "Bearer aaaaaaaa.bbbbbbbb.cccccccc"; - @MockBean - private AuthService authService; - @MockBean private ScheduleService scheduleService; - @MockBean - private SchedulerService schedulerService; - - @MockBean - private CalendarService calendarService; - @MockBean private SubscribingSchedulesFinder subscribingSchedulesFinder; - @MockBean - private IntegrationScheduleDao integrationScheduleDao; - - @MockBean - private ExternalCalendarClient externalCalendarClient; - @DisplayName("일정 정보를 등록하면 상태코드 201을 반환한다.") @Test void 일정_정보를_등록하면_상태코드_201을_반환한다() throws Exception { From f4cb8bbf6730f6d83c00ea20af7bbca44899fd66 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 20 Sep 2022 01:14:26 +0900 Subject: [PATCH 100/148] =?UTF-8?q?refactor:=20schedule=20=EB=B0=8F=20inte?= =?UTF-8?q?gration=20schedule=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/composition/application/CalendarService.java | 2 +- .../domain/composition/application/SchedulerService.java | 4 ++-- .../externalcalendar/application/ExternalCalendarClient.java | 2 +- .../dallog/domain/schedule/application/ScheduleService.java | 5 +++-- .../schedule/application/SubscribingSchedulesFinder.java | 4 ++-- .../domain/IntegrationSchedule.java | 3 +-- .../domain/IntegrationScheduleComparator.java | 2 +- .../domain/IntegrationSchedules.java | 2 +- .../{integrationschedule => schedule}/domain/Period.java | 2 +- .../dallog/domain/schedule/domain/ScheduleRepository.java | 1 - .../domain/ScheduleType.java | 2 +- .../domain/TypedSchedules.java | 2 +- .../dallog/domain/schedule/domain/scheduler/Scheduler.java | 4 ++-- .../domain/schedule/dto/response/MemberScheduleResponse.java | 2 +- .../schedule/dto/response/MemberScheduleResponses.java | 4 ++-- .../dallog/domain/schedule/dto/response/PeriodResponse.java | 2 +- .../dallog/domain/subscription/domain/Subscriptions.java | 2 +- .../oauth/client/GoogleExternalCalendarClient.java | 2 +- .../dallog/common/fixtures/IntegrationScheduleFixtures.java | 4 ++-- .../domain/composition/application/CalendarServiceTest.java | 2 +- .../domain/schedule/application/ScheduleServiceTest.java | 2 +- .../domain/IntegrationScheduleTest.java | 2 +- .../domain/IntegrationSchedulesTest.java | 2 +- .../{integrationschedule => schedule}/domain/PeriodTest.java | 2 +- .../domain/schedule/domain/scheduler/SchedulerTest.java | 4 ++-- .../oauth/client/StubExternalCalendarClient.java | 2 +- .../allog/dallog/presentation/ScheduleControllerTest.java | 4 ++++ .../allog/dallog/presentation/SchedulerControllerTest.java | 2 +- 28 files changed, 38 insertions(+), 35 deletions(-) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/IntegrationSchedule.java (96%) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/IntegrationScheduleComparator.java (95%) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/IntegrationSchedules.java (90%) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/Period.java (98%) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/ScheduleType.java (93%) rename backend/src/main/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/TypedSchedules.java (93%) rename backend/src/test/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/IntegrationScheduleTest.java (98%) rename backend/src/test/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/IntegrationSchedulesTest.java (98%) rename backend/src/test/java/com/allog/dallog/domain/{integrationschedule => schedule}/domain/PeriodTest.java (99%) diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java index 5b53d15b..9111b40e 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -5,8 +5,8 @@ import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.application.ScheduleService; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; diff --git a/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java index ad1a09b1..e2f2797c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java @@ -1,9 +1,9 @@ package com.allog.dallog.domain.composition.application; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.Period; import com.allog.dallog.domain.member.application.MemberService; import com.allog.dallog.domain.member.dto.MemberResponse; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.Period; import com.allog.dallog.domain.schedule.domain.scheduler.Scheduler; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; diff --git a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarClient.java index d550a16e..92fdb991 100644 --- a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarClient.java @@ -1,7 +1,7 @@ package com.allog.dallog.domain.externalcalendar.application; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import java.util.List; public interface ExternalCalendarClient { diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index b1f8d81b..dedac171 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -2,9 +2,9 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryRepository; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.domain.Schedule; import com.allog.dallog.domain.schedule.domain.ScheduleRepository; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; @@ -50,7 +50,8 @@ public ScheduleResponse findById(final Long id) { return new ScheduleResponse(schedule); } - public List findInternalByMemberIdAndDateRange(final Long memberId, final DateRangeRequest dateRange) { + public List findInternalByMemberIdAndDateRange(final Long memberId, + final DateRangeRequest dateRange) { Subscriptions subscriptions = new Subscriptions(subscriptionRepository.findByMemberId(memberId)); return integrationSchedules(dateRange, subscriptions); } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java index 2bce1e35..9b5ec284 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinder.java @@ -7,8 +7,8 @@ import com.allog.dallog.domain.category.application.ExternalCategoryDetailService; import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.TypedSchedules; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java similarity index 96% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java index 86be874e..cbad8379 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java @@ -1,8 +1,7 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.domain.CategoryType; -import com.allog.dallog.domain.schedule.domain.Schedule; import com.allog.dallog.domain.subscription.domain.Subscription; import java.time.LocalDateTime; import java.util.Objects; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleComparator.java similarity index 95% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleComparator.java index 37f8bcff..e0731edc 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleComparator.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import java.time.LocalDateTime; import java.util.Comparator; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedules.java similarity index 90% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedules.java index b6d42044..257bbd3d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedules.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import java.util.ArrayList; import java.util.List; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java similarity index 98% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java index 0f233d40..7386fba5 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import java.time.LocalDate; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java index 1fd22bf6..2e72efe8 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -1,7 +1,6 @@ package com.allog.dallog.domain.schedule.domain; import com.allog.dallog.domain.category.domain.Category; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import java.time.LocalDateTime; import java.util.List; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleType.java similarity index 93% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleType.java index 1605103c..eedebc3c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleType.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import java.util.Arrays; import java.util.function.Predicate; diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/TypedSchedules.java similarity index 93% rename from backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java rename to backend/src/main/java/com/allog/dallog/domain/schedule/domain/TypedSchedules.java index 135f9031..4199f832 100644 --- a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/TypedSchedules.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import java.util.HashMap; import java.util.List; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/scheduler/Scheduler.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/scheduler/Scheduler.java index 4b39a6e8..c644c0cb 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/scheduler/Scheduler.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/scheduler/Scheduler.java @@ -1,7 +1,7 @@ package com.allog.dallog.domain.schedule.domain.scheduler; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.Period; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java index fdc90341..3d1925a2 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java @@ -1,6 +1,6 @@ package com.allog.dallog.domain.schedule.dto.response; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.domain.subscription.domain.Color; import java.time.LocalDateTime; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java index 16b35a1a..579b4319 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java @@ -1,7 +1,7 @@ package com.allog.dallog.domain.schedule.dto.response; -import com.allog.dallog.domain.integrationschedule.domain.ScheduleType; -import com.allog.dallog.domain.integrationschedule.domain.TypedSchedules; +import com.allog.dallog.domain.schedule.domain.ScheduleType; +import com.allog.dallog.domain.schedule.domain.TypedSchedules; import com.allog.dallog.domain.subscription.domain.Subscriptions; import java.util.List; import java.util.stream.Collectors; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/PeriodResponse.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/PeriodResponse.java index 3d79d5e0..7c13cf1c 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/PeriodResponse.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/PeriodResponse.java @@ -1,6 +1,6 @@ package com.allog.dallog.domain.schedule.dto.response; -import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.domain.Period; import java.time.LocalDateTime; public class PeriodResponse { diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java index 2cef1610..3b85cea2 100644 --- a/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscriptions.java @@ -2,7 +2,7 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import java.util.List; import java.util.stream.Collectors; diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java index 392cc4dc..e5cd7875 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -3,7 +3,7 @@ import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarEventResponse; import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarEventsResponse; import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarListResponse; diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java index 1ef07d4d..28704445 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java @@ -3,8 +3,8 @@ import static com.allog.dallog.domain.category.domain.CategoryType.GOOGLE; import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.Period; import java.time.LocalDateTime; public class IntegrationScheduleFixtures { diff --git a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java index 01c0692c..ff88f9dd 100644 --- a/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -32,7 +32,7 @@ import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; import com.allog.dallog.domain.category.dto.response.CategoryResponse; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.application.ScheduleService; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index 73c2a018..08a2854e 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -27,7 +27,7 @@ import com.allog.dallog.domain.category.application.CategoryService; import com.allog.dallog.domain.category.dto.response.CategoryResponse; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java similarity index 98% rename from backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java rename to backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java index bc26cdda..248da1a8 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_메모; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_시작일시; diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedulesTest.java similarity index 98% rename from backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java rename to backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedulesTest.java index 5b505327..506f41b9 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedulesTest.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; import static org.assertj.core.api.Assertions.assertThat; diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java similarity index 99% rename from backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java rename to backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java index 42fe1469..a6a288bb 100644 --- a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java @@ -1,4 +1,4 @@ -package com.allog.dallog.domain.integrationschedule.domain; +package com.allog.dallog.domain.schedule.domain; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java index 9f8c3021..8ad61de1 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java @@ -22,8 +22,8 @@ import static org.junit.jupiter.api.Assertions.assertAll; import com.allog.dallog.domain.category.domain.Category; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; -import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.Period; import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayName; diff --git a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubExternalCalendarClient.java b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubExternalCalendarClient.java index d26f7013..9551ce56 100644 --- a/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubExternalCalendarClient.java +++ b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubExternalCalendarClient.java @@ -8,7 +8,7 @@ import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; -import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import java.util.List; public class StubExternalCalendarClient implements ExternalCalendarClient { diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index c56ac772..24d50f2b 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -27,6 +27,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import com.allog.dallog.domain.schedule.application.ScheduleService; @@ -52,6 +53,9 @@ class ScheduleControllerTest extends ControllerTest { private static final String AUTHORIZATION_HEADER_NAME = "Authorization"; private static final String AUTHORIZATION_HEADER_VALUE = "Bearer aaaaaaaa.bbbbbbbb.cccccccc"; + @MockBean + private AuthService authService; + @MockBean private ScheduleService scheduleService; diff --git a/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java index d261232a..21f7effd 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java @@ -22,7 +22,7 @@ import com.allog.dallog.domain.auth.application.AuthService; import com.allog.dallog.domain.composition.application.SchedulerService; -import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.domain.Period; import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; import java.util.List; import org.junit.jupiter.api.DisplayName; From 2e94af5d8ce2cec7a91438fbfc43d79f4dbac480 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Tue, 20 Sep 2022 00:32:44 +0900 Subject: [PATCH 101/148] =?UTF-8?q?refactor:=20=EA=B5=AC=EA=B8=80=20?= =?UTF-8?q?=EC=BA=98=EB=A6=B0=EB=8D=94=20=EC=9D=BC=EC=A0=95=EC=9D=B4=20?= =?UTF-8?q?=EC=A2=85=EC=9D=BC=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=20-1=EB=B6=84?= =?UTF-8?q?=20=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client/GoogleExternalCalendarClient.java | 25 ++----------------- .../dto/GoogleCalendarEventResponse.java | 11 ++++++-- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java index e5cd7875..500173ed 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -1,14 +1,11 @@ package com.allog.dallog.infrastructure.oauth.client; -import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; -import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarEventResponse; import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarEventsResponse; import com.allog.dallog.infrastructure.oauth.dto.GoogleCalendarListResponse; import com.allog.dallog.infrastructure.oauth.exception.OAuthException; -import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -62,13 +59,13 @@ public List getExternalCalendarSchedules(final String acces final String startDateTime, final String endDateTime) { HttpEntity request = new HttpEntity<>(generateCalendarRequestHeaders(accessToken)); - Map uriVariables = generateEventsVariables(externalCalendarId, startDateTime, endDateTime); + GoogleCalendarEventsResponse response = fetchGoogleCalendarEvents(request, uriVariables).getBody(); return response.getItems() .stream() - .map(event -> parseIntegrationSchedule(internalCategoryId, event)) + .map(event -> event.toIntegrationSchedule(internalCategoryId)) .collect(Collectors.toList()); } @@ -97,22 +94,4 @@ private ResponseEntity fetchGoogleCalendarEvents( throw new OAuthException(e); } } - - private IntegrationSchedule parseIntegrationSchedule(final Long internalCategoryId, - final GoogleCalendarEventResponse event) { - LocalDateTime startDateTime = event.getStartDateTime(); - LocalDateTime endDateTime = event.getEndDateTime(); - - if (isAllDay(startDateTime, endDateTime)) { - endDateTime = endDateTime.minusMinutes(1); - } - - return new IntegrationSchedule(event.getId(), internalCategoryId, event.getSummary(), startDateTime, - endDateTime, event.getDescription(), CategoryType.GOOGLE); - } - - private boolean isAllDay(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { - return startDateTime.getHour() == 0 && startDateTime.getMinute() == 0 - && endDateTime.getHour() == 0 && endDateTime.getMinute() == 0; - } } diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java index 93a91257..29134127 100644 --- a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java @@ -1,5 +1,7 @@ package com.allog.dallog.infrastructure.oauth.dto; +import com.allog.dallog.domain.category.domain.CategoryType; +import com.allog.dallog.domain.schedule.domain.IntegrationSchedule; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -26,7 +28,12 @@ public GoogleCalendarEventResponse(final String id, final String summary, final this.end = end; } - public LocalDateTime getStartDateTime() { + public IntegrationSchedule toIntegrationSchedule(final Long internalCategoryId) { + return new IntegrationSchedule(id, internalCategoryId, summary, getStartDateTime(), getEndDateTime(), + description, CategoryType.GOOGLE); + } + + private LocalDateTime getStartDateTime() { if (Objects.isNull(start.getDate())) { return LocalDateTime.parse(start.getDateTime().substring(0, 19)); } @@ -34,7 +41,7 @@ public LocalDateTime getStartDateTime() { return LocalDateTime.of(LocalDate.parse(start.getDate()), LocalTime.MIN); } - public LocalDateTime getEndDateTime() { + private LocalDateTime getEndDateTime() { if (Objects.isNull(end.getDate())) { return LocalDateTime.parse(end.getDateTime().substring(0, 19)); } From 99cb2b43dc06ac56c56ce8c29d0565e00217d653 Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Tue, 20 Sep 2022 00:36:08 +0900 Subject: [PATCH 102/148] =?UTF-8?q?refactor:=20=EC=A2=85=EC=9D=BC=EC=9D=98?= =?UTF-8?q?=20=EC=A2=85=EB=A3=8C=EC=8B=9C=EA=B0=84=EC=9D=84=20=EC=9E=90?= =?UTF-8?q?=EC=A0=95=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/domain/IntegrationSchedule.java | 13 ++--- .../dallog/domain/schedule/domain/Period.java | 14 ++---- .../common/fixtures/ScheduleFixtures.java | 3 ++ .../SubscribingSchedulesFinderTest.java | 16 +++--- .../domain/IntegrationScheduleTest.java | 49 +++++++++++++------ .../domain/schedule/domain/PeriodTest.java | 28 +++++------ 6 files changed, 64 insertions(+), 59 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java index cbad8379..b15155f3 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java @@ -9,8 +9,6 @@ public class IntegrationSchedule { private static final int ONE_DAY = 1; - private static final int MIDNIGHT_HOUR = 23; - private static final int MIDNIGHT_MINUTE = 59; private final String id; private final Long categoryId; @@ -45,18 +43,17 @@ public IntegrationSchedule(final String id, final Long categoryId, final String } public boolean isLongTerms() { - return period.calculateDayDifference() >= ONE_DAY; + return !isAllDays() + && period.calculateDayDifference() >= ONE_DAY; } public boolean isAllDays() { - return period.calculateDayDifference() < ONE_DAY - && period.calculateHourDifference() == MIDNIGHT_HOUR - && period.calculateMinuteDifference() == MIDNIGHT_MINUTE; + return period.calculateDayDifference() == ONE_DAY + && period.isMidnightToMidnight(); } public boolean isFewHours() { - return !isAllDays() - && period.calculateDayDifference() < ONE_DAY; + return period.calculateDayDifference() < ONE_DAY; } public boolean isSameCategory(final Subscription subscription) { diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java index 7386fba5..b44a969d 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Period.java @@ -1,5 +1,7 @@ package com.allog.dallog.domain.schedule.domain; +import static java.time.LocalTime.MIDNIGHT; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; @@ -10,8 +12,6 @@ public class Period { - private static final int ONE_HOUR = 60; - private final LocalDateTime startDateTime; private final LocalDateTime endDateTime; @@ -26,16 +26,10 @@ public long calculateDayDifference() { return ChronoUnit.DAYS.between(startDate, endDate); } - public long calculateHourDifference() { - LocalTime startTime = LocalTime.from(startDateTime); - LocalTime endTime = LocalTime.from(endDateTime); - return ChronoUnit.HOURS.between(startTime, endTime); - } - - public long calculateMinuteDifference() { + public boolean isMidnightToMidnight() { LocalTime startTime = LocalTime.from(startDateTime); LocalTime endTime = LocalTime.from(endDateTime); - return ChronoUnit.MINUTES.between(startTime, endTime) % ONE_HOUR; + return startTime.equals(MIDNIGHT) && endTime.equals(MIDNIGHT); } public List slice(final Period otherPeriod) { diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/ScheduleFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/ScheduleFixtures.java index 4f478972..5a83bf63 100644 --- a/backend/src/test/java/com/allog/dallog/common/fixtures/ScheduleFixtures.java +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/ScheduleFixtures.java @@ -13,6 +13,7 @@ public class ScheduleFixtures { public static final LocalDateTime 날짜_2022년_7월_7일_16시_0분 = LocalDateTime.of(2022, 7, 7, 16, 0); public static final LocalDateTime 날짜_2022년_7월_10일_0시_0분 = LocalDateTime.of(2022, 7, 10, 0, 0); public static final LocalDateTime 날짜_2022년_7월_10일_11시_59분 = LocalDateTime.of(2022, 7, 10, 23, 59); + public static final LocalDateTime 날짜_2022년_7월_11일_0시_0분 = LocalDateTime.of(2022, 7, 11, 0, 0); public static final LocalDateTime 날짜_2022년_7월_15일_16시_0분 = LocalDateTime.of(2022, 7, 15, 16, 0); public static final LocalDateTime 날짜_2022년_7월_16일_16시_0분 = LocalDateTime.of(2022, 7, 16, 16, 0); public static final LocalDateTime 날짜_2022년_7월_16일_16시_1분 = LocalDateTime.of(2022, 7, 16, 16, 1); @@ -21,8 +22,10 @@ public class ScheduleFixtures { public static final LocalDateTime 날짜_2022년_7월_17일_23시_59분 = LocalDateTime.of(2022, 7, 17, 23, 59); public static final LocalDateTime 날짜_2022년_7월_20일_0시_0분 = LocalDateTime.of(2022, 7, 20, 0, 0); public static final LocalDateTime 날짜_2022년_7월_20일_11시_59분 = LocalDateTime.of(2022, 7, 20, 23, 59); + public static final LocalDateTime 날짜_2022년_7월_21일_0시_0분 = LocalDateTime.of(2022, 7, 21, 0, 0); public static final LocalDateTime 날짜_2022년_7월_27일_0시_0분 = LocalDateTime.of(2022, 7, 27, 0, 0); public static final LocalDateTime 날짜_2022년_7월_27일_11시_59분 = LocalDateTime.of(2022, 7, 27, 23, 59); + public static final LocalDateTime 날짜_2022년_7월_28일_0시_0분 = LocalDateTime.of(2022, 7, 28, 0, 0); public static final LocalDateTime 날짜_2022년_7월_31일_0시_0분 = LocalDateTime.of(2022, 7, 31, 0, 0); public static final LocalDateTime 날짜_2022년_8월_15일_14시_0분 = LocalDateTime.of(2022, 8, 15, 14, 0); public static final LocalDateTime 날짜_2022년_8월_15일_17시_0분 = LocalDateTime.of(2022, 8, 15, 17, 0); diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java index 57d60285..4ebd97da 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/SubscribingSchedulesFinderTest.java @@ -4,7 +4,7 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.우아한테크코스_외부_일정_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_11일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_1분; @@ -12,9 +12,9 @@ import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_20시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_21일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_0시_0분; -import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_28일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; @@ -33,7 +33,6 @@ import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponse; import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; -import com.allog.dallog.domain.subscription.application.SubscriptionService; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -49,9 +48,6 @@ class SubscribingSchedulesFinderTest extends ServiceTest { @Autowired private ExternalCategoryDetailRepository externalCategoryDetailRepository; - @Autowired - private SubscriptionService subscriptionService; - @Autowired private ScheduleService scheduleService; @@ -81,11 +77,11 @@ class SubscribingSchedulesFinderTest extends ServiceTest { /* 종일 일정 */ scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); + new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_11일_0시_0분, "")); scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); + new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_21일_0시_0분, "")); scheduleService.save(memberId, BE_일정.getId(), - new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); + new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_28일_0시_0분, "")); /* 몇시간 일정 */ scheduleService.save(memberId, BE_일정.getId(), diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java index 248da1a8..23c37701 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/IntegrationScheduleTest.java @@ -27,9 +27,9 @@ class IntegrationScheduleTest { NORMAL)); } - @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 다르면 true를 반환한다.") + @DisplayName("LongTerm인지 확인 할 때, AllDays가 아니고 일정의 시작일과 종료일이 다르면 true를 반환한다.") @Test - void LongTerm인지_확인_할_떄_일정의_시작일시와_종료일시가_다르면_true를_반환한다() { + void LongTerm인지_확인_할_때_AllDays가_아니고_일정의_시작일과_종료일이_다르면_true를_반환한다() { // given String id = "1"; Long categoryId = 1L; @@ -44,9 +44,9 @@ class IntegrationScheduleTest { assertThat(actual).isTrue(); } - @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 같으면 false를 반환한다.") + @DisplayName("LongTerm인지 확인 할 때, 일정의 시작일과 종료일이 같으면 false를 반환한다.") @Test - void LongTerm인지_확인_할_때_일정의_시작일시와_종료일시가_다르면_false를_반환한다() { + void LongTerm인지_확인_할_때_일정의_시작일과_종료일이_같으면_false를_반환한다() { // given String id = "1"; Long categoryId = 1L; @@ -61,15 +61,32 @@ class IntegrationScheduleTest { assertThat(actual).isFalse(); } - @DisplayName("AllDays인지 확인 할 떄, 일정의 시작일시와 종료일시가 같고 자정이면 true를 반환한다.") + @DisplayName("LongTerm인지 확인 할 때, 일정의 시작일과 종료일이 달라도 AllDays면 false를 반환한다.") @Test - void AllDays인지_확인_할_때_일정의_시작일시와_종료일시가_같고_자정이면_true를_반환한다() { + void LongTerm인지_확인_할_때_일정의_시작일과_종료일이_달라도_AllDays면_false를_반환한다() { // given String id = "1"; Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, NORMAL); + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, NORMAL); + + // when + boolean actual = integrationSchedule.isLongTerms(); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("AllDays인지 확인 할 때, 일정의 일차가 하루고 시작시간과 종료시간 모두 자정이면 true를 반환한다.") + @Test + void AllDays인지_확인_할_때_일정의_일차가_하루고_시작시간과_종료시간이_모두_자정이면_true를_반환한다() { + // given + String id = "1"; + Long categoryId = 1L; + IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, + LocalDateTime.of(2022, 7, 1, 0, 0), + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isAllDays(); @@ -78,15 +95,15 @@ class IntegrationScheduleTest { assertThat(actual).isTrue(); } - @DisplayName("AllDays인지 확인 할 떄, 일정의 시작일시와 종료일시가 같지만 자정이 아니면 false를 반환한다.") + @DisplayName("AllDays인지 확인 할 때, 일정의 일차가 하루여도 시작시간과 종료시간이 자정이 아니면 false를 반환한다.") @Test - void AllDays인지_확인_할_때_일정의_시작일시와_종료일시가_같지만_자정이_아니면_false를_반환한다() { + void AllDays인지_확인_할_때_일정의_일차가_하루여도_시작시간과_종료시간이__자정이_아니면_false를_반환한다() { // given String id = "1"; Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, NORMAL); + LocalDateTime.of(2022, 7, 2, 0, 1), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isAllDays(); @@ -95,15 +112,15 @@ class IntegrationScheduleTest { assertThat(actual).isFalse(); } - @DisplayName("FewHours인지 확인 할 떄, 일정의 시작일시와 종료일시가 같고 자정이 아니면 true를 반환한다.") + @DisplayName("FewHours인지 확인 할 때, 일정의 시작일과 종료일이 같으면 true를 반환한다.") @Test - void FewHours인지_확인_할_때_일정의_시작일시와_종료일시가_같고_자정이_아니면_true를_반환한다() { + void FewHours인지_확인_할_때_일정의_시작일과_종료일이_같으면_true를_반환한다() { // given String id = "1"; Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 11, 58), 알록달록_회의_메모, NORMAL); + LocalDateTime.of(2022, 7, 1, 11, 59), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isFewHours(); @@ -112,15 +129,15 @@ class IntegrationScheduleTest { assertThat(actual).isTrue(); } - @DisplayName("FewHours인지 확인 할 떄, 일정의 시작일시와 종료일시가 같지만 자정이면 false를 반환한다.") + @DisplayName("FewHours인지 확인 할 때, 일정의 시작일과 종료일이 다르면 false를 반환한다.") @Test - void FewHours인지_확인_할_때_일정의_시작일시와_종료일시가_같지만_자정이면_false를_반환한다() { + void FewHours인지_확인_할_때_일정의_시작일과_종료일이_다르면_false를_반환한다() { // given String id = "1"; Long categoryId = 1L; IntegrationSchedule integrationSchedule = new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, LocalDateTime.of(2022, 7, 1, 0, 0), - LocalDateTime.of(2022, 7, 1, 23, 59), 알록달록_회의_메모, NORMAL); + LocalDateTime.of(2022, 7, 2, 0, 0), 알록달록_회의_메모, NORMAL); // when boolean actual = integrationSchedule.isFewHours(); diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java index a6a288bb..565d86ed 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/PeriodTest.java @@ -26,36 +26,36 @@ class PeriodTest { assertThat(dayDifference).isEqualTo(2); } - @DisplayName("시작일시와 종료일시의 시간 차이를 반환한다.") + @DisplayName("시작시간과 종료시간이 모두 자정이면 true를 반환한다.") @Test - void 시작일시와_종료일시의_시간_차이를_반환한다() { + void 시작시간과_종료시간이_모두_자정이면_true를_반환한다() { // given - LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 11, 0); - LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 23, 0); + LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 0, 0); + LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 0, 0); Period period = new Period(startDateTime, endDateTime); // when - long hourDifference = period.calculateHourDifference(); + boolean actual = period.isMidnightToMidnight(); // then - assertThat(hourDifference).isEqualTo(12); + assertThat(actual).isTrue(); } - @DisplayName("시작일시와 종료일시의 분 차이를 반환한다.") + @DisplayName("시작시간과 종료시간 중 하나라도 자정이 아니면 false를 반환한다.") @Test - void 시작일시와_종료일시의_분_차이를_반환한다() { + void 시작시간과_종료시간_중_하나라도_자정이_아니면_false를_반환한다() { // given - LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 11, 17); - LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 11, 19); + LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 10, 10); + LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 0, 0); Period period = new Period(startDateTime, endDateTime); // when - long minuteDifference = period.calculateMinuteDifference(); + boolean actual = period.isMidnightToMidnight(); // then - assertThat(minuteDifference).isEqualTo(2); + assertThat(actual).isFalse(); } @DisplayName("기간 뺄셈시 상대 기간이 우측에 걸쳐있을 때의 결과를 계산한다.") @@ -145,9 +145,7 @@ class PeriodTest { List actual = basePeriod.slice(otherPeriod); // then - assertAll(() -> { - assertThat(actual).hasSize(0); - }); + assertThat(actual).hasSize(0); } @DisplayName("기간 뺄셈시 상대 기간과 겹치지 않으면 자기자신을 리스트로 반환한다.") From 6f4fab5b71c938541e66ffa1bae2855159c0cceb Mon Sep 17 00:00:00 2001 From: hyeonic Date: Tue, 20 Sep 2022 13:26:33 +0900 Subject: [PATCH 103/148] =?UTF-8?q?refactor:=20=ED=8A=B9=EC=A0=95=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EC=9D=98=20=EA=B5=AC=EA=B0=84=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/application/ScheduleService.java | 11 +- .../schedule/domain/IntegrationSchedule.java | 7 +- .../schedule/domain/ScheduleRepository.java | 20 +- .../domain/ScheduleRepositoryTest.java | 307 ++++++++++++++++++ 4 files changed, 325 insertions(+), 20 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index dedac171..a6879fe4 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -13,7 +13,6 @@ import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; import com.allog.dallog.domain.subscription.domain.SubscriptionRepository; import com.allog.dallog.domain.subscription.domain.Subscriptions; -import java.util.ArrayList; import java.util.List; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -58,15 +57,9 @@ public List findInternalByMemberIdAndDateRange(final Long m private List integrationSchedules(final DateRangeRequest dateRange, final Subscriptions subscriptions) { - List integrationSchedules = new ArrayList<>(); - List categories = subscriptions.findInternalCategory(); - for (Category category : categories) { - List schedules = scheduleRepository.createByCategoryAndBetween( - category, dateRange.getStartDateTime(), dateRange.getEndDateTime()); - integrationSchedules.addAll(schedules); - } - return integrationSchedules; + return scheduleRepository.getByCategoriesAndBetween(categories, dateRange.getStartDateTime(), + dateRange.getEndDateTime()); } @Transactional diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java index b15155f3..f5e1b073 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/IntegrationSchedule.java @@ -17,13 +17,14 @@ public class IntegrationSchedule { private final String memo; private final CategoryType categoryType; - public IntegrationSchedule(final Schedule schedule, final CategoryType categoryType) { + public IntegrationSchedule(final Schedule schedule) { this.id = String.valueOf(schedule.getId()); - this.categoryId = schedule.getCategory().getId(); + Category category = schedule.getCategory(); + this.categoryId = category.getId(); this.title = schedule.getTitle(); this.period = new Period(schedule.getStartDateTime(), schedule.getEndDateTime()); this.memo = schedule.getMemo(); - this.categoryType = categoryType; + this.categoryType = category.getCategoryType(); } public IntegrationSchedule(final String id, final Long categoryId, final String title, diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java index 2e72efe8..6ba03d94 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -3,6 +3,7 @@ import com.allog.dallog.domain.category.domain.Category; import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; import org.springframework.data.jpa.repository.JpaRepository; @@ -15,24 +16,27 @@ public interface ScheduleRepository extends JpaRepository { @Query("SELECT s " + "FROM Schedule s " + "JOIN s.category c " - + "WHERE c = :category " + + "WHERE c IN :categories " + "AND s.startDateTime <= :endDate " + "AND s.endDateTime >= :startDate") - List findByCategoryAndBetween(final Category category, final LocalDateTime startDate, - final LocalDateTime endDate); + List findByCategoriesAndBetween(final List categories, final LocalDateTime startDate, + final LocalDateTime endDate); default Schedule getById(final Long id) { return this.findById(id) .orElseThrow(NoSuchScheduleException::new); } - default List createByCategoryAndBetween(final Category category, - final LocalDateTime startDateTime, - final LocalDateTime endDateTime) { - List schedules = findByCategoryAndBetween(category, startDateTime, endDateTime); + default List getByCategoriesAndBetween(final List categories, + final LocalDateTime startDateTime, + final LocalDateTime endDateTime) { + if (categories.isEmpty()) { + return new ArrayList<>(); + } + List schedules = findByCategoriesAndBetween(categories, startDateTime, endDateTime); return schedules.stream() - .map(schedule -> new IntegrationSchedule(schedule, category.getCategoryType())) + .map(IntegrationSchedule::new) .collect(Collectors.toList()); } } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleRepositoryTest.java index 3ac9064a..b81d5340 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleRepositoryTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleRepositoryTest.java @@ -3,11 +3,31 @@ import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정; import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; +import static com.allog.dallog.common.fixtures.CategoryFixtures.매트_아고라; import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; +import static com.allog.dallog.common.fixtures.MemberFixtures.후디; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_11시_59분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_16시_1분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_18시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_16일_20시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_17일_23시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_20일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_27일_11시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_31일_0시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_7일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_14시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_17시_0분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_8월_15일_23시_59분; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_메모; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.레벨_인터뷰_제목; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.매고라_메모; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.매고라_제목; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_메모; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회식_제목; import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_메모; @@ -19,6 +39,8 @@ import com.allog.dallog.domain.category.domain.CategoryRepository; import com.allog.dallog.domain.member.domain.Member; import com.allog.dallog.domain.member.domain.MemberRepository; +import java.time.LocalDateTime; +import java.util.Collections; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -75,4 +97,289 @@ class ScheduleRepositoryTest extends RepositoryTest { // then assertThat(scheduleRepository.findAll()).hasSize(0); } + + @DisplayName("카테코리와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") + @Test + void 카테고리와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(List.of(BE_일정), + 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분); + + // then + assertThat(actual).hasSize(1); + } + + @DisplayName("조회하기 위한 category id의 크기가 0인 경우 빈 리스트를 반환한다.") + @Test + void 조회하기_위한_category_id의_크기가_0인_경우_빈_리스트를_반환한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + List categories = Collections.emptyList(); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_31일_0시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(0); + } + + @DisplayName("카테고리가 여러 개 일 때, 카테고리와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") + @Test + void 카테고리가_여러_개_일_때_카테고리와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + Category FE_일정 = categoryRepository.save(FE_일정(관리자)); + Category 매트_아고라 = categoryRepository.save(매트_아고라(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + Schedule 레벨_인터뷰 = new Schedule(FE_일정, 레벨_인터뷰_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 레벨_인터뷰_메모); + + Schedule 매고라 = new Schedule(매트_아고라, 매고라_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 매고라_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + scheduleRepository.save(레벨_인터뷰); + scheduleRepository.save(매고라); + + List categories = List.of(BE_일정); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_31일_0시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, + endDate); + + // then + assertThat(actual).hasSize(1); + } + + @DisplayName("카테고리가 여러 개 일 때, 카테고리 id 리스트와 시작일시, 종료일시를 전달하면 그 사이에 해당하는 일정을 조회한다.") + @Test + void 카테고리가_여러_개_일_때_카테고리_id_리스트와_시작일시_종료일시를_전달하면_그_사이에_해당하는_일정을_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + Category FE_일정 = categoryRepository.save(FE_일정(관리자)); + Category 매트_아고라 = categoryRepository.save(매트_아고라(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + Schedule 레벨_인터뷰 = new Schedule(FE_일정, 레벨_인터뷰_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 레벨_인터뷰_메모); + + Schedule 매고라 = new Schedule(매트_아고라, 매고라_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 매고라_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + scheduleRepository.save(레벨_인터뷰); + scheduleRepository.save(매고라); + + List categories = List.of(BE_일정, FE_일정, 매트_아고라); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_31일_0시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(3); + } + + @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 시작날짜가 종료일시와 같으면 조회한다.") + @Test + void 시작일시와_종료일시를_전달할_때_일정의_시작일시와_같으면_조회된다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + List categories = List.of(BE_일정); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_15일_16시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(1); + } + + @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 시작날짜가 종료일시 이후이면 조회되지 않는다.") + @Test + void 카테고리와_시작일시_종료일시를_전달할_때_일정의_시작날짜가_종료일시_이후이면_조회되지_않는다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + List categories = List.of(BE_일정); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_7일_16시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(0); + } + + @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 종료날짜가 시작일시와 같으면 조회된다.") + @Test + void 카테고리와_시작일시와_종료일시를_전달할_때_일정의_종료날짜가_시작일시와_같으면_조회된다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + List categories = List.of(BE_일정); + LocalDateTime startDate = 날짜_2022년_7월_16일_16시_0분; + LocalDateTime endDate = 날짜_2022년_7월_31일_0시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(1); + } + + @DisplayName("카테고리와 시작일시, 종료일시를 전달할 때 일정의 종료날짜가 시작일시 이전이면 조회되지 않는다.") + @Test + void 카테고리와_시작일시와_종료일시를_전달할_때_일정의_종료날짜가_시작일시_이전이면_조회되지_않는다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + + Schedule 알록달록_회의 = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, 알록달록_회의_메모); + Schedule 알록달록_회식 = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의); + scheduleRepository.save(알록달록_회식); + + List categories = List.of(BE_일정); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_7월_7일_16시_0분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual).hasSize(0); + } + + @DisplayName("시작일시와 종료일시로 특정 카테고리의 일정을 조회한다.") + @Test + void 시작일시와_종료일시로_특정_카테고리의_일정을_조회한다() { + // given + Member 후디 = memberRepository.save(후디()); + + Category BE_일정 = categoryRepository.save(BE_일정(후디)); + Category FE_일정 = categoryRepository.save(FE_일정(후디)); + Category 공통_일정 = categoryRepository.save(공통_일정(후디)); + + /* BE 일정 */ + scheduleRepository.save(new Schedule(BE_일정, "BE 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); + scheduleRepository.save(new Schedule(BE_일정, "BE 2", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); + scheduleRepository.save(new Schedule(BE_일정, "BE 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + + /* FE 일정 */ + scheduleRepository.save(new Schedule(FE_일정, "FE 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleRepository.save(new Schedule(FE_일정, "FE 2", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); + scheduleRepository.save(new Schedule(FE_일정, "FE 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + + /* 공통 일정 */ + scheduleRepository.save(new Schedule(공통_일정, "공통 1", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleRepository.save(new Schedule(공통_일정, "공통 2", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); + scheduleRepository.save(new Schedule(공통_일정, "공통 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + + List categories = List.of(BE_일정, FE_일정); + LocalDateTime startDate = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDate = 날짜_2022년_8월_15일_23시_59분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, startDate, endDate); + + // then + assertThat(actual) + .extracting(IntegrationSchedule::getTitle) + .containsOnly("BE 1", "BE 2", "BE 3", "FE 1", "FE 2", "FE 3"); + } + + @DisplayName("시작일시와 종료일시로 특정 카테고리의 일정을 조회할 때 범위 밖의 일정은 제외된다.") + @Test + void 시작일시와_종료일시로_특정_카테고리의_일정을_조회할_때_범위_밖의_일정은_제외된다() { + // given + Member 후디 = memberRepository.save(후디()); + + Category BE_일정 = categoryRepository.save(BE_일정(후디)); + Category FE_일정 = categoryRepository.save(FE_일정(후디)); + + /* BE 일정 */ + scheduleRepository.save(new Schedule(BE_일정, "BE 1 포함", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); + scheduleRepository.save(new Schedule(BE_일정, "BE 2 포함", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); + scheduleRepository.save(new Schedule(BE_일정, "BE 3 포함", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleRepository.save(new Schedule(BE_일정, "BE 3 미포함", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); + + /* FE 일정 */ + scheduleRepository.save(new Schedule(FE_일정, "FE 1 포함", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleRepository.save(new Schedule(FE_일정, "FE 2 포함", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + scheduleRepository.save(new Schedule(FE_일정, "FE 3 미포함", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); + + List categories = List.of(BE_일정, FE_일정); + LocalDateTime startDateTime = 날짜_2022년_7월_1일_0시_0분; + LocalDateTime endDateTime = 날짜_2022년_7월_17일_23시_59분; + + // when + List actual = scheduleRepository.getByCategoriesAndBetween(categories, + startDateTime, endDateTime); + + // then + assertThat(actual).extracting(IntegrationSchedule::getTitle) + .containsOnly("BE 1 포함", "BE 2 포함", "BE 3 포함", "FE 1 포함", "FE 2 포함"); + } } From c749013967dc92bdb08cf9f0dc1ff354bc31d47e Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Tue, 20 Sep 2022 15:23:14 +0900 Subject: [PATCH 104/148] =?UTF-8?q?fix:=20=EC=A2=85=EC=9D=BC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80=20=EC=9A=94=EC=B2=AD=EC=9D=98=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleAddModal/ScheduleAddModal.tsx | 10 ++++++++-- frontend/src/constants/date.ts | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx index fd7b0640..23856f72 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -23,7 +23,7 @@ import { CACHE_KEY } from '@/constants/api'; import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; -import { getDate, getEndTime, getStartTime } from '@/utils/date'; +import { getDate, getEndTime, getISODateString, getNextDate, getStartTime } from '@/utils/date'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; @@ -93,7 +93,13 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { const body = { title: validationSchedule.title.inputValue, startDateTime: `${validationSchedule.startDate.inputValue}T${validationSchedule.startTime.inputValue}`, - endDateTime: `${validationSchedule.endDate.inputValue}T${validationSchedule.endTime.inputValue}`, + endDateTime: `${ + isAllDay + ? getISODateString( + getNextDate(new Date(validationSchedule.endDate.inputValue), 1).toISOString() + ) + : validationSchedule.endDate.inputValue + }T${validationSchedule.endTime.inputValue}`, memo: validationSchedule.memo.inputValue, }; diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index cb516f48..e3ac37a9 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -2,7 +2,7 @@ import { zeroFill } from '@/utils'; const DATE_TIME = { START: '00:00', - END: '23:59', + END: '00:00', }; const DAYS = ['일', '월', '화', '수', '목', '금', '토']; From f7c7953824221200ca39dd2e7b2b4fd6cd88e671 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Tue, 20 Sep 2022 17:42:47 +0900 Subject: [PATCH 105/148] =?UTF-8?q?fix:=20=EC=A2=85=EC=9D=BC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=EC=9D=98=20?= =?UTF-8?q?=EC=A2=85=EB=A3=8C=20=EC=8B=9C=EA=B0=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleModifyModal/ScheduleModifyModal.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index 85f0a435..cbaadfa7 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -20,7 +20,7 @@ import { CACHE_KEY } from '@/constants/api'; import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; -import { checkAllDay } from '@/utils/date'; +import { checkAllDay, getISODateString, getNextDate } from '@/utils/date'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; @@ -95,8 +95,12 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr startDateTime: `${validationSchedule.startDate.inputValue}T${ isAllDay ? DATE_TIME.START : validationSchedule.startTime.inputValue }`, - endDateTime: `${validationSchedule.endDate.inputValue}T${ - isAllDay ? DATE_TIME.END : validationSchedule.endTime.inputValue + endDateTime: `${ + isAllDay + ? `${getISODateString( + getNextDate(new Date(validationSchedule.endDate.inputValue), 1).toISOString() + )}T${DATE_TIME.END}` + : `${validationSchedule.endDate.inputValue}T${validationSchedule.endTime.inputValue}` }`, memo: validationSchedule.memo.inputValue, }; From da81f9e296c4df639e32133237b8d7f9a3467719 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Tue, 20 Sep 2022 20:05:22 +0900 Subject: [PATCH 106/148] =?UTF-8?q?fix:=20=EB=8B=AC=EB=A0=A5=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=A2=85=EC=9D=BC=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EC=9D=84=20=EA=B7=B8=EB=A6=AC=EB=8A=94=20?= =?UTF-8?q?=EB=B0=A9=EC=8B=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/pages/CalendarPage/CalendarPage.tsx | 59 ++++++++++++------- frontend/src/utils/date.ts | 11 +++- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/frontend/src/pages/CalendarPage/CalendarPage.tsx b/frontend/src/pages/CalendarPage/CalendarPage.tsx index 9d4f8b75..2ceaac24 100644 --- a/frontend/src/pages/CalendarPage/CalendarPage.tsx +++ b/frontend/src/pages/CalendarPage/CalendarPage.tsx @@ -26,13 +26,15 @@ import ScheduleModifyModal from '@/components/ScheduleModifyModal/ScheduleModify import { CALENDAR } from '@/constants'; import { CACHE_KEY } from '@/constants/api'; -import { DAYS } from '@/constants/date'; +import { DATE_TIME, DAYS } from '@/constants/date'; import { TRANSPARENT } from '@/constants/style'; import { + getBeforeDate, getDayFromFormattedDate, getFormattedDate, getISODateString, + getISOTimeString, getThisDate, getThisMonth, getThisYear, @@ -304,7 +306,19 @@ function CalendarPage() { const nowDate = getFormattedDate(info.year, info.month, info.date); const nowDay = getDayFromFormattedDate(nowDate); - if (startDate <= nowDate && nowDate <= endDate && el.priority >= maxView) { + const isAllDay = getISOTimeString(el.schedule.endDateTime).startsWith( + DATE_TIME.END + ); + + if ( + !( + startDate <= nowDate && + (nowDate < endDate || (nowDate == endDate && !isAllDay)) + ) + ) + return; + + if (el.priority >= maxView) { return ( onMouseEnter(el.schedule.id)} - onClick={(e) => handleClickSchedule(e, el.schedule)} - onMouseLeave={onMouseLeave} - > - {(startDate === nowDate || nowDay === 0) && - (el.schedule.title || CALENDAR.EMPTY_TITLE)} -
- ) +
onMouseEnter(el.schedule.id)} + onClick={(e) => handleClickSchedule(e, el.schedule)} + onMouseLeave={onMouseLeave} + > + {(startDate === nowDate || nowDay === 0) && + (el.schedule.title || CALENDAR.EMPTY_TITLE)} +
); })} diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index 0c82bd12..a1db35ba 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -4,7 +4,7 @@ import { DATE_TIME } from '@/constants/date'; import { zeroFill } from '.'; -const checkAllDay = (startDateTime: string | undefined, endDateTime: string | undefined) => { +const checkAllDay = (startDateTime?: string, endDateTime?: string) => { if (startDateTime === undefined || endDateTime === undefined) { return null; } @@ -116,6 +116,10 @@ const getISODateString = (ISOString: string) => { return ISOString.split('T')[0]; }; +const getISOTimeString = (ISOString: string) => { + return ISOString.split('T')[1]; +}; + const getKoreaISOString = (time: number) => { return new Date(time - new Date().getTimezoneOffset() * 60000).toISOString(); }; @@ -132,7 +136,7 @@ const getNextYearMonth = (targetYear: number, targetMonth: number) => { }; const getOneHourEarlierISOString = (ISOString: string) => { - const hour = ISOString.split('T')[1].split(':')[0]; + const hour = getISOTimeString(ISOString).split(':')[0]; const oneHourEarlierISOString = getKoreaISOString(new Date(ISOString).setHours(Number(hour) - 1)) .replace(/\..*/, '') @@ -142,7 +146,7 @@ const getOneHourEarlierISOString = (ISOString: string) => { }; const getOneHourLaterISOString = (ISOString: string) => { - const hour = ISOString.split('T')[1].split(':')[0]; + const hour = getISOTimeString(ISOString).split(':')[0]; const oneHourEarlierISOString = getKoreaISOString(new Date(ISOString).setHours(Number(hour) + 1)) .replace(/\..*/, '') @@ -174,6 +178,7 @@ export { getEndTime, getFormattedDate, getISODateString, + getISOTimeString, getKoreaISOString, getNextDate, getNextYearMonth, From e5235aeabee1d55e50814dc3632084effb4ab842 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Tue, 20 Sep 2022 20:59:03 +0900 Subject: [PATCH 107/148] =?UTF-8?q?style:=20=EB=8B=AC=EB=A0=A5=EC=9D=98=20?= =?UTF-8?q?height=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/CalendarPage/CalendarPage.styles.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/CalendarPage/CalendarPage.styles.ts b/frontend/src/pages/CalendarPage/CalendarPage.styles.ts index 3518010a..319b23cf 100644 --- a/frontend/src/pages/CalendarPage/CalendarPage.styles.ts +++ b/frontend/src/pages/CalendarPage/CalendarPage.styles.ts @@ -3,7 +3,7 @@ import { css, Theme } from '@emotion/react'; import { DAYS } from '@/constants/date'; const calendarPage = css` - padding: 0 5rem; + padding: 0 5rem 5rem; `; const calendarHeader = ({ colors, flex }: Theme) => css` @@ -82,12 +82,14 @@ const navButtonTitle = ({ colors }: Theme) => css` const navBarGrid = css` display: grid; grid-template-columns: repeat(7, calc(100% / 7)); + + height: 7rem; `; const calendarGrid = (rowNum: number) => css` display: grid; grid-template-columns: repeat(7, calc(100% / 7)); - grid-auto-rows: calc(75vh / ${rowNum}); + grid-auto-rows: calc(calc(100vh - 42rem) / ${rowNum}); `; const dayBar = ({ colors }: Theme, day: string) => css` From 9219a150e42fa58c2acebd13c74da9acd15b2652 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 10:22:50 +0900 Subject: [PATCH 108/148] =?UTF-8?q?perf:=20css=20=EC=B5=9C=EC=86=8C?= =?UTF-8?q?=ED=99=94=20=EB=B0=8F=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/webpack.config.js | 11 +- frontend/yarn.lock | 379 ++++++++++++++++++++++++++++++++++++- 3 files changed, 382 insertions(+), 9 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 4d793cf9..8e2fd0d7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -55,6 +55,7 @@ "babel-loader": "^8.2.5", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.7.1", + "css-minimizer-webpack-plugin": "^4.1.0", "eslint": "^8.19.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index bf706698..6e00f064 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -1,8 +1,10 @@ -const HtmlWebpackPlugin = require('html-webpack-plugin'); -const path = require('path'); const webpack = require('webpack'); -const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const path = require('path'); const Dotenv = require('dotenv-webpack'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); + +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const prod = process.env.NODE_ENV === 'production'; @@ -59,4 +61,7 @@ module.exports = { port: 3000, hot: true, }, + optimization: { + minimizer: [new CssMinimizerPlugin()], + }, }; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 69d0c369..58286b50 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2884,6 +2884,11 @@ javascript-natural-sort "0.7.1" lodash "4.17.21" +"@trysound/sax@0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" + integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== + "@types/aria-query@^4.2.0": version "4.2.2" resolved "https://registry.yarnpkg.com/@types/aria-query/-/aria-query-4.2.2.tgz#ed4e0ad92306a704f9fb132a0cfcf77486dbe2bc" @@ -4573,6 +4578,16 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" +browserslist@^4.0.0, browserslist@^4.16.6, browserslist@^4.20.3: + version "4.21.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987" + integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw== + dependencies: + caniuse-lite "^1.0.30001400" + electron-to-chromium "^1.4.251" + node-releases "^2.0.6" + update-browserslist-db "^1.0.9" + browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3: version "4.21.3" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" @@ -4764,6 +4779,21 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== +caniuse-api@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" + integrity sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw== + dependencies: + browserslist "^4.0.0" + caniuse-lite "^1.0.0" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" + +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001400: + version "1.0.30001407" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz#92281a6ee67cb90bfd8a6a1201fcc2dc19b60a15" + integrity sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w== + caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001370: version "1.0.30001390" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001390.tgz#158a43011e7068ef7fc73590e9fd91a7cece5e7f" @@ -5060,6 +5090,11 @@ color-support@^1.1.2: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +colord@^2.9.1: + version "2.9.3" + resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43" + integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw== + colorette@^1.2.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" @@ -5097,7 +5132,7 @@ commander@^6.2.1: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^7.0.0: +commander@^7.0.0, commander@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== @@ -5366,6 +5401,11 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" +css-declaration-sorter@^6.3.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz#be5e1d71b7a992433fb1c542c7a1b835e45682ec" + integrity sha512-fBffmak0bPAnyqc/HO8C3n2sHrp9wcqQz6ES9koRF2/mLOVAx9zIQ3Y7R29sYCteTPqMCwns4WYQoCX91Xl3+w== + css-loader@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" @@ -5415,6 +5455,18 @@ css-loader@^6.7.1: postcss-value-parser "^4.2.0" semver "^7.3.5" +css-minimizer-webpack-plugin@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-4.1.0.tgz#2ab9f7d8148c48f5d498604025e6e62cf9528855" + integrity sha512-Zd+yz4nta4GXi3pMqF6skO8kjzuCUbr62z8SLMGZZtxWxTGTLopOiabPGNDEyjHCRhnhdA1EfHmqLa2Oekjtng== + dependencies: + cssnano "^5.1.8" + jest-worker "^27.5.1" + postcss "^8.4.13" + schema-utils "^4.0.0" + serialize-javascript "^6.0.0" + source-map "^0.6.1" + css-select@^4.1.3: version "4.3.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" @@ -5426,6 +5478,14 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-tree@^1.1.2, css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + css-what@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" @@ -5436,6 +5496,62 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssnano-preset-default@^5.2.12: + version "5.2.12" + resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz#ebe6596ec7030e62c3eb2b3c09f533c0644a9a97" + integrity sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew== + dependencies: + css-declaration-sorter "^6.3.0" + cssnano-utils "^3.1.0" + postcss-calc "^8.2.3" + postcss-colormin "^5.3.0" + postcss-convert-values "^5.1.2" + postcss-discard-comments "^5.1.2" + postcss-discard-duplicates "^5.1.0" + postcss-discard-empty "^5.1.1" + postcss-discard-overridden "^5.1.0" + postcss-merge-longhand "^5.1.6" + postcss-merge-rules "^5.1.2" + postcss-minify-font-values "^5.1.0" + postcss-minify-gradients "^5.1.1" + postcss-minify-params "^5.1.3" + postcss-minify-selectors "^5.2.1" + postcss-normalize-charset "^5.1.0" + postcss-normalize-display-values "^5.1.0" + postcss-normalize-positions "^5.1.1" + postcss-normalize-repeat-style "^5.1.1" + postcss-normalize-string "^5.1.0" + postcss-normalize-timing-functions "^5.1.0" + postcss-normalize-unicode "^5.1.0" + postcss-normalize-url "^5.1.0" + postcss-normalize-whitespace "^5.1.1" + postcss-ordered-values "^5.1.3" + postcss-reduce-initial "^5.1.0" + postcss-reduce-transforms "^5.1.0" + postcss-svgo "^5.1.0" + postcss-unique-selectors "^5.1.1" + +cssnano-utils@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" + integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== + +cssnano@^5.1.8: + version "5.1.13" + resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.1.13.tgz#83d0926e72955332dc4802a7070296e6258efc0a" + integrity sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ== + dependencies: + cssnano-preset-default "^5.2.12" + lilconfig "^2.0.3" + yaml "^1.10.2" + +csso@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" + integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== + dependencies: + css-tree "^1.1.2" + cssom@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" @@ -5831,6 +5947,11 @@ electron-to-chromium@^1.4.202: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.242.tgz#51284820b0e6f6ce6c60d3945a3c4f9e4bd88f5f" integrity sha512-nPdgMWtjjWGCtreW/2adkrB2jyHjClo9PtVhR6rW+oxa4E4Wom642Tn+5LslHP3XPL5MCpkn5/UEY60EXylNeQ== +electron-to-chromium@^1.4.251: + version "1.4.256" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz#c735032f412505e8e0482f147a8ff10cfca45bf4" + integrity sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw== + elliptic@^6.5.3: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -8523,7 +8644,7 @@ jest-worker@^26.5.0, jest-worker@^26.6.2: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^27.4.5: +jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== @@ -8754,6 +8875,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lilconfig@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" + integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -8825,12 +8951,17 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.memoize@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" + integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.uniq@4.5.0: +lodash.uniq@4.5.0, lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== @@ -8995,6 +9126,11 @@ mdast-util-to-string@^1.0.0: resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + mdurl@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -9485,6 +9621,11 @@ normalize-range@^0.1.2: resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== +normalize-url@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a" + integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A== + npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" @@ -10119,6 +10260,52 @@ posix-character-classes@^0.1.0: resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== +postcss-calc@^8.2.3: + version "8.2.4" + resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-8.2.4.tgz#77b9c29bfcbe8a07ff6693dc87050828889739a5" + integrity sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q== + dependencies: + postcss-selector-parser "^6.0.9" + postcss-value-parser "^4.2.0" + +postcss-colormin@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.3.0.tgz#3cee9e5ca62b2c27e84fce63affc0cfb5901956a" + integrity sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + colord "^2.9.1" + postcss-value-parser "^4.2.0" + +postcss-convert-values@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz#31586df4e184c2e8890e8b34a0b9355313f503ab" + integrity sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g== + dependencies: + browserslist "^4.20.3" + postcss-value-parser "^4.2.0" + +postcss-discard-comments@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz#8df5e81d2925af2780075840c1526f0660e53696" + integrity sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ== + +postcss-discard-duplicates@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz#9eb4fe8456706a4eebd6d3b7b777d07bad03e848" + integrity sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw== + +postcss-discard-empty@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz#e57762343ff7f503fe53fca553d18d7f0c369c6c" + integrity sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A== + +postcss-discard-overridden@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz#7e8c5b53325747e9d90131bb88635282fb4a276e" + integrity sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw== + postcss-flexbugs-fixes@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.2.1.tgz#9218a65249f30897deab1033aced8578562a6690" @@ -10137,6 +10324,56 @@ postcss-loader@^4.2.0: schema-utils "^3.0.0" semver "^7.3.4" +postcss-merge-longhand@^5.1.6: + version "5.1.6" + resolved "https://registry.yarnpkg.com/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz#f378a8a7e55766b7b644f48e5d8c789ed7ed51ce" + integrity sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw== + dependencies: + postcss-value-parser "^4.2.0" + stylehacks "^5.1.0" + +postcss-merge-rules@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz#7049a14d4211045412116d79b751def4484473a5" + integrity sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + cssnano-utils "^3.1.0" + postcss-selector-parser "^6.0.5" + +postcss-minify-font-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz#f1df0014a726083d260d3bd85d7385fb89d1f01b" + integrity sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-minify-gradients@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz#f1fe1b4f498134a5068240c2f25d46fcd236ba2c" + integrity sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw== + dependencies: + colord "^2.9.1" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-params@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz#ac41a6465be2db735099bbd1798d85079a6dc1f9" + integrity sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg== + dependencies: + browserslist "^4.16.6" + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-minify-selectors@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz#d4e7e6b46147b8117ea9325a915a801d5fe656c6" + integrity sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg== + dependencies: + postcss-selector-parser "^6.0.5" + postcss-modules-extract-imports@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-2.0.0.tgz#818719a1ae1da325f9832446b01136eeb493cd7e" @@ -10198,7 +10435,93 @@ postcss-modules-values@^4.0.0: dependencies: icss-utils "^5.0.0" -postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: +postcss-normalize-charset@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz#9302de0b29094b52c259e9b2cf8dc0879879f0ed" + integrity sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg== + +postcss-normalize-display-values@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz#72abbae58081960e9edd7200fcf21ab8325c3da8" + integrity sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-positions@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz#ef97279d894087b59325b45c47f1e863daefbb92" + integrity sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-repeat-style@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz#e9eb96805204f4766df66fd09ed2e13545420fb2" + integrity sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-string@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz#411961169e07308c82c1f8c55f3e8a337757e228" + integrity sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-timing-functions@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz#d5614410f8f0b2388e9f240aa6011ba6f52dafbb" + integrity sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-normalize-unicode@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz#3d23aede35e160089a285e27bf715de11dc9db75" + integrity sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ== + dependencies: + browserslist "^4.16.6" + postcss-value-parser "^4.2.0" + +postcss-normalize-url@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz#ed9d88ca82e21abef99f743457d3729a042adcdc" + integrity sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew== + dependencies: + normalize-url "^6.0.1" + postcss-value-parser "^4.2.0" + +postcss-normalize-whitespace@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz#08a1a0d1ffa17a7cc6efe1e6c9da969cc4493cfa" + integrity sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-ordered-values@^5.1.3: + version "5.1.3" + resolved "https://registry.yarnpkg.com/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz#b6fd2bd10f937b23d86bc829c69e7732ce76ea38" + integrity sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ== + dependencies: + cssnano-utils "^3.1.0" + postcss-value-parser "^4.2.0" + +postcss-reduce-initial@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz#fc31659ea6e85c492fb2a7b545370c215822c5d6" + integrity sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw== + dependencies: + browserslist "^4.16.6" + caniuse-api "^3.0.0" + +postcss-reduce-transforms@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz#333b70e7758b802f3dd0ddfe98bb1ccfef96b6e9" + integrity sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.5, postcss-selector-parser@^6.0.9: version "6.0.10" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== @@ -10206,6 +10529,21 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector cssesc "^3.0.0" util-deprecate "^1.0.2" +postcss-svgo@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/postcss-svgo/-/postcss-svgo-5.1.0.tgz#0a317400ced789f233a28826e77523f15857d80d" + integrity sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA== + dependencies: + postcss-value-parser "^4.2.0" + svgo "^2.7.0" + +postcss-unique-selectors@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz#a9f273d1eacd09e9aa6088f4b0507b18b1b541b6" + integrity sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA== + dependencies: + postcss-selector-parser "^6.0.5" + postcss-value-parser@^4.1.0, postcss-value-parser@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" @@ -10219,7 +10557,7 @@ postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0 picocolors "^0.2.1" source-map "^0.6.1" -postcss@^8.2.15, postcss@^8.4.7: +postcss@^8.2.15, postcss@^8.4.13, postcss@^8.4.7: version "8.4.16" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.16.tgz#33a1d675fac39941f5f445db0de4db2b6e01d43c" integrity sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ== @@ -11776,6 +12114,14 @@ style-to-object@0.3.0, style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" +stylehacks@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.1.0.tgz#a40066490ca0caca04e96c6b02153ddc39913520" + integrity sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q== + dependencies: + browserslist "^4.16.6" + postcss-selector-parser "^6.0.4" + stylis@4.0.13: version "4.0.13" resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" @@ -11815,6 +12161,19 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +svgo@^2.7.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" + integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^4.1.3" + css-tree "^1.1.3" + csso "^4.2.0" + picocolors "^1.0.0" + stable "^0.1.8" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -12396,6 +12755,14 @@ update-browserslist-db@^1.0.5: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.9: + version "1.0.9" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz#2924d3927367a38d5c555413a7ce138fc95fcb18" + integrity sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -13014,7 +13381,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml@^1.10.0, yaml@^1.7.2: +yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== From bc662d03514e775429f8026487529d914a8a6c7d Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 10:33:05 +0900 Subject: [PATCH 109/148] =?UTF-8?q?perf:=20production=20mode=EC=97=90?= =?UTF-8?q?=EC=84=9C=20css=20=ED=8C=8C=EC=9D=BC=20=EA=B0=9C=EB=B3=84=20?= =?UTF-8?q?=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/webpack.config.js | 4 +++- frontend/yarn.lock | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 8e2fd0d7..c8338701 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -66,6 +66,7 @@ "html-webpack-plugin": "^5.5.0", "jest": "^28.1.3", "jest-environment-jsdom": "^28.1.3", + "mini-css-extract-plugin": "^2.6.1", "msw": "^0.43.0", "prettier": "^2.7.1", "react-icons": "^4.4.0", diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 6e00f064..1e2a13aa 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -5,6 +5,7 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const prod = process.env.NODE_ENV === 'production'; @@ -26,7 +27,7 @@ module.exports = { }, { test: /\\.css$/, - use: ['style-loader', 'css-loader'], + use: [prod ? MiniCssExtractPlugin.loader : 'style-loader', 'css-loader'], }, { test: /\.(png|jp(e*)g|gif)$/, @@ -55,6 +56,7 @@ module.exports = { }), new CleanWebpackPlugin(), new Dotenv(), + new MiniCssExtractPlugin(), ], devServer: { historyApiFallback: true, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 58286b50..5e1f6d96 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -9304,6 +9304,13 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== +mini-css-extract-plugin@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-2.6.1.tgz#9a1251d15f2035c342d99a468ab9da7a0451b71e" + integrity sha512-wd+SD57/K6DiV7jIR34P+s3uckTRuQvx0tKPcvjFlrEylk6P4mQ2KSWk1hblj1Kxaqok7LogKOieygXqBczNlg== + dependencies: + schema-utils "^4.0.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" From 7a7cd8981dee31c2adaf15352f74ca9684c16027 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 12:33:30 +0900 Subject: [PATCH 110/148] =?UTF-8?q?perf:=20=EB=B2=88=EB=93=A4=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/webpack.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 1e2a13aa..06e0ea86 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -64,6 +64,6 @@ module.exports = { hot: true, }, optimization: { - minimizer: [new CssMinimizerPlugin()], + minimizer: ['...', new CssMinimizerPlugin()], }, }; From 101d91c56a5e63c04bcc2feb957d4c7d5ae66d92 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 12:41:28 +0900 Subject: [PATCH 111/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MyCategoryItem/MyCategoryItem.style.ts | 63 ----------- .../MyCategoryItem/MyCategoryItem.tsx | 101 ------------------ .../MyCategoryList.fallback.tsx | 33 ------ .../MyCategoryList/MyCategoryList.styles.ts | 47 -------- .../MyCategoryList/MyCategoryList.tsx | 41 ------- 5 files changed, 285 deletions(-) delete mode 100644 frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts delete mode 100644 frontend/src/components/MyCategoryItem/MyCategoryItem.tsx delete mode 100644 frontend/src/components/MyCategoryList/MyCategoryList.fallback.tsx delete mode 100644 frontend/src/components/MyCategoryList/MyCategoryList.styles.ts delete mode 100644 frontend/src/components/MyCategoryList/MyCategoryList.tsx diff --git a/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts b/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts deleted file mode 100644 index 587c27cd..00000000 --- a/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { css, Theme } from '@emotion/react'; - -const buttonStyle = ({ colors }: Theme) => css` - position: relative; - - width: 8rem; - height: 8rem; - - background: transparent; - - color: ${colors.GRAY_700}; - - &:hover { - border-radius: 50%; - - background: ${colors.GRAY_100}; - - filter: none; - } - - &:hover span { - visibility: visible; - } -`; - -const menuTitle = ({ colors }: Theme) => css` - visibility: hidden; - position: absolute; - top: 120%; - left: 50%; - transform: translateX(-50%); - - padding: 2rem 3rem; - - background: ${colors.GRAY_700}ee; - - font-size: 3rem; - font-weight: normal; - color: ${colors.WHITE}; - white-space: nowrap; -`; - -const categoryItemStyle = ({ colors, flex }: Theme) => css` - ${flex.row} - - justify-content: space-around; - - height: 20rem; - border-bottom: 1px solid ${colors.GRAY_400}; - - font-size: 4rem; -`; - -const itemStyle = css` - flex: 1 1 0; - text-align: center; -`; - -const grayTextStyle = ({ colors }: Theme) => css` - color: ${colors.GRAY_600}; -`; - -export { buttonStyle, categoryItemStyle, grayTextStyle, itemStyle, menuTitle }; diff --git a/frontend/src/components/MyCategoryItem/MyCategoryItem.tsx b/frontend/src/components/MyCategoryItem/MyCategoryItem.tsx deleted file mode 100644 index e041a8dd..00000000 --- a/frontend/src/components/MyCategoryItem/MyCategoryItem.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { useMutation, useQueryClient } from 'react-query'; -import { useRecoilValue } from 'recoil'; - -import useToggle from '@/hooks/useToggle'; - -import { CategoryType } from '@/@types/category'; - -import { userState } from '@/recoil/atoms'; - -import Button from '@/components/@common/Button/Button'; -import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; -import CategoryModifyModal from '@/components/CategoryModifyModal/CategoryModifyModal'; - -import { CACHE_KEY } from '@/constants/api'; -import { CATEGORY_TYPE } from '@/constants/category'; -import { CONFIRM_MESSAGE, TOOLTIP_MESSAGE } from '@/constants/message'; - -import { getISODateString } from '@/utils/date'; - -import categoryApi from '@/api/category'; - -import { FiEdit3 } from 'react-icons/fi'; -import { RiDeleteBin6Line } from 'react-icons/ri'; - -import { - buttonStyle, - categoryItemStyle, - grayTextStyle, - itemStyle, - menuTitle, -} from './MyCategoryItem.style'; - -interface MyCategoryItemProps { - category: CategoryType; -} - -function MyCategoryItem({ category }: MyCategoryItemProps) { - const { accessToken } = useRecoilValue(userState); - - const { state: isCategoryModifyModalOpen, toggleState: toggleCategoryModifyModalOpen } = - useToggle(); - - const queryClient = useQueryClient(); - const { mutate } = useMutation(() => categoryApi.delete(accessToken, category.id), { - onSuccess: () => onSuccessDeleteCategory(), - }); - - const handleClickDeleteButton = () => { - if (confirm(CONFIRM_MESSAGE.DELETE)) { - mutate(); - } - }; - - const onSuccessDeleteCategory = () => { - queryClient.invalidateQueries(CACHE_KEY.CATEGORIES); - queryClient.invalidateQueries(CACHE_KEY.MY_CATEGORIES); - queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); - }; - - const canEditCategory = category.categoryType !== CATEGORY_TYPE.PERSONAL; - - return ( -
- {getISODateString(category.createdAt)} - - {category.name} - - {category.categoryType === CATEGORY_TYPE.GOOGLE && ' (구글)'} - {category.categoryType === CATEGORY_TYPE.PERSONAL && ' (기본)'} - - -
- - - - - -
-
- ); -} - -export default MyCategoryItem; diff --git a/frontend/src/components/MyCategoryList/MyCategoryList.fallback.tsx b/frontend/src/components/MyCategoryList/MyCategoryList.fallback.tsx deleted file mode 100644 index 8f7c2f26..00000000 --- a/frontend/src/components/MyCategoryList/MyCategoryList.fallback.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Skeleton from '@/components/@common/Skeleton/Skeleton'; -import { - categoryTableHeaderStyle, - categoryTableStyle, - itemStyle, -} from '@/components/CategoryList/CategoryList.styles'; -import { - categoryItem, - item, -} from '@/components/SubscribedCategoryItem/SubscribedCategoryItem.styles'; - -function CategoryListFallback() { - return ( -
-
- 생성 날짜 - 카테고리 이름 - 수정 / 삭제 -
-
- {new Array(6).fill(0).map((el, index) => ( -
- - - -
- ))} -
-
- ); -} - -export default CategoryListFallback; diff --git a/frontend/src/components/MyCategoryList/MyCategoryList.styles.ts b/frontend/src/components/MyCategoryList/MyCategoryList.styles.ts deleted file mode 100644 index 17a620cf..00000000 --- a/frontend/src/components/MyCategoryList/MyCategoryList.styles.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { css, Theme } from '@emotion/react'; - -const itemStyle = css` - flex: 1 1 0; - text-align: center; -`; - -const headerStyle = ({ flex, colors }: Theme) => css` - ${flex.row} - - justify-content: space-around; - - width: 100%; - height: 12rem; - border-bottom: 2px solid ${colors.GRAY_400}; - - background: ${colors.GRAY_100}; - - font-size: 4rem; - font-weight: 700; -`; - -const buttonStyle = ({ colors }: Theme) => css` - width: 8rem; - height: 8rem; - - background: transparent; - - color: ${colors.GRAY_700}; - - &:hover { - border-radius: 50%; - - background: ${colors.GRAY_100}; - - filter: none; - } -`; - -const listStyle = css` - width: 100%; - height: 100%; - - overflow-y: overlay; -`; - -export { buttonStyle, listStyle, headerStyle, itemStyle }; diff --git a/frontend/src/components/MyCategoryList/MyCategoryList.tsx b/frontend/src/components/MyCategoryList/MyCategoryList.tsx deleted file mode 100644 index d724cff5..00000000 --- a/frontend/src/components/MyCategoryList/MyCategoryList.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { AxiosError, AxiosResponse } from 'axios'; -import { useQuery } from 'react-query'; -import { useRecoilValue } from 'recoil'; - -import { CategoryType } from '@/@types/category'; - -import { userState } from '@/recoil/atoms'; - -import MyCategoryItem from '@/components/MyCategoryItem/MyCategoryItem'; - -import { CACHE_KEY } from '@/constants/api'; - -import categoryApi from '@/api/category'; - -import { headerStyle, itemStyle, listStyle } from './MyCategoryList.styles'; - -function MyCategoryList() { - const { accessToken } = useRecoilValue(userState); - - const { data } = useQuery, AxiosError>( - CACHE_KEY.MY_CATEGORIES, - () => categoryApi.getMy(accessToken) - ); - - return ( - <> -
- 생성 날짜 - 카테고리 이름 - 수정 / 삭제 -
-
- {data?.data.map((category) => ( - - ))} -
- - ); -} - -export default MyCategoryList; From 15692ab5a8f2e2e023155af7331a907f356d1de6 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 14:46:22 +0900 Subject: [PATCH 112/148] =?UTF-8?q?chore:=20webpack-bundle-analyzer=20?= =?UTF-8?q?=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/webpack.config.js | 3 +- frontend/yarn.lock | 68 +++++++++++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index c8338701..2048e562 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -74,6 +74,7 @@ "ts-loader": "^9.3.1", "typescript": "^4.7.4", "webpack": "^5.73.0", + "webpack-bundle-analyzer": "^4.6.1", "webpack-cli": "^4.10.0", "webpack-dev-server": "^4.9.3" }, diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 06e0ea86..25e6e94d 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -6,9 +6,9 @@ const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const prod = process.env.NODE_ENV === 'production'; - module.exports = { mode: prod ? 'production' : 'development', devtool: prod ? 'hidden-source-map' : 'eval', @@ -57,6 +57,7 @@ module.exports = { new CleanWebpackPlugin(), new Dotenv(), new MiniCssExtractPlugin(), + new BundleAnalyzerPlugin(), ], devServer: { historyApiFallback: true, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5e1f6d96..4a82d1db 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1865,6 +1865,11 @@ schema-utils "^3.0.0" source-map "^0.7.3" +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + "@sinclair/typebox@^0.24.1": version "0.24.37" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.37.tgz#3ea4cf8f3cf8a943c17baf5bb7b33587afa5f76b" @@ -3761,6 +3766,11 @@ acorn-walk@^7.1.1, acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + acorn@^6.4.1: version "6.4.2" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" @@ -3771,7 +3781,7 @@ acorn@^7.1.1, acorn@^7.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: +acorn@^8.0.4, acorn@^8.5.0, acorn@^8.7.1, acorn@^8.8.0: version "8.8.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== @@ -5927,6 +5937,11 @@ dotenv@^8.0.0, dotenv@^8.2.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + duplexify@^3.4.2, duplexify@^3.6.0: version "3.7.1" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" @@ -7156,6 +7171,13 @@ graphql@^16.3.0: resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.6.0.tgz#c2dcffa4649db149f6282af726c8c83f1c7c5fdb" integrity sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw== +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + hamt_plus@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601" @@ -9417,6 +9439,11 @@ move-concurrently@^1.0.1: rimraf "^2.5.4" run-queue "^1.0.3" +mrmime@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" + integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -9825,6 +9852,11 @@ open@^8.0.9, open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + optionator@^0.8.1: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" @@ -11677,6 +11709,15 @@ signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +sirv@^1.0.7: + version "1.0.19" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" + integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== + dependencies: + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^1.0.0" + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -12403,6 +12444,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + tough-cookie@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.2.tgz#e53e84b85f24e0b65dd526f46628db6c85f6b874" @@ -12990,6 +13036,21 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== +webpack-bundle-analyzer@^4.6.1: + version "4.6.1" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.6.1.tgz#bee2ee05f4ba4ed430e4831a319126bb4ed9f5a6" + integrity sha512-oKz9Oz9j3rUciLNfpGFjOb49/jEpXNmWdVH8Ls//zNcnLlQdTGXQQMsBbb/gR7Zl8WNLxVCq+0Hqbx3zv6twBw== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^7.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + webpack-cli@^4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" @@ -13341,6 +13402,11 @@ write-file-atomic@^4.0.1: imurmurhash "^0.1.4" signal-exit "^3.0.7" +ws@^7.3.1: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + ws@^8.2.3, ws@^8.4.2: version "8.8.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" From 74684b3e8828117f54ec372aba46ec6f4e977067 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 15:05:13 +0900 Subject: [PATCH 113/148] =?UTF-8?q?refactor:=20=EC=95=84=EC=9D=B4=EC=BD=98?= =?UTF-8?q?=20=ED=95=98=EB=82=98=EC=9D=98=20=ED=85=8C=EB=A7=88=EB=A1=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/NavBar/NavBar.tsx | 23 +++++++++++-------- frontend/src/components/Profile/Profile.tsx | 7 +++--- .../ScheduleAddButton/ScheduleAddButton.tsx | 4 ++-- .../ScheduleModal/ScheduleModal.tsx | 17 ++++++++------ .../SideGoogleList/SideGoogleList.tsx | 7 +++--- frontend/src/components/SideItem/SideItem.tsx | 9 ++++---- .../src/components/SideMyList/SideMyList.tsx | 7 +++--- .../SideSubscribedList/SideSubscribedList.tsx | 11 +++++---- .../SubscriptionModifyModal.tsx | 7 +++--- .../src/pages/CalendarPage/CalendarPage.tsx | 10 ++++---- .../src/pages/CategoryPage/CategoryPage.tsx | 4 ++-- .../pages/SchedulingPage/SchedulingPage.tsx | 9 ++++---- 12 files changed, 59 insertions(+), 56 deletions(-) diff --git a/frontend/src/components/NavBar/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx index c996ef20..9874c23f 100644 --- a/frontend/src/components/NavBar/NavBar.tsx +++ b/frontend/src/components/NavBar/NavBar.tsx @@ -15,11 +15,14 @@ import ProfileFallback from '@/components/Profile/Profile.fallback'; import { PATH } from '@/constants'; import { TRANSPARENT } from '@/constants/style'; -import { BiCategory } from 'react-icons/bi'; -import { FaUserCircle } from 'react-icons/fa'; -import { FiCalendar } from 'react-icons/fi'; -import { HiChevronDoubleLeft, HiMenu } from 'react-icons/hi'; -import { IoPeopleOutline } from 'react-icons/io5'; +import { + MdAccessTime, + MdCalendarToday, + MdMenu, + MdMenuOpen, + MdOutlineCategory, + MdPersonOutline, +} from 'react-icons/md'; import BlackLogo from '../../assets/dallog_black.png'; import { logo, logoImg, logoText, menu, menus, menuTitle, navBar } from './NavBar.styles'; @@ -60,7 +63,7 @@ function NavBar() {
{accessToken && ( )} @@ -73,19 +76,19 @@ function NavBar() { {accessToken && ( <> handleClickCompleteButton(user.displayName)} > - + 완료 @@ -113,7 +112,7 @@ function Profile() {
{user.displayName}
diff --git a/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx index e835f153..6d4bba2b 100644 --- a/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx +++ b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx @@ -2,7 +2,7 @@ import { useTheme } from '@emotion/react'; import Button from '@/components/@common/Button/Button'; -import { BsCalendarPlusFill } from 'react-icons/bs'; +import { MdEditCalendar } from 'react-icons/md'; import { scheduleAddButton } from './ScheduleAddButton.styles'; @@ -15,7 +15,7 @@ function ScheduleAddButton({ onClick }: ScheduleAddButtonProps) { return ( ); } diff --git a/frontend/src/components/ScheduleModal/ScheduleModal.tsx b/frontend/src/components/ScheduleModal/ScheduleModal.tsx index 5dd8ce5a..9f7e4e5f 100644 --- a/frontend/src/components/ScheduleModal/ScheduleModal.tsx +++ b/frontend/src/components/ScheduleModal/ScheduleModal.tsx @@ -17,9 +17,12 @@ import { CONFIRM_MESSAGE } from '@/constants/message'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; -import { FiCalendar, FiEdit3 } from 'react-icons/fi'; -import { GrClose } from 'react-icons/gr'; -import { RiDeleteBin6Line } from 'react-icons/ri'; +import { + MdClose, + MdDeleteOutline, + MdOutlineCalendarToday, + MdOutlineModeEdit, +} from 'react-icons/md'; import { buttonStyle, @@ -101,23 +104,23 @@ function ScheduleModal({ {canEditSchedule && ( <> )}
- +

{scheduleInfo.title}

diff --git a/frontend/src/components/SideGoogleList/SideGoogleList.tsx b/frontend/src/components/SideGoogleList/SideGoogleList.tsx index 36c7a5d6..06988911 100644 --- a/frontend/src/components/SideGoogleList/SideGoogleList.tsx +++ b/frontend/src/components/SideGoogleList/SideGoogleList.tsx @@ -12,8 +12,7 @@ import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; import GoogleImportModal from '@/components/GoogleImportModal/GoogleImportModal'; import SideItem from '@/components/SideItem/SideItem'; -import { AiOutlineDown, AiOutlineUp } from 'react-icons/ai'; -import { BsPlus } from 'react-icons/bs'; +import { MdAdd, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; import { contentStyle, @@ -47,11 +46,11 @@ function SideGoogleList({ categories }: SideGoogleListProps) { 구글 카테고리

diff --git a/frontend/src/components/SideItem/SideItem.tsx b/frontend/src/components/SideItem/SideItem.tsx index 3068baaf..22e930f8 100644 --- a/frontend/src/components/SideItem/SideItem.tsx +++ b/frontend/src/components/SideItem/SideItem.tsx @@ -16,8 +16,7 @@ import { TRANSPARENT } from '@/constants/style'; import subscriptionApi from '@/api/subscription'; -import { BiDotsVerticalRounded } from 'react-icons/bi'; -import { RiCheckboxBlankLine, RiCheckboxFill } from 'react-icons/ri'; +import { MdCheckBox, MdCheckBoxOutlineBlank, MdMoreVert } from 'react-icons/md'; import { checkBoxNameStyle, @@ -64,7 +63,7 @@ function SideItem({ subscription }: SideItemProps) { {isLoading ? ( ) : subscription.checked ? ( - { @@ -72,7 +71,7 @@ function SideItem({ subscription }: SideItemProps) { }} /> ) : ( - { @@ -91,7 +90,7 @@ function SideItem({ subscription }: SideItemProps) {
{isPaletteOpen && (
diff --git a/frontend/src/components/SideSubscribedList/SideSubscribedList.tsx b/frontend/src/components/SideSubscribedList/SideSubscribedList.tsx index c379566e..62e49d17 100644 --- a/frontend/src/components/SideSubscribedList/SideSubscribedList.tsx +++ b/frontend/src/components/SideSubscribedList/SideSubscribedList.tsx @@ -13,8 +13,7 @@ import SideItem from '@/components/SideItem/SideItem'; import { PATH } from '@/constants'; -import { AiOutlineDown, AiOutlineUp } from 'react-icons/ai'; -import { BsPlus } from 'react-icons/bs'; +import { MdAdd, MdKeyboardArrowDown, MdKeyboardArrowUp } from 'react-icons/md'; import { contentStyle, @@ -47,11 +46,15 @@ function SideSubscribedList({ categories }: SideSubscribedListProps) { 구독 카테고리
diff --git a/frontend/src/components/SubscriptionModifyModal/SubscriptionModifyModal.tsx b/frontend/src/components/SubscriptionModifyModal/SubscriptionModifyModal.tsx index 245ce8ed..203f2821 100644 --- a/frontend/src/components/SubscriptionModifyModal/SubscriptionModifyModal.tsx +++ b/frontend/src/components/SubscriptionModifyModal/SubscriptionModifyModal.tsx @@ -21,8 +21,7 @@ import { PALETTE } from '@/constants/style'; import categoryApi from '@/api/category'; -import { FiEdit3 } from 'react-icons/fi'; -import { RiDeleteBin5Line } from 'react-icons/ri'; +import { MdDeleteOutline, MdOutlineModeEdit } from 'react-icons/md'; import { colorStyle, @@ -94,11 +93,11 @@ function SubscriptionModifyModal({ {canEditSubscription && ( <> diff --git a/frontend/src/pages/CalendarPage/CalendarPage.tsx b/frontend/src/pages/CalendarPage/CalendarPage.tsx index 2ceaac24..3ed4bd4d 100644 --- a/frontend/src/pages/CalendarPage/CalendarPage.tsx +++ b/frontend/src/pages/CalendarPage/CalendarPage.tsx @@ -42,7 +42,7 @@ import { import scheduleApi from '@/api/schedule'; -import { AiOutlineLeft, AiOutlineRight } from 'react-icons/ai'; +import { MdKeyboardArrowLeft, MdKeyboardArrowRight } from 'react-icons/md'; import { calendarGrid, @@ -163,14 +163,14 @@ function CalendarPage() {
@@ -257,14 +257,14 @@ function CalendarPage() {
diff --git a/frontend/src/pages/CategoryPage/CategoryPage.tsx b/frontend/src/pages/CategoryPage/CategoryPage.tsx index 214099a7..53311fef 100644 --- a/frontend/src/pages/CategoryPage/CategoryPage.tsx +++ b/frontend/src/pages/CategoryPage/CategoryPage.tsx @@ -10,7 +10,7 @@ import PageLayout from '@/components/@common/PageLayout/PageLayout'; import CategoryAddModal from '@/components/CategoryAddModal/CategoryAddModal'; import CategoryListFallback from '@/components/CategoryList/CategoryList.fallback'; -import { GoSearch } from 'react-icons/go'; +import { MdSearch } from 'react-icons/md'; import { buttonStyle, @@ -55,7 +55,7 @@ function CategoryPage() {
- +
@@ -140,7 +139,7 @@ function SchedulingPage() { schedulingGetResponse.data.map((schedule) => (
{formatDateTime(schedule.startDateTime)}
- +
{formatDateTime(schedule.endDateTime)}
))} From a52023ad450eda41fdfad0c8c88e4c03b113a714 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 15:18:28 +0900 Subject: [PATCH 114/148] =?UTF-8?q?perf:=20=EB=9D=BC=EC=9A=B0=ED=84=B0?= =?UTF-8?q?=EB=8B=A8=EC=97=90=EC=84=9C=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20lazy=20import=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6284ef5f..4d6a6e8b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,5 @@ import { AxiosError } from 'axios'; +import { lazy } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; @@ -6,21 +7,22 @@ import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; import useSnackBar from '@/hooks/useSnackBar'; import ErrorBoundary from '@/components/@common/ErrorBoundary/ErrorBoundary'; -import NavBar from '@/components/NavBar/NavBar'; -import ProtectRoute from '@/components/ProtectRoute/ProtectRoute'; -import SideBar from '@/components/SideBar/SideBar'; -import SnackBar from '@/components/SnackBar/SnackBar'; -import AuthPage from '@/pages/AuthPage/AuthPage'; -import CategoryPage from '@/pages/CategoryPage/CategoryPage'; -import MainPage from '@/pages/MainPage/MainPage'; -import NotFoundPage from '@/pages/NotFoundPage/NotFoundPage'; -import PrivacyPolicyPage from '@/pages/PrivacyPolicyPage/PrivacyPolicyPage'; -import SchedulingPage from '@/pages/SchedulingPage/SchedulingPage'; import { PATH } from '@/constants'; import { ERROR_MESSAGE } from './constants/message'; +const NavBar = lazy(() => import('@/components/NavBar/NavBar')); +const ProtectRoute = lazy(() => import('@/components/ProtectRoute/ProtectRoute')); +const SideBar = lazy(() => import('@/components/SideBar/SideBar')); +const SnackBar = lazy(() => import('@/components/SnackBar/SnackBar')); +const AuthPage = lazy(() => import('@/pages/AuthPage/AuthPage')); +const CategoryPage = lazy(() => import('@/pages/CategoryPage/CategoryPage')); +const MainPage = lazy(() => import('@/pages/MainPage/MainPage')); +const NotFoundPage = lazy(() => import('@/pages/NotFoundPage/NotFoundPage')); +const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage/PrivacyPolicyPage')); +const SchedulingPage = lazy(() => import('@/pages/SchedulingPage/SchedulingPage')); + function App() { const { openSnackBar } = useSnackBar(); From ceac15487fc7ec245b68841a392e9c1b7516d0f8 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 16:13:56 +0900 Subject: [PATCH 115/148] =?UTF-8?q?chore:=20=EB=B2=88=EB=93=A4=20=EB=A6=AC?= =?UTF-8?q?=ED=8F=AC=ED=8A=B8=EB=A5=BC=20=EC=9C=84=ED=95=9C=20=EB=AA=85?= =?UTF-8?q?=EB=A0=B9=EC=96=B4=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 3 ++- frontend/webpack.config.js | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 2048e562..de2dcbb3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,7 +12,8 @@ "pretty": "prettier . --write", "check-lint": "eslint . --ext .js,.jsx,.ts,.tsx", "check-prettier": "prettier -c ./src", - "test": "jest --setupFiles ./setupFile.js" + "test": "jest --setupFiles ./setupFile.js", + "report": "webpack-bundle-analyzer --port 4200 dist/stats.json" }, "dependencies": { "@emotion/react": "^11.9.3", diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js index 25e6e94d..75333fff 100644 --- a/frontend/webpack.config.js +++ b/frontend/webpack.config.js @@ -57,7 +57,10 @@ module.exports = { new CleanWebpackPlugin(), new Dotenv(), new MiniCssExtractPlugin(), - new BundleAnalyzerPlugin(), + new BundleAnalyzerPlugin({ + analyzerMode: 'disabled', + generateStatsFile: true, + }), ], devServer: { historyApiFallback: true, From e5e289612d9a3af36f0fcda001c7543bea05531c Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 17:28:42 +0900 Subject: [PATCH 116/148] =?UTF-8?q?refactor:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=8B=9C=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=EC=9D=98=20Id=EB=8F=84=20=EC=9A=94=EC=B2=AD=20body?= =?UTF-8?q?=EA=B0=92=EC=97=90=20=EB=93=A4=EC=96=B4=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/dto/request/ScheduleUpdateRequest.java | 13 ++++++++++--- .../dallog/acceptance/ScheduleAcceptanceTest.java | 3 ++- .../schedule/application/ScheduleServiceTest.java | 9 ++++++--- .../dallog/presentation/ScheduleControllerTest.java | 12 +++++++++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleUpdateRequest.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleUpdateRequest.java index 3744671b..05d1aee4 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleUpdateRequest.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleUpdateRequest.java @@ -1,12 +1,14 @@ package com.allog.dallog.domain.schedule.dto.request; import java.time.LocalDateTime; -import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import org.springframework.format.annotation.DateTimeFormat; public class ScheduleUpdateRequest { + @NotNull(message = "Null일 수 없습니다.") + private long categoryId; + @NotNull(message = "Null일 수 없습니다.") private String title; @@ -22,8 +24,9 @@ public class ScheduleUpdateRequest { private ScheduleUpdateRequest() { } - public ScheduleUpdateRequest(final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, - final String memo) { + public ScheduleUpdateRequest(final Long categoryId, final String title, final LocalDateTime startDateTime, + final LocalDateTime endDateTime, final String memo) { + this.categoryId = categoryId; this.title = title; this.startDateTime = startDateTime; this.endDateTime = endDateTime; @@ -45,4 +48,8 @@ public LocalDateTime getEndDateTime() { public String getMemo() { return memo; } + + public Long getCategoryId() { + return categoryId; + } } diff --git a/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java index 3cefdf3c..4c9aa314 100644 --- a/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java +++ b/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java @@ -65,7 +65,8 @@ class ScheduleAcceptanceTest extends AcceptanceTest { CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); ScheduleResponse 알록달록_회의 = 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()).as(ScheduleResponse.class); - ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(공통_일정_응답.getId(), 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, + 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); // when ExtractableResponse response = 일정을_수정한다(accessToken, 알록달록_회의.getId(), 일정_수정_요청); diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index 08a2854e..b3eb6902 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -223,7 +223,8 @@ class ScheduleServiceTest extends ServiceTest { CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); - ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(BE_일정.getId(), 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); // when scheduleService.update(기존_일정.getId(), 후디_id, 일정_수정_요청); @@ -250,7 +251,8 @@ class ScheduleServiceTest extends ServiceTest { CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); - ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(BE_일정.getId(), 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); // when & then assertThatThrownBy(() -> scheduleService.update(기존_일정.getId(), 리버_id, 일정_수정_요청)) @@ -265,7 +267,8 @@ class ScheduleServiceTest extends ServiceTest { CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); - ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(BE_일정.getId(), 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); // when & then assertThatThrownBy(() -> scheduleService.update(기존_일정.getId() + 1, 후디_id, 일정_수정_요청)) diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java index 24d50f2b..70c93da3 100644 --- a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -176,8 +176,10 @@ class ScheduleControllerTest extends ControllerTest { @Test void 일정을_수정하는데_성공하면_204를_반환한다() throws Exception { // given + Long categoryId = 1L; Long scheduleId = 1L; - ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(categoryId, 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); willDoNothing() .given(scheduleService) .update(any(), any(), any()); @@ -202,8 +204,10 @@ class ScheduleControllerTest extends ControllerTest { @Test void 일정을_수정하는데_해당_일정의_카테고리에_대한_권한이_없다면_403을_반환한다() throws Exception { // given + Long categoryId = 1L; Long scheduleId = 1L; - ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(categoryId, 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); willThrow(new NoPermissionException()) .given(scheduleService) .update(any(), any(), any()); @@ -225,8 +229,10 @@ class ScheduleControllerTest extends ControllerTest { @Test void 일정을_수정하는데_일정이_존재하지_않는_경우_404를_반환한다() throws Exception { // given + Long categoryId = 1L; Long scheduleId = 1L; - ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(categoryId, 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); willThrow(new NoSuchScheduleException()) .given(scheduleService) .update(any(), any(), any()); From 9ae6bc4f14513273136c5f63f953b4ae7e9e7c9f Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 17:29:57 +0900 Subject: [PATCH 117/148] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=EC=9D=98=20?= =?UTF-8?q?=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=EB=A5=BC=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../schedule/application/ScheduleService.java | 5 +++++ .../dallog/domain/schedule/domain/Schedule.java | 15 +++++++++++++++ .../domain/schedule/domain/ScheduleTest.java | 15 +++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index a6879fe4..571e9beb 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -67,6 +67,11 @@ public void update(final Long id, final Long memberId, final ScheduleUpdateReque Schedule schedule = scheduleRepository.getById(id); schedule.validateEditablePossible(memberId); schedule.change(request.getTitle(), request.getStartDateTime(), request.getEndDateTime(), request.getMemo()); + + if (schedule.hasNotSameCategory(request.getCategoryId())) { + Category category = categoryRepository.getById(request.getCategoryId()); + schedule.changeCategory(category); + } } @Transactional diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index 62207712..e2f371f2 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -69,6 +69,17 @@ public void change(final String title, final LocalDateTime startDateTime, final this.memo = memo; } + public void changeCategory(final Category category) { + validateCategoryType(category); + this.category = category; + } + + private void validateCategoryType(final Category category) { + if (category.isExternal()) { + throw new InvalidScheduleException("일정의 카테고리를 외부 연동 카테고리로 변경 할 수 없습니다."); + } + } + private void validateTitleLength(final String title) { if (title.length() > MAX_TITLE_LENGTH) { throw new InvalidScheduleException("일정 제목의 길이는 20을 초과할 수 없습니다."); @@ -96,6 +107,10 @@ public void validateEditablePossible(final Long memberId) { } } + public boolean hasNotSameCategory(final Long categoryId) { + return !category.getId().equals(categoryId); + } + public Long getId() { return id; } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java index 2b456c5d..f9e2bd39 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java @@ -10,6 +10,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -52,4 +53,18 @@ public class ScheduleTest { assertThatThrownBy(() -> new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 잘못된_메모)) .isInstanceOf(InvalidScheduleException.class); } + + @DisplayName("일정의 카테고리를 외부연동 카테고리로 변경 하려는 경우 예외를 던진다.") + @Test + void 일정의_카테고리를_외부연동_카테고리로_변경_하려는_경우_예외를_던진다() { + // given + Category BE_일정_카테고리 = BE_일정(관리자()); + Schedule BE_일정 = new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); + + Category 외부_연동_카테고리 = new Category("외부 카테고리", 관리자(), CategoryType.GOOGLE); + + // when & then + assertThatThrownBy(() -> BE_일정.changeCategory(외부_연동_카테고리)) + .isInstanceOf(InvalidScheduleException.class); + } } From 6a76cb73116179234e1eb40b7c31d5a33302707e Mon Sep 17 00:00:00 2001 From: koo Date: Mon, 19 Sep 2022 18:01:23 +0900 Subject: [PATCH 118/148] =?UTF-8?q?feat:=20=EB=B3=80=EA=B2=BD=ED=95=98?= =?UTF-8?q?=EB=A0=A4=EB=8A=94=20=EC=9D=BC=EC=A0=95=EC=9D=98=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20creator=EB=A5=BC=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=ED=95=98=EB=8A=94=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/domain/CategoryRepository.java | 1 + .../schedule/application/ScheduleService.java | 16 +++++----- .../domain/schedule/domain/Schedule.java | 11 +++---- .../application/ScheduleServiceTest.java | 31 +++++++++++++++++++ 4 files changed, 44 insertions(+), 15 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index fdc6f411..7c776800 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -3,6 +3,7 @@ import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java index 571e9beb..0e51b9dc 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -64,20 +64,20 @@ private List integrationSchedules(final DateRangeRequest da @Transactional public void update(final Long id, final Long memberId, final ScheduleUpdateRequest request) { - Schedule schedule = scheduleRepository.getById(id); - schedule.validateEditablePossible(memberId); - schedule.change(request.getTitle(), request.getStartDateTime(), request.getEndDateTime(), request.getMemo()); + Long categoryId = request.getCategoryId(); + categoryRepository.validateExistsByIdAndMemberId(categoryId, memberId); - if (schedule.hasNotSameCategory(request.getCategoryId())) { - Category category = categoryRepository.getById(request.getCategoryId()); - schedule.changeCategory(category); - } + Category categoryForUpdate = categoryRepository.getById(categoryId); + Schedule schedule = scheduleRepository.getById(id); + schedule.validateEditPossible(memberId); + schedule.change(categoryForUpdate, request.getTitle(), request.getStartDateTime(), request.getEndDateTime(), + request.getMemo()); } @Transactional public void delete(final Long id, final Long memberId) { Schedule schedule = scheduleRepository.getById(id); - schedule.validateEditablePossible(memberId); + schedule.validateEditPossible(memberId); scheduleRepository.deleteById(id); } } diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index e2f371f2..5358c3c9 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -58,11 +58,12 @@ public Schedule(final Category category, final String title, final LocalDateTime this.memo = memo; } - public void change(final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, - final String memo) { + public void change(final Category category, final String title, final LocalDateTime startDateTime, + final LocalDateTime endDateTime, final String memo) { validateTitleLength(title); validatePeriod(startDateTime, endDateTime); validateMemoLength(memo); + this.category = category; this.title = title; this.startDateTime = startDateTime; this.endDateTime = endDateTime; @@ -98,7 +99,7 @@ private void validateMemoLength(final String memo) { } } - public void validateEditablePossible(final Long memberId) { + public void validateEditPossible(final Long memberId) { if (category.isExternal()) { throw new NoPermissionException("외부 연동 카테고리의 일정은 변경할 수 없습니다."); } @@ -107,10 +108,6 @@ public void validateEditablePossible(final Long memberId) { } } - public boolean hasNotSameCategory(final Long categoryId) { - return !category.getId().equals(categoryId); - } - public Long getId() { return id; } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index b3eb6902..ee1fc068 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -3,6 +3,7 @@ import static com.allog.dallog.common.fixtures.AuthFixtures.리버_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.AuthFixtures.후디_인증_코드_토큰_요청; import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.FE_일정_생성_요청; import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.대한민국_공휴일_생성_요청; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_15일_16시_0분; import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_1일_0시_0분; @@ -275,6 +276,36 @@ class ScheduleServiceTest extends ServiceTest { .isInstanceOf(NoSuchScheduleException.class); } + @DisplayName("일정의 카테고리도 수정한다.") + @Test + void 일정의_카테고리도_수정한다() { + // given + Long 후디_id = parseMemberId(후디_인증_코드_토큰_요청()); + CategoryResponse BE_일정 = categoryService.save(후디_id, BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디_id, BE_일정.getId(), 알록달록_회의_생성_요청); + + CategoryResponse FE_일정 = categoryService.save(후디_id, FE_일정_생성_요청); + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(FE_일정.getId(), 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, + 레벨_인터뷰_메모); + + // when + scheduleService.update(기존_일정.getId(), 후디_id, 일정_수정_요청); + + // then + ScheduleResponse actual = scheduleService.findById(기존_일정.getId()); + assertAll( + () -> { + assertThat(actual.getId()).isEqualTo(기존_일정.getId()); + assertThat(actual.getCategoryType()).isEqualTo(FE_일정.getCategoryType()); + assertThat(actual.getTitle()).isEqualTo(레벨_인터뷰_제목); + assertThat(actual.getStartDateTime()).isEqualTo(레벨_인터뷰_시작일시); + assertThat(actual.getEndDateTime()).isEqualTo(레벨_인터뷰_종료일시); + assertThat(actual.getMemo()).isEqualTo(레벨_인터뷰_메모); + } + ); + } + + @DisplayName("일정을 삭제한다.") @Test void 일정을_삭제한다() { From cc93319ddfb1d6bd8db4835ca2f7c6df86e1765e Mon Sep 17 00:00:00 2001 From: summerlunaa Date: Wed, 21 Sep 2022 16:28:57 +0900 Subject: [PATCH 119/148] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EB=8A=94=20import=20=EB=B0=8F=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../category/domain/CategoryRepository.java | 1 - .../dallog/domain/schedule/domain/Schedule.java | 14 -------------- .../schedule/application/ScheduleServiceTest.java | 1 - .../domain/schedule/domain/ScheduleTest.java | 15 --------------- 4 files changed, 31 deletions(-) diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java index 7c776800..fdc6f411 100644 --- a/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -3,7 +3,6 @@ import com.allog.dallog.domain.auth.exception.NoPermissionException; import com.allog.dallog.domain.category.exception.NoSuchCategoryException; import java.util.List; -import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java index 5358c3c9..4ed39f83 100644 --- a/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -70,17 +70,6 @@ public void change(final Category category, final String title, final LocalDateT this.memo = memo; } - public void changeCategory(final Category category) { - validateCategoryType(category); - this.category = category; - } - - private void validateCategoryType(final Category category) { - if (category.isExternal()) { - throw new InvalidScheduleException("일정의 카테고리를 외부 연동 카테고리로 변경 할 수 없습니다."); - } - } - private void validateTitleLength(final String title) { if (title.length() > MAX_TITLE_LENGTH) { throw new InvalidScheduleException("일정 제목의 길이는 20을 초과할 수 없습니다."); @@ -100,9 +89,6 @@ private void validateMemoLength(final String memo) { } public void validateEditPossible(final Long memberId) { - if (category.isExternal()) { - throw new NoPermissionException("외부 연동 카테고리의 일정은 변경할 수 없습니다."); - } if (!category.isCreatorId(memberId)) { throw new NoPermissionException(); } diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java index ee1fc068..949d1548 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -305,7 +305,6 @@ class ScheduleServiceTest extends ServiceTest { ); } - @DisplayName("일정을 삭제한다.") @Test void 일정을_삭제한다() { diff --git a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java index f9e2bd39..2b456c5d 100644 --- a/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java @@ -10,7 +10,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import com.allog.dallog.domain.category.domain.Category; -import com.allog.dallog.domain.category.domain.CategoryType; import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -53,18 +52,4 @@ public class ScheduleTest { assertThatThrownBy(() -> new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 잘못된_메모)) .isInstanceOf(InvalidScheduleException.class); } - - @DisplayName("일정의 카테고리를 외부연동 카테고리로 변경 하려는 경우 예외를 던진다.") - @Test - void 일정의_카테고리를_외부연동_카테고리로_변경_하려는_경우_예외를_던진다() { - // given - Category BE_일정_카테고리 = BE_일정(관리자()); - Schedule BE_일정 = new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); - - Category 외부_연동_카테고리 = new Category("외부 카테고리", 관리자(), CategoryType.GOOGLE); - - // when & then - assertThatThrownBy(() -> BE_일정.changeCategory(외부_연동_카테고리)) - .isInstanceOf(InvalidScheduleException.class); - } } From 0e8aa9b074405025b697cda2331a5af3f082fc48 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 17:50:34 +0900 Subject: [PATCH 120/148] =?UTF-8?q?style:=20=EC=98=B5=EC=85=98=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=8A=A4?= =?UTF-8?q?=ED=83=80=EC=9D=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/@types/index.ts | 8 +++++++- .../components/@common/Select/Select.styles.ts | 11 ++++++++--- frontend/src/components/@common/Select/Select.tsx | 15 ++++++++++----- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/frontend/src/@types/index.ts b/frontend/src/@types/index.ts index 0cb0d39d..30daa7d5 100644 --- a/frontend/src/@types/index.ts +++ b/frontend/src/@types/index.ts @@ -6,6 +6,12 @@ interface FieldsetCssPropType { label?: SerializedStyles; } +interface SelectCssPropType { + select?: SerializedStyles; + optionBox?: SerializedStyles; + option?: SerializedStyles; +} + interface InputRefType { [index: string]: React.RefObject; } @@ -17,4 +23,4 @@ interface ModalPosType { left?: number; } -export { FieldsetCssPropType, InputRefType, ModalPosType }; +export { FieldsetCssPropType, InputRefType, ModalPosType, SelectCssPropType }; diff --git a/frontend/src/components/@common/Select/Select.styles.ts b/frontend/src/components/@common/Select/Select.styles.ts index 30aa265e..b1c5cd5b 100644 --- a/frontend/src/components/@common/Select/Select.styles.ts +++ b/frontend/src/components/@common/Select/Select.styles.ts @@ -2,6 +2,10 @@ import { css, Theme } from '@emotion/react'; import { OPTION_HEIGHT } from '@/constants/style'; +const layoutStyle = css` + width: 100%; +`; + const hiddenStyle = css` display: none; `; @@ -18,7 +22,7 @@ const dimmerStyle = (isSelectOpen: boolean) => css` `; const selectStyle = ({ colors }: Theme) => css` - width: 42.75rem; + width: 100%; height: 11.75rem; border-radius: 8px; border: 1px solid ${colors.GRAY_400}; @@ -40,8 +44,8 @@ const optionLayoutStyle = ({ colors }: Theme, isSelectOpen: boolean) => css` position: absolute; overflow: overlay; - width: 42.75rem; - height: ${isSelectOpen ? '50rem' : 0}; + width: 100%; + max-height: ${isSelectOpen ? '50rem' : 0}; border: ${isSelectOpen && `1px solid ${colors.GRAY_400}`}; border-radius: 8px; @@ -82,6 +86,7 @@ const relativeStyle = css` export { dimmerStyle, labelStyle, + layoutStyle, hiddenStyle, selectStyle, optionStyle, diff --git a/frontend/src/components/@common/Select/Select.tsx b/frontend/src/components/@common/Select/Select.tsx index 10f120fd..59c0209c 100644 --- a/frontend/src/components/@common/Select/Select.tsx +++ b/frontend/src/components/@common/Select/Select.tsx @@ -3,12 +3,15 @@ import { useEffect, useRef } from 'react'; import useToggle from '@/hooks/useToggle'; +import { SelectCssPropType } from '@/@types'; + import { OPTION_HEIGHT } from '@/constants/style'; import { dimmerStyle, hiddenStyle, labelStyle, + layoutStyle, optionLayoutStyle, optionStyle, relativeStyle, @@ -21,9 +24,10 @@ interface SelectProps { onChange: ({ target, }: React.ChangeEvent | React.ChangeEvent) => void; + cssProp?: SelectCssPropType; } -function Select({ options, value, onChange }: SelectProps) { +function Select({ options, value, onChange, cssProp }: SelectProps) { const theme = useTheme(); const ref = useRef(null); @@ -42,13 +46,13 @@ function Select({ options, value, onChange }: SelectProps) { }; return ( -
+
-
+
{value || '옵션 선택'}
-
+
{isSelectOpen && options.map((opt, index) => (
@@ -61,7 +65,8 @@ function Select({ options, value, onChange }: SelectProps) { css={hiddenStyle} onClick={toggleSelectOpen} /> -
From 07189ae94fd9ceebe1d0c7fc1bb6284829da0ad3 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Mon, 19 Sep 2022 17:52:00 +0900 Subject: [PATCH 121/148] =?UTF-8?q?style:=20=EC=9D=BC=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20=EC=98=B5?= =?UTF-8?q?=EC=85=98=20=EC=84=A0=ED=83=9D=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=EB=A7=81=20=EC=A3=BC?= =?UTF-8?q?=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleAddModal/ScheduleAddModal.styles.ts | 7 +++++++ .../src/components/ScheduleAddModal/ScheduleAddModal.tsx | 3 +++ 2 files changed, 10 insertions(+) diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts index d113d781..45397a4b 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts @@ -156,6 +156,12 @@ const dateFieldsetStyle = (isAllDay: boolean) => { }; }; +const selectTimeStyle = { + select: css` + width: 45%; + `, +}; + export { arrow, categorySelect, @@ -170,4 +176,5 @@ export { saveButton, scheduleAddModal, selectBoxStyle, + selectTimeStyle, }; diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx index 23856f72..46f275c0 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -42,6 +42,7 @@ import { saveButton, scheduleAddModal, selectBoxStyle, + selectTimeStyle, } from './ScheduleAddModal.styles'; interface ScheduleAddModalProps { @@ -159,6 +160,7 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { options={TIMES} value={validationSchedule.startTime.inputValue} onChange={validationSchedule.startTime.onChangeValue} + cssProp={selectTimeStyle} /> )}
@@ -176,6 +178,7 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { options={TIMES} value={validationSchedule.endTime.inputValue} onChange={validationSchedule.endTime.onChangeValue} + cssProp={selectTimeStyle} /> )}
From 4a9e0c4043047cbd9e32e275913ea048ab8d99e0 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Wed, 21 Sep 2022 12:04:05 +0900 Subject: [PATCH 122/148] =?UTF-8?q?feat:=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC?= =?UTF-8?q?=EB=8F=84=20=EC=84=A0=ED=83=9D=ED=95=A0=20=EC=88=98=20=EC=9E=88?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ScheduleModifyModal.styles.ts | 29 ++++---------- .../ScheduleModifyModal.tsx | 38 ++++++++++++++----- 2 files changed, 37 insertions(+), 30 deletions(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts index 8c99f0ec..553bb959 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts @@ -14,25 +14,6 @@ const formStyle = ({ flex }: Theme) => css` gap: 6rem; `; -const categoryStyle = ({ colors }: Theme, colorCode: string) => css` - padding: 0 3rem; - - width: 100%; - height: 12rem; - border: 1px solid ${colors.GRAY_500}; - border-radius: 8px; - - background: ${colorCode}; - - font-size: 5rem; - color: ${colors.WHITE}; - line-height: 12rem; - - &:hover { - cursor: default; - } -`; - const dateFieldsetStyle = (isAllDay: boolean) => { return { div: css` @@ -163,10 +144,16 @@ const categoryBoxStyle = ({ flex }: Theme) => css` width: 100%; `; +const selectTimeStyle = { + select: css` + width: 45%; + `, +}; + export { arrowStyle, cancelButtonStyle, - categoryStyle, + categoryBoxStyle, checkboxStyle, controlButtonsStyle, dateFieldsetStyle, @@ -176,5 +163,5 @@ export { labelStyle, modalStyle, saveButtonStyle, - categoryBoxStyle, + selectTimeStyle, }; diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index cbaadfa7..10914bb1 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -5,6 +5,7 @@ import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from 'react-query'; import { useRecoilValue } from 'recoil'; +import useControlledInput from '@/hooks/useControlledInput'; import useValidateSchedule from '@/hooks/useValidateSchedule'; import { CategoryType } from '@/@types/category'; @@ -20,7 +21,7 @@ import { CACHE_KEY } from '@/constants/api'; import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; -import { checkAllDay, getISODateString, getNextDate } from '@/utils/date'; +import { checkAllDay, getBeforeDate, getISODateString, getNextDate } from '@/utils/date'; import categoryApi from '@/api/category'; import scheduleApi from '@/api/schedule'; @@ -29,7 +30,6 @@ import { arrowStyle, cancelButtonStyle, categoryBoxStyle, - categoryStyle, checkboxStyle, controlButtonsStyle, dateFieldsetStyle, @@ -39,6 +39,7 @@ import { labelStyle, modalStyle, saveButtonStyle, + selectTimeStyle, } from './ScheduleModifyModal.styles'; interface ScheduleModifyModalProps { @@ -57,8 +58,9 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr const queryClient = useQueryClient(); - const { data } = useQuery, AxiosError>(CACHE_KEY.CATEGORY, () => - categoryApi.getSingle(scheduleInfo.categoryId) + const { data: categoriesGetResponse } = useQuery, AxiosError>( + CACHE_KEY.MY_CATEGORIES, + () => categoryApi.getMy(accessToken) ); const { mutate } = useMutation< @@ -70,6 +72,10 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr onSuccess: () => onSuccessPatchSchedule(), }); + const categoryId = useControlledInput( + categoriesGetResponse?.data.find((category) => category.id === scheduleInfo.categoryId)?.name + ); + const onSuccessPatchSchedule = () => { queryClient.invalidateQueries(CACHE_KEY.SCHEDULES); closeModal(); @@ -82,7 +88,7 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr initialTitle: scheduleInfo.title, initialStartDate: startDate, initialStartTime: startTime.slice(0, 5), - initialEndDate: endDate, + initialEndDate: getISODateString(getBeforeDate(new Date(endDate), 1).toISOString()), initialEndTime: endTime.slice(0, 5), initialMemo: scheduleInfo.memo, }); @@ -103,6 +109,9 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr : `${validationSchedule.endDate.inputValue}T${validationSchedule.endTime.inputValue}` }`, memo: validationSchedule.memo.inputValue, + categoryId: + categoriesGetResponse?.data.find((category) => category.name === categoryId.inputValue) + ?.id || scheduleInfo.categoryId, }; mutate(body); @@ -112,13 +121,11 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr setAllDay((prev) => !prev); }; + const categories = categoriesGetResponse?.data.map((category) => category.name); + return (
-
-
카테고리
-
{data?.data.name}
-
)}
@@ -169,16 +177,28 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr value={validationSchedule.endDate.inputValue} onChange={validationSchedule.endDate.onChangeValue} cssProp={dateFieldsetStyle(isAllDay)} + min={validationSchedule.startDate.inputValue} /> {!isAllDay && ( +
+ )}
Date: Wed, 21 Sep 2022 16:17:10 +0900 Subject: [PATCH 123/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/@common/Select/Select.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/@common/Select/Select.tsx b/frontend/src/components/@common/Select/Select.tsx index 59c0209c..33d4e838 100644 --- a/frontend/src/components/@common/Select/Select.tsx +++ b/frontend/src/components/@common/Select/Select.tsx @@ -48,7 +48,7 @@ function Select({ options, value, onChange, cssProp }: SelectProps) { return (
-
+
{value || '옵션 선택'}
From a0452e153da471312737ac37a495a6033313238e Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 21 Sep 2022 17:28:27 +0900 Subject: [PATCH 124/148] =?UTF-8?q?fix:=20dynamic=20import=EC=97=90=20susp?= =?UTF-8?q?ense=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/App.tsx | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4d6a6e8b..ae206e18 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,5 @@ import { AxiosError } from 'axios'; -import { lazy } from 'react'; +import { lazy, Suspense } from 'react'; import { QueryClient, QueryClientProvider } from 'react-query'; import { ReactQueryDevtools } from 'react-query/devtools'; import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; @@ -51,18 +51,20 @@ function App() { - - - - } /> - } /> - } /> - } /> - }> - } /> - } /> - - + }> + + + + } /> + } /> + } /> + } /> + }> + } /> + } /> + + + From 1ef6d1cbef024ae8076f546e339031d5700973f3 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 21 Sep 2022 17:49:12 +0900 Subject: [PATCH 125/148] =?UTF-8?q?fix:=20=EC=A2=85=EC=9D=BC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EA=B2=80=EC=82=AC=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/constants/date.ts | 1 + frontend/src/utils/date.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts index e3ac37a9..1e14e4d4 100644 --- a/frontend/src/constants/date.ts +++ b/frontend/src/constants/date.ts @@ -1,6 +1,7 @@ import { zeroFill } from '@/utils'; const DATE_TIME = { + START_INDEX: 11, START: '00:00', END: '00:00', }; diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index a1db35ba..de3cb152 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -9,7 +9,10 @@ const checkAllDay = (startDateTime?: string, endDateTime?: string) => { return null; } - return startDateTime.includes(DATE_TIME.START) && endDateTime.includes(DATE_TIME.END); + return ( + startDateTime.startsWith(DATE_TIME.START, DATE_TIME.START_INDEX) && + endDateTime.startsWith(DATE_TIME.END, DATE_TIME.START_INDEX) + ); }; const getBeforeDate = (targetDay: Date, offset: number) => From d902000f6e73caba5e03d3935ded4cbec36a2dab Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 21 Sep 2022 17:50:01 +0900 Subject: [PATCH 126/148] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20=EC=A2=85?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EC=9D=BC=EC=A0=95?= =?UTF-8?q?=EC=9D=98=20=EC=A2=85=EB=A3=8C=20=EB=82=A0=EC=A7=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleModifyModal/ScheduleModifyModal.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index 10914bb1..b229982a 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -88,7 +88,9 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr initialTitle: scheduleInfo.title, initialStartDate: startDate, initialStartTime: startTime.slice(0, 5), - initialEndDate: getISODateString(getBeforeDate(new Date(endDate), 1).toISOString()), + initialEndDate: isAllDay + ? getISODateString(getBeforeDate(new Date(endDate), 1).toISOString()) + : endDate, initialEndTime: endTime.slice(0, 5), initialMemo: scheduleInfo.memo, }); From 6efa4f174f9781e2563b606cdf69c12a6bd80632 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Wed, 21 Sep 2022 17:57:48 +0900 Subject: [PATCH 127/148] =?UTF-8?q?fix:=20=EC=A2=85=EC=9D=BC=20=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EA=B2=80=EC=82=AC=20=EB=B0=A9=EC=8B=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/date.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts index de3cb152..8face6af 100644 --- a/frontend/src/utils/date.ts +++ b/frontend/src/utils/date.ts @@ -5,7 +5,7 @@ import { DATE_TIME } from '@/constants/date'; import { zeroFill } from '.'; const checkAllDay = (startDateTime?: string, endDateTime?: string) => { - if (startDateTime === undefined || endDateTime === undefined) { + if (startDateTime === undefined || endDateTime === undefined || startDateTime === endDateTime) { return null; } From 77eda4dae303caf45fbe06bb62d4cf78e6e4f3db Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 22 Sep 2022 12:21:01 +0900 Subject: [PATCH 128/148] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=AA=A8=EB=8B=AC=EC=97=90=EC=84=9C=20=EC=A2=85?= =?UTF-8?q?=EC=9D=BC=EC=9D=B4=20=EC=95=84=EB=8B=8C=20=EB=B3=B5=EC=88=98=20?= =?UTF-8?q?=EC=9D=BC=EC=A0=95=EC=97=90=20=EB=8C=80=ED=95=B4=20endDate=20?= =?UTF-8?q?=EC=98=A4=EB=A5=98=EB=A5=BC=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleModifyModal/ScheduleModifyModal.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index b229982a..0eb11b4f 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -88,9 +88,10 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr initialTitle: scheduleInfo.title, initialStartDate: startDate, initialStartTime: startTime.slice(0, 5), - initialEndDate: isAllDay - ? getISODateString(getBeforeDate(new Date(endDate), 1).toISOString()) - : endDate, + initialEndDate: + isAllDay && endTime.slice(0, 5) === DATE_TIME.END + ? getISODateString(getBeforeDate(new Date(endDate), 1).toISOString()) + : endDate, initialEndTime: endTime.slice(0, 5), initialMemo: scheduleInfo.memo, }); From cfde9ae9e0ac1e8be928dd2c6f86d6c4bca905de Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Thu, 22 Sep 2022 12:40:28 +0900 Subject: [PATCH 129/148] =?UTF-8?q?perf:=20=EC=A0=84=EC=97=AD=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EB=93=9C=20=EB=B0=94=20=EC=83=81=ED=83=9C=EA=B0=80=20?= =?UTF-8?q?true=EC=9D=B8=20=EA=B2=BD=EC=9A=B0=EC=97=90=EB=A7=8C=20?= =?UTF-8?q?=EA=B5=AC=EB=8F=85=20=EB=AA=A9=EB=A1=9D=20=EC=9A=94=EC=B2=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: jhy979 --- frontend/src/components/SideBar/SideBar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/SideBar/SideBar.tsx b/frontend/src/components/SideBar/SideBar.tsx index df5349cb..f919d51b 100644 --- a/frontend/src/components/SideBar/SideBar.tsx +++ b/frontend/src/components/SideBar/SideBar.tsx @@ -29,7 +29,7 @@ function SideBar() { CACHE_KEY.SUBSCRIPTIONS, () => subscriptionApi.get(user.accessToken), { - enabled: !!user.accessToken, + enabled: isSideBarOpen && !!user.accessToken, } ); From 2a51edae548bd29f6b6a4212c781f61e618fa531 Mon Sep 17 00:00:00 2001 From: hyeonic Date: Thu, 22 Sep 2022 13:52:16 +0900 Subject: [PATCH 130/148] =?UTF-8?q?refactor:=20=EA=B0=9C=EB=B0=9C=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B6=84=EB=A6=AC=20=EB=B0=8F=20=EB=A1=9C?= =?UTF-8?q?=EA=B9=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-test.yml | 33 +++++++++---------- backend/src/main/resources/config | 2 +- backend/src/main/resources/logback-spring.xml | 16 ++++++--- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/backend/src/main/resources/application-test.yml b/backend/src/main/resources/application-test.yml index 7e34287b..cadd0137 100644 --- a/backend/src/main/resources/application-test.yml +++ b/backend/src/main/resources/application-test.yml @@ -1,4 +1,9 @@ spring: + data: + web: + pageable: + max-page-size: 100 + main: allow-bean-definition-overriding: true @@ -6,10 +11,6 @@ spring: url: jdbc:h2:~/dallog;MODE=MYSQL;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE username: sa - sql: - init: - mode: NEVER - jpa: properties: hibernate: @@ -19,10 +20,15 @@ spring: hibernate: ddl-auto: create - data: - web: - pageable: - max-page-size: 100 +cors: + allow-origin: + urls: http://localhost:3000 + +security: + jwt: + token: + secret-key: fsmjgbdafmjgbasmfgadbsgmadfhgbfamjghbvmssdgsdfgdf + expire-length: 3600000 oauth: google: @@ -34,13 +40,4 @@ oauth: - https://www.googleapis.com/auth/userinfo.profile - https://www.googleapis.com/auth/userinfo.email token-uri: https://oauth2.googleapis.com/token - -cors: - allow-origin: - urls: http://localhost:3000 - -security: - jwt: - token: - secret-key: fsmjgbdafmjgbasmfgadbsgmadfhgbfamjghbvmssdgsdfgdf - expire-length: 3600000 + access-type: offline diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config index 553c1765..cf508249 160000 --- a/backend/src/main/resources/config +++ b/backend/src/main/resources/config @@ -1 +1 @@ -Subproject commit 553c17653bab46e2ebd0ff2afd45e05518ba337d +Subproject commit cf5082492d2b7850ed1d9da54d6933dd96f29806 diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml index b7676f92..07c7a542 100644 --- a/backend/src/main/resources/logback-spring.xml +++ b/backend/src/main/resources/logback-spring.xml @@ -55,18 +55,26 @@ - + + - + + + + + - + - + + + + From e2a3abd8c0cea279d548efab7c90795036c4b3fb Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Thu, 22 Sep 2022 15:35:21 +0900 Subject: [PATCH 131/148] =?UTF-8?q?perf:=20=ED=8F=B0=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EB=82=98=EB=AF=B9=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=EC=85=8B=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: jhy979 --- frontend/src/index.html | 6 ++++++ frontend/src/styles/GlobalStyle.tsx | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/frontend/src/index.html b/frontend/src/index.html index 43007932..4e1cffd3 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -3,6 +3,12 @@ + 달록 diff --git a/frontend/src/styles/GlobalStyle.tsx b/frontend/src/styles/GlobalStyle.tsx index efdf9a00..21ef8c0c 100644 --- a/frontend/src/styles/GlobalStyle.tsx +++ b/frontend/src/styles/GlobalStyle.tsx @@ -2,8 +2,6 @@ import { css, Global, Theme } from '@emotion/react'; import emotionReset from 'emotion-reset'; const global = ({ colors }: Theme) => css` - @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.4/dist/web/static/pretendard.css'); - ${emotionReset} *, @@ -17,7 +15,9 @@ const global = ({ colors }: Theme) => css` body { overflow: overlay; - font-family: 'Pretendard', sans-serif; + font-family: Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, 'Helvetica Neue', + 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', 'Apple Color Emoji', + 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif; font-size: 3rem; *::-webkit-scrollbar { From bfefadfe13b403fe571c281d5259d462a3c4b1e8 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Thu, 22 Sep 2022 16:49:03 +0900 Subject: [PATCH 132/148] =?UTF-8?q?perf:=20=EC=8B=9C=EC=9E=91=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=97=90=EC=84=9C=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=A9=EC=8B=9D=EC=9D=84=20transform=20?= =?UTF-8?q?translateX=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/StartPage/StartPage.styles.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/StartPage/StartPage.styles.ts b/frontend/src/pages/StartPage/StartPage.styles.ts index b6e161cd..cdaadc38 100644 --- a/frontend/src/pages/StartPage/StartPage.styles.ts +++ b/frontend/src/pages/StartPage/StartPage.styles.ts @@ -46,7 +46,7 @@ const dateItemStyle = ({ colors }: Theme) => css` const itemStyle = css` position: relative; - left: -100%; + transform: translateX(-100%); width: 200rem; height: 50rem; @@ -61,11 +61,11 @@ const itemStyle = css` @keyframes slideIn { from { opacity: 0; - left: -100%; + transform: translateX(-100%); } to { - left: 0; + transform: translateX(0); opacity: 1; } } From dd41c5736d12535e8d7fb94f0dc2c0a8d3d8be8f Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 22 Sep 2022 17:26:26 +0900 Subject: [PATCH 133/148] =?UTF-8?q?fix:=20=EB=B3=B5=EC=88=98=20=EC=A2=85?= =?UTF-8?q?=EC=9D=BC=20=EC=9D=BC=EC=A0=95=20=EB=A7=88=EC=A7=80=EB=A7=89=20?= =?UTF-8?q?=EB=82=A0=EC=A7=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20=EC=9A=B0?= =?UTF-8?q?=EC=84=A0=EC=88=9C=EC=9C=84=20=EB=A1=9C=EC=A7=81=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/hooks/useSchedulePriority.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frontend/src/hooks/useSchedulePriority.ts b/frontend/src/hooks/useSchedulePriority.ts index f9beef4f..d45139a9 100644 --- a/frontend/src/hooks/useSchedulePriority.ts +++ b/frontend/src/hooks/useSchedulePriority.ts @@ -1,8 +1,9 @@ import { ScheduleType } from '@/@types/schedule'; import { CALENDAR } from '@/constants'; +import { DATE_TIME } from '@/constants/date'; -import { getFormattedDate, getISODateString } from '@/utils/date'; +import { getFormattedDate, getISODateString, getISOTimeString } from '@/utils/date'; interface DateType { year: number; @@ -32,12 +33,13 @@ function useSchedulePriority(calendarMonth: DateType[]) { longTerms.map((el) => { const startDate = getISODateString(el.startDateTime); const endDate = getISODateString(el.endDateTime); - + const isAllDay = getISOTimeString(el.endDateTime).startsWith(DATE_TIME.END); const scheduleRange = calendarMonth + .filter((el) => { const date = getFormattedDate(el.year, el.month, el.date); - return startDate <= date && date <= endDate; + return startDate <= date && (date < endDate || (date == endDate && !isAllDay)); }) .map((el) => getFormattedDate(el.year, el.month, el.date)); From f7bb7672d84055a73e48f81e73e3fcd442fa4233 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 22 Sep 2022 17:33:44 +0900 Subject: [PATCH 134/148] =?UTF-8?q?fix:=20=EC=9D=BC=EC=A0=95=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EC=8B=9C=20=EA=B5=AC=EA=B8=80=20=EC=B9=B4=ED=85=8C?= =?UTF-8?q?=EA=B3=A0=EB=A6=AC=EB=A5=BC=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EB=AA=BB=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/ScheduleModifyModal/ScheduleModifyModal.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx index 0eb11b4f..4f3e2089 100644 --- a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -18,6 +18,7 @@ import Fieldset from '@/components/@common/Fieldset/Fieldset'; import Select from '@/components/@common/Select/Select'; import { CACHE_KEY } from '@/constants/api'; +import { CATEGORY_TYPE } from '@/constants/category'; import { DATE_TIME, TIMES } from '@/constants/date'; import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; @@ -124,7 +125,9 @@ function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalPr setAllDay((prev) => !prev); }; - const categories = categoriesGetResponse?.data.map((category) => category.name); + const categories = categoriesGetResponse?.data + .filter((category) => category.categoryType !== CATEGORY_TYPE.GOOGLE) + .map((category) => category.name); return (
From 7eab0fa76a30ed652e42d94e6d8907b47a7a1b66 Mon Sep 17 00:00:00 2001 From: Daye Lee <508yeah@gmail.com> Date: Thu, 22 Sep 2022 17:32:24 +0900 Subject: [PATCH 135/148] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=ED=8C=8C=EC=9D=BC=20=EB=B0=8F=20=EC=BD=94=EB=93=9C?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/@types/scheduler.ts | 6 - frontend/src/App.tsx | 2 - frontend/src/api/scheduler.ts | 33 ---- frontend/src/components/NavBar/NavBar.tsx | 9 -- frontend/src/constants/api.ts | 1 - frontend/src/constants/index.ts | 1 - .../SchedulingPage/SchedulingPage.styles.ts | 124 -------------- .../pages/SchedulingPage/SchedulingPage.tsx | 152 ------------------ 8 files changed, 328 deletions(-) delete mode 100644 frontend/src/@types/scheduler.ts delete mode 100644 frontend/src/api/scheduler.ts delete mode 100644 frontend/src/pages/SchedulingPage/SchedulingPage.styles.ts delete mode 100644 frontend/src/pages/SchedulingPage/SchedulingPage.tsx diff --git a/frontend/src/@types/scheduler.ts b/frontend/src/@types/scheduler.ts deleted file mode 100644 index dee1743a..00000000 --- a/frontend/src/@types/scheduler.ts +++ /dev/null @@ -1,6 +0,0 @@ -interface SchedulingResponseType { - startDateTime: string; - endDateTime: string; -} - -export { SchedulingResponseType }; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ae206e18..2755e89f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,7 +21,6 @@ const CategoryPage = lazy(() => import('@/pages/CategoryPage/CategoryPage')); const MainPage = lazy(() => import('@/pages/MainPage/MainPage')); const NotFoundPage = lazy(() => import('@/pages/NotFoundPage/NotFoundPage')); const PrivacyPolicyPage = lazy(() => import('@/pages/PrivacyPolicyPage/PrivacyPolicyPage')); -const SchedulingPage = lazy(() => import('@/pages/SchedulingPage/SchedulingPage')); function App() { const { openSnackBar } = useSnackBar(); @@ -61,7 +60,6 @@ function App() { } /> }> } /> - } /> diff --git a/frontend/src/api/scheduler.ts b/frontend/src/api/scheduler.ts deleted file mode 100644 index f846ff22..00000000 --- a/frontend/src/api/scheduler.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { SchedulingResponseType } from '@/@types/scheduler'; - -import { DATE_TIME } from '@/constants/date'; - -import dallogApi from '.'; - -const schedulerApi = { - endpoint: (categoryId: number) => `/api/scheduler/categories/${categoryId}/available-periods`, - - headers: { - 'Content-Type': 'application/json', - }, - - get: async ( - accessToken: string, - categoryId: number, - startDateTime: string, - endDateTime: string - ) => { - const response = await dallogApi.get( - `${schedulerApi.endpoint(categoryId)}?startDateTime=${startDateTime}T${ - DATE_TIME.START - }&endDateTime=${endDateTime}T${DATE_TIME.END}`, - { - headers: { ...schedulerApi.headers, Authorization: `Bearer ${accessToken}` }, - } - ); - - return response; - }, -}; - -export default schedulerApi; diff --git a/frontend/src/components/NavBar/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx index 9874c23f..42d0ba60 100644 --- a/frontend/src/components/NavBar/NavBar.tsx +++ b/frontend/src/components/NavBar/NavBar.tsx @@ -16,7 +16,6 @@ import { PATH } from '@/constants'; import { TRANSPARENT } from '@/constants/style'; import { - MdAccessTime, MdCalendarToday, MdMenu, MdMenuOpen, @@ -50,10 +49,6 @@ function NavBar() { navigate(PATH.CATEGORY); }; - const handleClickSchedulingMenuButton = () => { - navigate(PATH.SCHEDULING); - }; - const handleClickProfileMenuButton = () => { toggleProfileModalOpen(); }; @@ -83,10 +78,6 @@ function NavBar() { 카테고리 - -
- {schedulingGetResponse && - schedulingGetResponse.data.map((schedule) => ( -
-
{formatDateTime(schedule.startDateTime)}
- -
{formatDateTime(schedule.endDateTime)}
-
- ))} -
-
- - ); -} - -export default SchedulingPage; From 99480954a40d60c17bc368b8de13231aa9cd1dc4 Mon Sep 17 00:00:00 2001 From: jhy979 Date: Thu, 22 Sep 2022 17:45:19 +0900 Subject: [PATCH 136/148] =?UTF-8?q?fix:=20=EC=A0=9C=EC=96=B4=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20=EC=A2=85?= =?UTF-8?q?=EC=9D=BC=20=EC=B2=B4=ED=81=AC=EB=B0=95=EC=8A=A4=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=9C=EC=96=B4=20=EB=8C=80=EC=83=81=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx | 1 + .../src/components/ScheduleModifyModal/ScheduleModifyModal.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx index 46f275c0..cd25851e 100644 --- a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -143,6 +143,7 @@ function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { id="allDay" checked={isAllDay} onClick={handleClickAllDayButton} + readOnly />