diff --git a/.github/ISSUE_TEMPLATE/bug-template.md b/.github/ISSUE_TEMPLATE/bug-template.md index 0c0d4d81..baf919c0 100644 --- a/.github/ISSUE_TEMPLATE/bug-template.md +++ b/.github/ISSUE_TEMPLATE/bug-template.md @@ -1,7 +1,7 @@ --- name: Bug Template about: 버그를 이슈에 등록한다. -title: "" +title: '이슈의 제목을 입력해주세요!' labels: '' assignees: '' --- @@ -16,4 +16,3 @@ assignees: '' ## 📸 스크린샷 ## 👄 참고 사항 - diff --git a/.github/ISSUE_TEMPLATE/feature-template.md b/.github/ISSUE_TEMPLATE/feature-template.md index 752e91d4..b34d74cb 100644 --- a/.github/ISSUE_TEMPLATE/feature-template.md +++ b/.github/ISSUE_TEMPLATE/feature-template.md @@ -1,7 +1,7 @@ --- name: Feature Template about: 구현할 기능을 이슈에 등록한다. -title: "" +title: '이슈의 제목을 입력해주세요!' labels: '' assignees: '' --- @@ -17,4 +17,3 @@ assignees: '' ## 📄 참고 사항 ## ⏰ 예상 소요 기간 - diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 3af222a0..e5f724db 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,10 @@ +- [ ] 🔀 PR 제목의 형식을 잘 작성했나요? e.g. `[feat] PR을 등록한다.` - [ ] 💯 테스트는 잘 통과했나요? - [ ] 🏗️ 빌드는 성공했나요? - [ ] 🧹 불필요한 코드는 제거했나요? - [ ] 💭 이슈는 등록했나요? - [ ] 🏷️ 라벨은 등록했나요? +- [ ] 💻 git rebase를 사용했나요? - [ ] 🌈 알록달록한가요? ## 작업 내용 @@ -12,4 +14,3 @@ ## 주의사항 Closes #{이슈 번호} - diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml new file mode 100644 index 00000000..8d58dd5e --- /dev/null +++ b/.github/workflows/backend-ci.yml @@ -0,0 +1,39 @@ +name: Backend CI + +on: + pull_request: + branches: + - main + - develop + paths: + - backend/** + - .github/** # Github Actions 작업을 위한 포함 + +jobs: + build: + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./backend + + steps: + - uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: "11" + distribution: "temurin" + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Build with Gradle + run: ./gradlew build + + - name: Publish Unit Test Results + uses: EnricoMi/publish-unit-test-result-action@v1 + if: ${{ always() }} + with: + files: build/test-results/**/*.xml diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml new file mode 100644 index 00000000..81475110 --- /dev/null +++ b/.github/workflows/frontend-ci.yml @@ -0,0 +1,38 @@ +name: Frontend CI + +on: + pull_request: + branches: + - main + - develop + paths: + - frontend/** + - .github/** # Github Actions 작업을 위한 포함 + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./frontend + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14" + + - name: Install node packages + run: npm install + + - name: Check lint + run: npm run check-lint + + - name: Check prettier + run: npm run check-prettier + + - name: Build + run: npm run dev-build + + - name: Component test + run: npm run test diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4f39dc5d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "backend/src/main/resources/config"] + path = backend/src/main/resources/config + url = https://github.com/dallog/config.git diff --git a/README.md b/README.md index 098cf0a2..51809af0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # 2022-dallog +## 알록달록🌈 팀원 소개 + +| Backend | Backend | Backend | Backend | Frontend | Frontend | +| :------------------------------------------: | :------------------------------------------------: | :----------------------------------------------: | :------------------------------------------: | :--------------------------------------------: | :-----------------------------------------: | +| ![](https://github.com/hyeonic.png?size=120) | ![](https://github.com/gudonghee2000.png?size=120) | ![](https://github.com/summerlunaa.png?size=120) | ![](https://github.com/devHudi.png?size=120) | ![](https://github.com/daaaayeah.png?size=120) | ![](https://github.com/jhy979.png?size=120) | +| [매트(최기현)](https://github.com/hyeonic) | [리버(구동희)](https://github.com/gudonghee2000) | [파랑(이하은)](https://github.com/summerlunaa) | [후디(조동현)](https://github.com/devHudi) | [티거(이다예)](https://github.com/daaaayeah) | [나인(장호영)](https://github.com/jhy979) | diff --git a/backend/.gitignore b/backend/.gitignore index c2065bc2..dcd2aed6 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -35,3 +35,9 @@ out/ ### VS Code ### .vscode/ + +### resources ### +/src/main/resources/static/docs + +### logs ### +logs/ diff --git a/backend/build.gradle b/backend/build.gradle index fc7408c2..7f26c83a 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -1,25 +1,154 @@ plugins { - id 'org.springframework.boot' version '2.7.1' - id 'io.spring.dependency-management' version '1.0.11.RELEASE' - id 'java' + id 'org.springframework.boot' version '2.7.1' + id 'io.spring.dependency-management' version '1.0.11.RELEASE' + id 'org.asciidoctor.jvm.convert' version '3.3.2' + id 'org.sonarqube' version '3.3' + id 'java' + id 'jacoco' } group = 'com.allog' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' +ext { + snippetsDir = file('build/generated-snippets') +} + +configurations { + asciidoctorExtensions +} + repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-validation' - implementation 'org.springframework.boot:spring-boot-starter-web' - runtimeOnly 'com.h2database:h2' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + 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' + + runtimeOnly 'mysql:mysql-connector-java' + runtimeOnly 'com.h2database:h2' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'io.rest-assured:rest-assured:4.4.0' + + // JWT를 위한 의존성 + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // Restdocs를 위한 의존성 + asciidoctorExtensions 'org.springframework.restdocs:spring-restdocs-asciidoctor' + testImplementation 'org.springframework.restdocs:spring-restdocs-mockmvc' + + // Sonarqube를 위한 의존성 + implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.3' +} + +test { + outputs.dir snippetsDir + useJUnitPlatform() + finalizedBy 'jacocoTestReport' +} + +jacoco { + toolVersion = "0.8.8" +} + +jacocoTestReport { + reports { + xml.enabled true + csv.enabled true + html.enabled true + + xml.destination file("${buildDir}/jacoco/index.xml") + csv.destination file("${buildDir}/jacoco/index.csv") + html.destination file("${buildDir}/jacoco/index.html") + } + + afterEvaluate { + classDirectories.setFrom( + files(classDirectories.files.collect { + fileTree(dir: it, excludes: [ + '**/*Application*', + '**/*Exception*', + '**/dto/**', + '**/infrastructure/**', + '**/global/config/**', + '**/BaseEntity*', + '**/ControllerAdvice*', + '**/AuthorizationExtractor*' + ]) + }) + ) + } + + finalizedBy 'jacocoTestCoverageVerification' +} + +jacocoTestCoverageVerification { + violationRules { + rule { + enabled = true + element = "CLASS" + + // 모든 클래스 각각 라인 커버리지 75% 만족시 빌드 성공 + limit { + counter = 'LINE' + value = 'COVEREDRATIO' + minimum = 0.75 + } + + excludes = [ + '*.*Application', + '*.*Exception', + '*.dto.*', + '*.infrastructure.*', + '*.global.config.*', + '*.BaseEntity', + '*.ControllerAdvice', + '*.AuthorizationExtractor' + ] + } + } +} + +sonarqube { + properties { + property 'sonar.projectKey', 'dallog' + property "sonar.sources", "src" + property "sonar.language", "java" + property "sonar.sourceEncoding", "UTF-8" + property "sonar.profile", "Sonar way" + property "sonar.java.binaries", "${buildDir}/classes" + property "sonar.test.inclusions", "**/*Test.java" + property 'sonar.exclusions', '**/jacoco/**' + property 'sonar.coverage.exclusions', '**/test/**/*, **/*Application*, **/global/config/**, **/dto/**, **/*Exception*, **/infrastructure/**, **/BaseEntity*, **/ControllerAdvice*, **/AuthorizationExtractor*' + property "sonar.coverage.jacoco.xmlReportPaths", "${buildDir}/jacoco/index.xml" + } +} + +asciidoctor { + configurations 'asciidoctorExtensions' + baseDirFollowsSourceFile() + inputs.dir snippetsDir + dependsOn test +} + +asciidoctor.doFirst { + delete file('src/main/resources/static/docs') +} + +task copyDocument(type: Copy) { + dependsOn asciidoctor + + from "${asciidoctor.outputDir}" + into file("src/main/resources/static/docs") } -tasks.named('test') { - useJUnitPlatform() +bootJar { + dependsOn copyDocument } diff --git a/backend/src/docs/asciidoc/auth.adoc b/backend/src/docs/asciidoc/auth.adoc new file mode 100644 index 00000000..c785188a --- /dev/null +++ b/backend/src/docs/asciidoc/auth.adoc @@ -0,0 +1,59 @@ +== Auth(인증) + +=== OAuth 로그인 링크 생성 + +==== Request + +include::{snippets}/auth/link/http-request.adoc[] + +==== Path Parameters + +include::{snippets}/auth/link/path-parameters.adoc[] + +==== Request Parameters + +include::{snippets}/auth/link/request-parameters.adoc[] + +==== Response + +include::{snippets}/auth/link/http-response.adoc[] + +==== ResponseFields + +include::{snippets}/auth/link/response-fields.adoc[] + +=== OAuth 로그인 + +==== Request + +include::{snippets}/auth/token/http-request.adoc[] + +==== PathParameters + +include::{snippets}/auth/token/path-parameters.adoc[] + +==== RequestFields + +include::{snippets}/auth/token/request-fields.adoc[] + +==== Response + +include::{snippets}/auth/token/http-response.adoc[] + +=== OAuth 로그인 : Resource Server 에러 + +==== Request + +include::{snippets}/auth/exception/token/http-request.adoc[] + +==== PathParameters + +include::{snippets}/auth/exception/token/path-parameters.adoc[] + +==== RequestFields + +include::{snippets}/auth/exception/token/request-fields.adoc[] + +==== Response + +include::{snippets}/auth/exception/token/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/category.adoc b/backend/src/docs/asciidoc/category.adoc new file mode 100644 index 00000000..91f9ceed --- /dev/null +++ b/backend/src/docs/asciidoc/category.adoc @@ -0,0 +1,145 @@ +== Category(카테고리) + +=== 카테고리 생성 + +==== Request + +include::{snippets}/categories/save/http-request.adoc[] + +==== Response + +include::{snippets}/categories/save/http-response.adoc[] + +=== 카테고리 생성 (유효하지 않은 카테고리 이름) + +==== Request + +include::{snippets}/categories/save/badRequest/http-request.adoc[] + +==== Response + +include::{snippets}/categories/save/badRequest/http-response.adoc[] + +=== 전체 카테고리 조회 + +==== Request + +include::{snippets}/categories/findAll/http-request.adoc[] + +==== RequestParameters + +include::{snippets}/categories/findAll/request-parameters.adoc[] + +==== Response + +include::{snippets}/categories/findAll/http-response.adoc[] + +=== 카테고리 제목 조회 + +==== Request + +include::{snippets}/categories/findAllLikeName/http-request.adoc[] + +==== RequestParameters + +include::{snippets}/categories/findAllLikeName/request-parameters.adoc[] + +==== Response + +include::{snippets}/categories/findAllLikeName/http-response.adoc[] + +=== 자신이 생성한 카테고리 조회 + +==== Request + +include::{snippets}/categories/findMine/http-request.adoc[] + +==== RequestParameters + +include::{snippets}/categories/findMine/request-parameters.adoc[] + +==== Response + +include::{snippets}/categories/findMine/response-body.adoc[] + +=== ID를 통한 카테고리 단건 조회 + +==== Request + +include::{snippets}/categories/findById/http-request.adoc[] + +==== PathParameters + +include::{snippets}/categories/findById/path-parameters.adoc[] + +==== Response + +include::{snippets}/categories/findById/http-response.adoc[] + +=== ID를 통한 카테고리 단건 조회 (존재하지 않는 경우) + +==== Request + +include::{snippets}/categories/findById/notFound/http-request.adoc[] + +==== Response + +include::{snippets}/categories/findById/notFound/http-response.adoc[] + +=== 카테고리 수정 + +==== Request + +include::{snippets}/categories/update/http-request.adoc[] + +==== PathParameters + +include::{snippets}/categories/update/path-parameters.adoc[] + +==== Response + +include::{snippets}/categories/update/http-response.adoc[] + +=== 카테고리 수정 (존재하지 않는 경우) + +==== Request + +include::{snippets}/categories/update/notFound/http-request.adoc[] + +==== Response + +include::{snippets}/categories/update/notFound/http-response.adoc[] + +=== 카테고리 수정 (유효하지 않은 카테고리 이름) + +==== Request + +include::{snippets}/categories/update/badRequest/http-request.adoc[] + +==== Response + +include::{snippets}/categories/update/badRequest/http-response.adoc[] + +=== 카테고리 삭제 + +==== Request + +include::{snippets}/categories/delete/http-request.adoc[] + +==== PathParameters + +include::{snippets}/categories/delete/path-parameters.adoc[] + +==== Response + +include::{snippets}/categories/delete/http-response.adoc[] + +=== 카테고리 삭제 (존재하지 않는 경우) + +==== Request + +include::{snippets}/categories/delete/notFound/http-request.adoc[] + +==== Response + +include::{snippets}/categories/delete/notFound/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/external-calendar.adoc b/backend/src/docs/asciidoc/external-calendar.adoc new file mode 100644 index 00000000..72e3bc0b --- /dev/null +++ b/backend/src/docs/asciidoc/external-calendar.adoc @@ -0,0 +1,47 @@ +== External Calendar (외부 캘린더) + +=== 자신의 외부 캘린더 조회 + +==== Request + +include::{snippets}/external-calendars/get/http-request.adoc[] + +==== Response + +include::{snippets}/external-calendars/get/http-response.adoc[] + +=== 자신의 외부 캘린더 저장 + +==== Request + +include::{snippets}/external-calendars/save/http-request.adoc[] + +==== Request Headers + +include::{snippets}/external-calendars/save/request-headers.adoc[] + +==== Request Body + +include::{snippets}/external-calendars/save/request-fields.adoc[] + +==== Response + +include::{snippets}/external-calendars/save/http-response.adoc[] + +=== 자신의 외부 캘린더 중복 저장 시 예외 발생 + +==== Request + +include::{snippets}/external-calendars/duplicated-save/http-request.adoc[] + +==== Request Headers + +include::{snippets}/external-calendars/duplicated-save/request-headers.adoc[] + +==== Request Body + +include::{snippets}/external-calendars/duplicated-save/request-fields.adoc[] + +==== Response + +include::{snippets}/external-calendars/duplicated-save/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/index.adoc b/backend/src/docs/asciidoc/index.adoc new file mode 100644 index 00000000..c7357dcc --- /dev/null +++ b/backend/src/docs/asciidoc/index.adoc @@ -0,0 +1,13 @@ += 달록 API 문서 +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 3 + +include::auth.adoc[] +include::member.adoc[] +include::category.adoc[] +include::schedule.adoc[] +include::subscription.adoc[] +include::external-calendar.adoc[] diff --git a/backend/src/docs/asciidoc/member.adoc b/backend/src/docs/asciidoc/member.adoc new file mode 100644 index 00000000..ece68ebb --- /dev/null +++ b/backend/src/docs/asciidoc/member.adoc @@ -0,0 +1,53 @@ +== Member(회원) + +=== 내 정보 조회 + +==== Request + +include::{snippets}/members/me/http-request.adoc[] + +==== RequestHeaders + +include::{snippets}/members/me/request-headers.adoc[] + +==== Response + +include::{snippets}/members/me/http-response.adoc[] + +=== 삭제된 회원 정보 조회 + +==== Request + +include::{snippets}/members/exception/notfound/http-request.adoc[] + +==== Response + +include::{snippets}/members/exception/notfound/http-response.adoc[] + +=== 내 정보 수정 + +==== Request + +include::{snippets}/members/update/http-request.adoc[] + +==== RequestHeaders + +include::{snippets}/members/update/request-headers.adoc[] + +==== Request Parameters + +include::{snippets}/members/update/request-body.adoc[] + +==== Response + +include::{snippets}/members/update/http-response.adoc[] + +=== 회원 탈퇴 + +==== Request + +include::{snippets}/members/delete/http-request.adoc[] + +==== Response + +include::{snippets}/members/delete/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/schedule.adoc b/backend/src/docs/asciidoc/schedule.adoc new file mode 100644 index 00000000..46ea60b8 --- /dev/null +++ b/backend/src/docs/asciidoc/schedule.adoc @@ -0,0 +1,137 @@ +== Schedule(일정) + +=== 회원 일정 목록 조회 + +==== Request + +include::{snippets}/schedules/findAllByMember/http-request.adoc[] + +==== RequestParameters + +include::{snippets}/schedules/findAllByMember/request-parameters.adoc[] + +==== Response + +include::{snippets}/schedules/findAllByMember/http-response.adoc[] + +=== 회원 일정 및 외부 일정 목록 조회 + +==== Request + +include::{snippets}/schedules/findAllByMemberWithExternalSchedule/http-request.adoc[] + +==== RequestParameters + +include::{snippets}/schedules/findAllByMemberWithExternalSchedule/request-parameters.adoc[] + +==== Response + +include::{snippets}/schedules/findAllByMemberWithExternalSchedule/http-response.adoc[] + +=== 일정 등록 + +==== Request + +include::{snippets}/schedules/save/http-request.adoc[] + +==== Response + +include::{snippets}/schedules/save/http-response.adoc[] + +=== 일정 생성 (카테고리 권한 없음) + +==== Response + +include::{snippets}/schedules/save/forbidden/http-response.adoc[] + +=== 일정 생성 (카테고리가 존재하지 않음) + +==== Response + +include::{snippets}/schedules/save/notfound/http-response.adoc[] + +=== 일정 단건 조회 + +==== Request + +include::{snippets}/schedules/findone/http-request.adoc[] + +==== Response + +include::{snippets}/schedules/findone/http-response.adoc[] + +=== 일정 단건 조회 (일정이 존재하지 않음) + +==== Response + +include::{snippets}/schedules/findone/notfound/http-response.adoc[] + +=== 일정 수정 + +==== Request + +include::{snippets}/schedules/update/http-request.adoc[] + +==== Path Variable + +include::{snippets}/schedules/update/path-parameters.adoc[] + +==== Response + +include::{snippets}/schedules/update/http-response.adoc[] + +=== 일정 수정 (카테고리 권한 없음) + +==== Response + +include::{snippets}/schedules/update/forbidden/http-response.adoc[] + +=== 일정 수정 (카테고리가 존재하지 않음) + +==== Response + +include::{snippets}/schedules/update/notfound/http-response.adoc[] + +=== 일정 제거 + +==== Request + +include::{snippets}/schedules/delete/http-request.adoc[] + +==== Path Variable + +include::{snippets}/schedules/delete/path-parameters.adoc[] + +==== Response + +include::{snippets}/schedules/delete/http-response.adoc[] + +=== 일정 제거 (카테고리 권한 없음) + +==== Response + +include::{snippets}/schedules/delete/forbidden/http-response.adoc[] + +=== 일정 제거 (카테고리가 존재하지 않음) + +==== Response + +include::{snippets}/schedules/delete/notfound/http-response.adoc[] + +=== 일정 조율 + +카테고리 ID를 전달하면, 해당 카테고리를 구독중인 유저의 모든 체크한 구독 정보를 바탕으로 일정이 없는 시간대를 반환합니다. + +==== Request + +include::{snippets}/scheduler/category/available-periods/http-request.adoc[] + +==== Parameters + +include::{snippets}/scheduler/category/available-periods/path-parameters.adoc[] + +include::{snippets}/scheduler/category/available-periods/request-parameters.adoc[] + +==== Response + +include::{snippets}/scheduler/category/available-periods/http-response.adoc[] diff --git a/backend/src/docs/asciidoc/subscription.adoc b/backend/src/docs/asciidoc/subscription.adoc new file mode 100644 index 00000000..93dbcedc --- /dev/null +++ b/backend/src/docs/asciidoc/subscription.adoc @@ -0,0 +1,123 @@ +== Subscription(구독) + +=== 구독 등록 + +==== Request + +include::{snippets}/subscription/save/http-request.adoc[] + +==== Path Parameters + +include::{snippets}/subscription/save/path-parameters.adoc[] + +==== Request Headers + +include::{snippets}/subscription/save/request-headers.adoc[] + +==== 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[] + +==== Response + +include::{snippets}/subscription/exist/http-response.adoc[] + +==== Request + +include::{snippets}/subscription/me/http-request.adoc[] + +==== Request Headers + +include::{snippets}/subscription/me/request-headers.adoc[] + +==== Response + +include::{snippets}/subscription/me/http-response.adoc[] + +=== 3자의 개인 카테고리 구독 요청시 + +==== Response + +include::{snippets}/subscription/private-category/http-response.adoc[] + +=== 내 구독 정보 수정 + +==== Request + +include::{snippets}/subscription/update/http-request.adoc[] + +==== Path Parameters + +include::{snippets}/subscription/update/path-parameters.adoc[] + +==== Request Headers + +include::{snippets}/subscription/update/request-headers.adoc[] + +==== Request Parameters + +include::{snippets}/subscription/update/request-body.adoc[] + +==== Response + +include::{snippets}/subscription/update/http-response.adoc[] + +=== 내 구독 정보 삭제 + +==== Request + +include::{snippets}/subscription/delete/http-request.adoc[] + +==== Path Parameters + +include::{snippets}/subscription/delete/path-parameters.adoc[] + +==== Request Headers + +include::{snippets}/subscription/delete/request-headers.adoc[] + +==== 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 + +include::{snippets}/subscription/permission/http-response.adoc[] diff --git a/backend/src/main/java/com/allog/dallog/DallogApplication.java b/backend/src/main/java/com/allog/dallog/DallogApplication.java index 12175e2c..0d989070 100644 --- a/backend/src/main/java/com/allog/dallog/DallogApplication.java +++ b/backend/src/main/java/com/allog/dallog/DallogApplication.java @@ -6,8 +6,7 @@ @SpringBootApplication public class DallogApplication { - public static void main(String[] args) { - SpringApplication.run(DallogApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(DallogApplication.class, args); + } } 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 new file mode 100644 index 00000000..fdcb2dd1 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/AuthService.java @@ -0,0 +1,79 @@ +package com.allog.dallog.domain.auth.application; + +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.member.domain.Member; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class AuthService { + + 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) { + this.oAuthUri = oAuthUri; + this.oAuthClient = oAuthClient; + this.memberService = memberService; + this.registerService = registerService; + this.oAuthTokenRepository = oAuthTokenRepository; + this.tokenProvider = tokenProvider; + } + + public String generateGoogleLink(final String redirectUri) { + return oAuthUri.generate(redirectUri); + } + + @Transactional + public TokenResponse generateToken(final TokenRequest tokenRequest) { + String code = tokenRequest.getCode(); + String redirectUri = tokenRequest.getRedirectUri(); + + OAuthMember oAuthMember = oAuthClient.getOAuthMember(code, redirectUri); + Member foundMember = getMember(oAuthMember); + + OAuthToken oAuthToken = getOAuthToken(oAuthMember, foundMember); + oAuthToken.change(oAuthMember.getRefreshToken()); + String accessToken = tokenProvider.createToken(String.valueOf(foundMember.getId())); + + return new TokenResponse(accessToken); + } + + private Member getMember(final OAuthMember oAuthMember) { + if (!memberService.existsByEmail(oAuthMember.getEmail())) { + registerService.register(oAuthMember); + } + + return memberService.getByEmail(oAuthMember.getEmail()); + } + + private OAuthToken getOAuthToken(final OAuthMember oAuthMember, final Member foundMember) { + if (!oAuthTokenRepository.existsByMemberId(foundMember.getId())) { + oAuthTokenRepository.save(new OAuthToken(foundMember, oAuthMember.getRefreshToken())); + } + + return oAuthTokenRepository.findByMemberId(foundMember.getId()) + .orElseThrow(NoSuchOAuthTokenException::new); + } + + public Long extractMemberId(final String accessToken) { + tokenProvider.validateToken(accessToken); + Long memberId = Long.valueOf(tokenProvider.getPayload(accessToken)); + memberService.validateExistsMember(memberId); + return memberId; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/JwtTokenProvider.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/JwtTokenProvider.java new file mode 100644 index 00000000..f6e57cca --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/JwtTokenProvider.java @@ -0,0 +1,66 @@ +package com.allog.dallog.domain.auth.application; + +import com.allog.dallog.domain.auth.exception.InvalidTokenException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtTokenProvider implements TokenProvider { + + private final SecretKey key; + private final long validityInMilliseconds; + + public JwtTokenProvider(@Value("${security.jwt.token.secret-key}") final String secretKey, + @Value("${security.jwt.token.expire-length}") final long validityInMilliseconds) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + this.validityInMilliseconds = validityInMilliseconds; + } + + @Override + public String createToken(final String payload) { + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setSubject(payload) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + @Override + public String getPayload(final String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + + @Override + public void validateToken(final String token) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + + claims.getBody() + .getExpiration() + .before(new Date()); + } catch (JwtException | IllegalArgumentException e) { + throw new InvalidTokenException("권한이 없습니다."); + } + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthClient.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthClient.java new file mode 100644 index 00000000..a21239e0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthClient.java @@ -0,0 +1,11 @@ +package com.allog.dallog.domain.auth.application; + +import com.allog.dallog.domain.auth.dto.OAuthMember; +import com.allog.dallog.domain.auth.dto.response.OAuthAccessTokenResponse; + +public interface OAuthClient { + + OAuthMember getOAuthMember(final String code, final String redirectUri); + + OAuthAccessTokenResponse getAccessToken(final String refreshToken); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthUri.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthUri.java new file mode 100644 index 00000000..d14cc469 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/OAuthUri.java @@ -0,0 +1,7 @@ +package com.allog.dallog.domain.auth.application; + +@FunctionalInterface +public interface OAuthUri { + + String generate(final String redirectUri); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/application/TokenProvider.java b/backend/src/main/java/com/allog/dallog/domain/auth/application/TokenProvider.java new file mode 100644 index 00000000..e5105806 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/application/TokenProvider.java @@ -0,0 +1,10 @@ +package com.allog.dallog.domain.auth.application; + +public interface TokenProvider { + + String createToken(final String payload); + + String getPayload(final String token); + + void validateToken(final String token); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthToken.java b/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthToken.java new file mode 100644 index 00000000..d3fc29d0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthToken.java @@ -0,0 +1,53 @@ +package com.allog.dallog.domain.auth.domain; + +import com.allog.dallog.domain.common.BaseEntity; +import com.allog.dallog.domain.member.domain.Member; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Table(name = "oauth_tokens") +@Entity +public class OAuthToken extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "members_id", nullable = false) + private Member member; + + @Column(name = "refresh_token", nullable = false) + private String refreshToken; + + protected OAuthToken() { + } + + public OAuthToken(final Member member, final String refreshToken) { + this.member = member; + this.refreshToken = refreshToken; + } + + public void change(final String refreshToken) { + this.refreshToken = refreshToken; + } + + public Long getId() { + return id; + } + + public Member getMember() { + return member; + } + + public String getRefreshToken() { + return refreshToken; + } +} 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 new file mode 100644 index 00000000..c95cf2f8 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepository.java @@ -0,0 +1,13 @@ +package com.allog.dallog.domain.auth.domain; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface OAuthTokenRepository extends JpaRepository { + + boolean existsByMemberId(final Long memberId); + + Optional findByMemberId(final Long memberId); + + void deleteByMemberId(final Long memberId); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/LoginMember.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/LoginMember.java new file mode 100644 index 00000000..c414adca --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/LoginMember.java @@ -0,0 +1,17 @@ +package com.allog.dallog.domain.auth.dto; + +public class LoginMember { + + private Long id; + + private LoginMember() { + } + + public LoginMember(final Long id) { + this.id = id; + } + + public Long getId() { + return id; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/OAuthMember.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/OAuthMember.java new file mode 100644 index 00000000..c0894f38 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/OAuthMember.java @@ -0,0 +1,33 @@ +package com.allog.dallog.domain.auth.dto; + +public class OAuthMember { + + private final String email; + private final String displayName; + private final String profileImageUrl; + private final String refreshToken; + + public OAuthMember(final String email, final String displayName, final String profileImageUrl, + final String refreshToken) { + this.email = email; + this.displayName = displayName; + this.profileImageUrl = profileImageUrl; + this.refreshToken = refreshToken; + } + + public String getEmail() { + return email; + } + + public String getDisplayName() { + return displayName; + } + + public String getProfileImageUrl() { + return profileImageUrl; + } + + public String getRefreshToken() { + return refreshToken; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/request/TokenRequest.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/request/TokenRequest.java new file mode 100644 index 00000000..3b84326c --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/request/TokenRequest.java @@ -0,0 +1,29 @@ +package com.allog.dallog.domain.auth.dto.request; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +public class TokenRequest { + + @NotBlank(message = "인가 코드는 공백일 수 없습니다.") + private String code; + + @NotNull(message = "Null일 수 없습니다.") + private String redirectUri; + + private TokenRequest() { + } + + public TokenRequest(final String code, final String redirectUri) { + this.code = code; + this.redirectUri = redirectUri; + } + + public String getCode() { + return code; + } + + public String getRedirectUri() { + return redirectUri; + } +} 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 new file mode 100644 index 00000000..9b8539b1 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthAccessTokenResponse.java @@ -0,0 +1,40 @@ +package com.allog.dallog.domain.auth.dto.response; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) +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) { + 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/main/java/com/allog/dallog/domain/auth/dto/response/OAuthUriResponse.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthUriResponse.java new file mode 100644 index 00000000..1a4dcf31 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/OAuthUriResponse.java @@ -0,0 +1,18 @@ +package com.allog.dallog.domain.auth.dto.response; + +// OAuth 인증 URI(소셜 로그인 링크)를 전달하는 DTO +public class OAuthUriResponse { + + private String oAuthUri; + + private OAuthUriResponse() { + } + + public OAuthUriResponse(final String oAuthUri) { + this.oAuthUri = oAuthUri; + } + + public String getoAuthUri() { + return oAuthUri; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/TokenResponse.java b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/TokenResponse.java new file mode 100644 index 00000000..16ce9455 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/dto/response/TokenResponse.java @@ -0,0 +1,17 @@ +package com.allog.dallog.domain.auth.dto.response; + +public class TokenResponse { + + private String accessToken; + + private TokenResponse() { + } + + public TokenResponse(final String accessToken) { + this.accessToken = accessToken; + } + + public String getAccessToken() { + return accessToken; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/exception/EmptyAuthorizationHeaderException.java b/backend/src/main/java/com/allog/dallog/domain/auth/exception/EmptyAuthorizationHeaderException.java new file mode 100644 index 00000000..af2611f8 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/exception/EmptyAuthorizationHeaderException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.auth.exception; + +public class EmptyAuthorizationHeaderException extends RuntimeException { + + public EmptyAuthorizationHeaderException(final String message) { + super(message); + } + + public EmptyAuthorizationHeaderException() { + this("header에 Authorization이 존재하지 않습니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/exception/InvalidTokenException.java b/backend/src/main/java/com/allog/dallog/domain/auth/exception/InvalidTokenException.java new file mode 100644 index 00000000..db514ee4 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/exception/InvalidTokenException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.auth.exception; + +public class InvalidTokenException extends RuntimeException { + + public InvalidTokenException(final String message) { + super(message); + } + + public InvalidTokenException() { + this("유효하지 않은 토큰입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoPermissionException.java b/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoPermissionException.java new file mode 100644 index 00000000..52f85e18 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoPermissionException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.auth.exception; + +public class NoPermissionException extends RuntimeException { + + public NoPermissionException(final String message) { + super(message); + } + + public NoPermissionException() { + this("권한이 없는 요청 입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoSuchOAuthTokenException.java b/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoSuchOAuthTokenException.java new file mode 100644 index 00000000..fc093a3b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/auth/exception/NoSuchOAuthTokenException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.auth.exception; + +public class NoSuchOAuthTokenException extends RuntimeException { + + public NoSuchOAuthTokenException(final String message) { + super(message); + } + + public NoSuchOAuthTokenException() { + this("존재하지 않는 OAuthToken 입니다."); + } +} 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 new file mode 100644 index 00000000..898ca2aa --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/application/CategoryService.java @@ -0,0 +1,164 @@ +package com.allog.dallog.domain.category.application; + +import static com.allog.dallog.domain.category.domain.CategoryType.NORMAL; + +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; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetail; +import com.allog.dallog.domain.category.domain.ExternalCategoryDetailRepository; +import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; +import com.allog.dallog.domain.category.dto.request.CategoryUpdateRequest; +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.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; +import java.util.stream.Collectors; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class CategoryService { + + private final CategoryRepository categoryRepository; + private final ExternalCategoryDetailRepository externalCategoryDetailRepository; + private final MemberRepository memberRepository; + private final SubscriptionRepository subscriptionRepository; + private final ScheduleRepository scheduleRepository; + + public CategoryService(final CategoryRepository categoryRepository, + final ExternalCategoryDetailRepository externalCategoryDetailRepository, + final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, + final ScheduleRepository scheduleRepository) { + this.categoryRepository = categoryRepository; + this.externalCategoryDetailRepository = externalCategoryDetailRepository; + this.memberRepository = memberRepository; + this.subscriptionRepository = subscriptionRepository; + this.scheduleRepository = scheduleRepository; + } + + @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, + CategoryType.valueOf(request.getCategoryType().toUpperCase())); + categoryRepository.save(newCategory); + return new CategoryResponse(newCategory); + } + + @Transactional + public CategoryResponse save(final Long memberId, final ExternalCategoryCreateRequest request) { + List categories = categoryRepository.findByMemberId(memberId); + validateDuplicateExternalCategory(request.getExternalId(), categories); + + CategoryResponse response = save(memberId, new CategoryCreateRequest(request.getName(), CategoryType.GOOGLE)); + Category category = getCategory(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(); + + 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(); + + return new CategoriesResponse(pageable.getPageNumber(), categories); + } + + public CategoryResponse findById(final Long id) { + return new CategoryResponse(getCategory(id)); + } + + public Category getCategory(final Long id) { + return categoryRepository.findById(id) + .orElseThrow(NoSuchCategoryException::new); + } + + @Transactional + public void update(final Long memberId, final Long categoryId, final CategoryUpdateRequest request) { + Category category = getCategory(categoryId); + validatePermission(memberId, categoryId); + + 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); + } + + private void validatePersonalCategory(final Long categoryId) { + Category category = getCategory(categoryId); + if (category.isPersonal()) { + throw new InvalidCategoryException("내 일정 카테고리는 삭제할 수 없습니다."); + } + } + + private void validateCategoryExisting(final Long categoryId) { + if (!categoryRepository.existsById(categoryId)) { + throw new NoSuchCategoryException("존재하지 않는 카테고리를 삭제할 수 없습니다."); + } + } + + private void validatePermission(final Long memberId, final Long categoryId) { + if (!categoryRepository.existsByIdAndMemberId(categoryId, memberId)) { + throw new NoPermissionException(); + } + } + + @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); + } + + public void validateCreatorBy(final Long memberId, final Category category) { + if (!category.isCreator(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 new file mode 100644 index 00000000..e4cc2867 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/Category.java @@ -0,0 +1,113 @@ +package com.allog.dallog.domain.category.domain; + +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.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; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Table(name = "categories") +@Entity +public class Category extends BaseEntity { + + public static final int MAX_NAME_LENGTH = 20; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "name", nullable = false) + private String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "members_id", nullable = false) + private Member member; + + @Enumerated(value = EnumType.STRING) + @Column(name = "category_type", nullable = false) + private CategoryType categoryType; + + protected Category() { + } + + public Category(final String name, final Member member) { + validateNameLength(name); + this.name = name; + this.member = member; + this.categoryType = CategoryType.NORMAL; + } + + public Category(final String name, final Member member, final CategoryType categoryType) { + validateNameLength(name); + this.name = name; + this.member = member; + this.categoryType = categoryType; + } + + public void changeName(final String name) { + validatePersonal(); + validateNameLength(name); + this.name = name; + } + + private void validatePersonal() { + if (isPersonal()) { + throw new InvalidCategoryException("내 일정은 수정할 수 없습니다."); + } + } + + private void validateNameLength(final String name) { + if (name.isBlank()) { + throw new InvalidCategoryException("카테고리 이름은 공백일 수 없습니다."); + } + if (name.length() > MAX_NAME_LENGTH) { + throw new InvalidCategoryException("카테고리 이름의 길이는 20을 초과할 수 없습니다."); + } + } + + public boolean isCreator(final Long memberId) { + return Objects.equals(member.getId(), memberId); + } + + public boolean isPersonal() { + return categoryType == PERSONAL; + } + + public boolean isInternal() { + return categoryType != GOOGLE; + } + + public boolean isExternal() { + return categoryType == GOOGLE; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Member getMember() { + return member; + } + + public CategoryType getCategoryType() { + return 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 new file mode 100644 index 00000000..203b2279 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryRepository.java @@ -0,0 +1,26 @@ +package com.allog.dallog.domain.category.domain; + +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +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); + + @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); + + List findByMemberId(Long memberId); + + boolean existsByIdAndMemberId(Long id, Long memberId); + + void deleteByMemberId(Long memberId); +} 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 new file mode 100644 index 00000000..87060ada --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/CategoryType.java @@ -0,0 +1,16 @@ +package com.allog.dallog.domain.category.domain; + +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; + +public enum CategoryType { + + NORMAL, PERSONAL, GOOGLE; + + public static CategoryType from(final String value) { + try { + return CategoryType.valueOf(value.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new NoSuchCategoryException("(" + value + ")는 존재하지 않는 카테고리 타입입니다."); + } + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetail.java b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetail.java new file mode 100644 index 00000000..7c93616d --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetail.java @@ -0,0 +1,49 @@ +package com.allog.dallog.domain.category.domain; + +import com.allog.dallog.domain.common.BaseEntity; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.OneToOne; +import javax.persistence.Table; + +@Table(name = "external_category_details") +@Entity +public class ExternalCategoryDetail extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "categories_id", nullable = false) + private Category category; + + @Column(name = "external_id", nullable = false) + private String externalId; + + protected ExternalCategoryDetail() { + } + + public ExternalCategoryDetail(final Category category, final String externalId) { + this.category = category; + this.externalId = externalId; + } + + public Long getId() { + return id; + } + + public Category getCategory() { + return category; + } + + public String getExternalId() { + return externalId; + } +} 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 new file mode 100644 index 00000000..767a66ff --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/domain/ExternalCategoryDetailRepository.java @@ -0,0 +1,14 @@ +package com.allog.dallog.domain.category.domain; + +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); + + boolean existsByExternalIdAndCategoryIn(final String externalId, final List categories); + + void deleteByCategoryId(final Long categoryId); +} 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 new file mode 100644 index 00000000..d6bd4467 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryCreateRequest.java @@ -0,0 +1,29 @@ +package com.allog.dallog.domain.category.dto.request; + +import com.allog.dallog.domain.category.domain.CategoryType; +import javax.validation.constraints.NotBlank; + +public class CategoryCreateRequest { + + @NotBlank(message = "공백일 수 없습니다.") + private String name; + + @NotBlank(message = "공백일 수 없습니다.") + private String categoryType; + + private CategoryCreateRequest() { + } + + public CategoryCreateRequest(final String name, final CategoryType categoryType) { + this.name = name; + this.categoryType = categoryType.name(); + } + + public String getName() { + return name; + } + + public String getCategoryType() { + return categoryType; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryUpdateRequest.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryUpdateRequest.java new file mode 100644 index 00000000..9baafa36 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/CategoryUpdateRequest.java @@ -0,0 +1,20 @@ +package com.allog.dallog.domain.category.dto.request; + +import javax.validation.constraints.NotBlank; + +public class CategoryUpdateRequest { + + @NotBlank(message = "공백일 수 없습니다.") + private String name; + + private CategoryUpdateRequest() { + } + + public CategoryUpdateRequest(final String name) { + this.name = name; + } + + public String getName() { + return name; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/request/ExternalCategoryCreateRequest.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/ExternalCategoryCreateRequest.java new file mode 100644 index 00000000..0b4bc775 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/request/ExternalCategoryCreateRequest.java @@ -0,0 +1,28 @@ +package com.allog.dallog.domain.category.dto.request; + +import javax.validation.constraints.NotBlank; + +public class ExternalCategoryCreateRequest { + + @NotBlank(message = "공백일 수 없습니다.") + private String externalId; + + @NotBlank(message = "공백일 수 없습니다.") + private String name; + + private ExternalCategoryCreateRequest() { + } + + public ExternalCategoryCreateRequest(final String externalId, final String name) { + this.externalId = externalId; + this.name = name; + } + + public String getExternalId() { + return externalId; + } + + public String getName() { + return name; + } +} 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 new file mode 100644 index 00000000..71e99893 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoriesResponse.java @@ -0,0 +1,33 @@ +package com.allog.dallog.domain.category.dto.response; + +import com.allog.dallog.domain.category.domain.Category; +import java.util.List; +import java.util.stream.Collectors; + +public class CategoriesResponse { + + private int page; + private List categories; + + public CategoriesResponse() { + } + + public CategoriesResponse(final int page, final List categories) { + this.page = page; + this.categories = convertToResponses(categories); + } + + private List convertToResponses(final List categories) { + return categories.stream() + .map(CategoryResponse::new) + .collect(Collectors.toList()); + } + + public int getPage() { + return page; + } + + public List getCategories() { + return categories; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoryResponse.java b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoryResponse.java new file mode 100644 index 00000000..6ef31a04 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/dto/response/CategoryResponse.java @@ -0,0 +1,51 @@ +package com.allog.dallog.domain.category.dto.response; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.member.dto.MemberResponse; +import java.time.LocalDateTime; + +public class CategoryResponse { + + private Long id; + private String name; + private String categoryType; + private MemberResponse creator; + private LocalDateTime createdAt; + + private CategoryResponse() { + } + + public CategoryResponse(final Category category) { + this(category.getId(), category.getName(), category.getCategoryType().name(), + new MemberResponse(category.getMember()), category.getCreatedAt()); + } + + public CategoryResponse(final Long id, final String name, final String categoryType, final MemberResponse creator, + final LocalDateTime createdAt) { + this.id = id; + this.name = name; + this.categoryType = categoryType; + this.creator = creator; + this.createdAt = createdAt; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getCategoryType() { + return categoryType; + } + + public MemberResponse getCreator() { + return creator; + } + + public LocalDateTime getCreatedAt() { + return createdAt; + } +} 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 new file mode 100644 index 00000000..9bfe6a77 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/exception/DuplicatedExternalCategoryException.java @@ -0,0 +1,12 @@ +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/InvalidCategoryException.java b/backend/src/main/java/com/allog/dallog/domain/category/exception/InvalidCategoryException.java new file mode 100644 index 00000000..d133cd9f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/exception/InvalidCategoryException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.category.exception; + +public class InvalidCategoryException extends RuntimeException { + + public InvalidCategoryException(final String message) { + super(message); + } + + public InvalidCategoryException() { + this("잘못된 카테고리입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchCategoryException.java b/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchCategoryException.java new file mode 100644 index 00000000..0f08a63f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchCategoryException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.category.exception; + +public class NoSuchCategoryException extends RuntimeException { + + public NoSuchCategoryException(final String message) { + super(message); + } + + public NoSuchCategoryException() { + this("존재하지 않는 카테고리입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchExternalCategoryDetailException.java b/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchExternalCategoryDetailException.java new file mode 100644 index 00000000..4a9ed5b5 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/category/exception/NoSuchExternalCategoryDetailException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.category.exception; + +public class NoSuchExternalCategoryDetailException extends RuntimeException { + + public NoSuchExternalCategoryDetailException(final String message) { + super(message); + } + + public NoSuchExternalCategoryDetailException() { + this("존재하지 않는 외부 카테고리 정보 입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/common/BaseEntity.java b/backend/src/main/java/com/allog/dallog/domain/common/BaseEntity.java new file mode 100644 index 00000000..1df01b5a --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/common/BaseEntity.java @@ -0,0 +1,30 @@ +package com.allog.dallog.domain.common; + +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public abstract class BaseEntity { + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + public LocalDateTime getCreatedAt() { + return createdAt; + } + + public LocalDateTime getUpdatedAt() { + return updatedAt; + } +} 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 new file mode 100644 index 00000000..7da8dc06 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CalendarService.java @@ -0,0 +1,140 @@ +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.Subscription; +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 { + + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; + + private final SubscriptionService subscriptionService; + private final IntegrationScheduleDao integrationScheduleDao; + private final ExternalCategoryDetailRepository externalCategoryDetailRepository; + private final OAuthTokenRepository oAuthTokenRepository; + private final OAuthClient oAuthClient; + private final ExternalCalendarClient externalCalendarClient; + + public CalendarService(final SubscriptionService subscriptionService, + final IntegrationScheduleDao integrationScheduleDao, + final ExternalCategoryDetailRepository externalCategoryDetailRepository, + final OAuthTokenRepository oAuthTokenRepository, final OAuthClient oAuthClient, + final ExternalCalendarClient externalCalendarClient) { + this.subscriptionService = subscriptionService; + this.integrationScheduleDao = integrationScheduleDao; + this.externalCategoryDetailRepository = externalCategoryDetailRepository; + this.oAuthTokenRepository = oAuthTokenRepository; + this.oAuthClient = oAuthClient; + this.externalCalendarClient = externalCalendarClient; + } + + public List getSchedulesOfSubscriberIds(final List subscriberIds, + final DateRangeRequest dateRange) { + return subscriberIds.stream() + .flatMap(subscriberId -> getIntegrationSchedulesByMemberIdAndSubscriptions(subscriberId, + dateRange).stream()) + .distinct() + .collect(Collectors.toList()); + } + + public MemberScheduleResponses findSchedulesByMemberId(final Long memberId, + final DateRangeRequest dateRange) { + List subscriptions = subscriptionService.getAllByMemberId(memberId); + List integrationSchedules = getIntegrationSchedulesByMemberIdAndSubscriptions(memberId, + dateRange); + return new MemberScheduleResponses(subscriptions, new TypedSchedules(integrationSchedules)); + } + + private List getIntegrationSchedulesByMemberIdAndSubscriptions(final Long memberId, + final DateRangeRequest dateRange) { + List subscriptions = subscriptionService.getAllByMemberId(memberId); + List internalCategoryIds = findCheckedInternalCategories(subscriptions); + + List integrationSchedules = new ArrayList<>( + integrationScheduleDao.findByCategoryIdInAndBetween( + internalCategoryIds, dateRange.getStartDateTime(), dateRange.getEndDateTime())); + + List externalCategoryDetails = findCheckedExternalCategoryDetails(subscriptions); + if (!externalCategoryDetails.isEmpty()) { + integrationSchedules.addAll(getExternalSchedules(memberId, dateRange, externalCategoryDetails)); + } + + 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) + .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()) + .orElseThrow(NoSuchExternalCategoryDetailException::new); + } + + private List getExternalSchedules(final Long memberId, final DateRangeRequest request, + final List externalCategories) { + String refreshToken = getOAuthToken(memberId).getRefreshToken(); + String accessToken = oAuthClient.getAccessToken(refreshToken).getAccessToken(); + + return externalCategories.stream() + .flatMap(externalCategory -> flatIntegrationSchedules(request, accessToken, externalCategory)) + .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) { + String startDateTime = dateRangeRequest.getStartDateTime().format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + String endDateTime = dateRangeRequest.getEndDateTime().format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + + return externalCalendarClient.getExternalCalendarSchedules(accessToken, externalCategory.getCategory().getId(), + externalCategory.getExternalId(), startDateTime, endDateTime).stream(); + } +} 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 new file mode 100644 index 00000000..62199bdd --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/CategorySubscriptionService.java @@ -0,0 +1,37 @@ +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/domain/composition/application/RegisterService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java new file mode 100644 index 00000000..74b83626 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/RegisterService.java @@ -0,0 +1,58 @@ +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/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java new file mode 100644 index 00000000..7352f517 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/composition/application/SchedulerService.java @@ -0,0 +1,54 @@ +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.scheduler.Scheduler; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; +import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; +import java.util.List; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class SchedulerService { + + private final CalendarService calendarService; + private final SubscriptionService subscriptionService; + private final MemberService memberService; + + public SchedulerService(final CalendarService calendarService, final SubscriptionService subscriptionService, + final MemberService memberService) { + this.calendarService = calendarService; + this.subscriptionService = subscriptionService; + this.memberService = memberService; + } + + public List getAvailablePeriods(final Long categoryId, final DateRangeRequest dateRange) { + List subscriberIds = getSubscriberIds(categoryId); + List schedules = calendarService.getSchedulesOfSubscriberIds(subscriberIds, dateRange); + + Scheduler scheduler = new Scheduler(schedules, dateRange.getStartDateTime(), dateRange.getEndDateTime()); + + return convertPeriodsToPeriodResponses(scheduler.getPeriods()); + } + + private List getSubscriberIds(final Long categoryId) { + List subscriptions = subscriptionService.findByCategoryId(categoryId); + return subscriptions.stream() + .map(subscriptionResponse -> memberService.findBySubscriptionId(subscriptionResponse.getId())) + .map(MemberResponse::getId) + .collect(Collectors.toList()); + } + + private List convertPeriodsToPeriodResponses(final List periods) { + return periods.stream() + .map(PeriodResponse::new) + .collect(Collectors.toList()); + } +} 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 new file mode 100644 index 00000000..d550a16e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarClient.java @@ -0,0 +1,15 @@ +package com.allog.dallog.domain.externalcalendar.application; + +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import java.util.List; + +public interface ExternalCalendarClient { + + List getExternalCalendars(final String accessToken); + + List getExternalCalendarSchedules(final String accessToken, + final Long internalCategoryId, + final String externalCalendarId, + final String startDateTime, final String endDateTime); +} 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 new file mode 100644 index 00000000..1dfca38f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarService.java @@ -0,0 +1,40 @@ +package com.allog.dallog.domain.externalcalendar.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.auth.exception.NoSuchOAuthTokenException; +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; +import java.util.List; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class ExternalCalendarService { + + private final OAuthClient oAuthClient; + private final ExternalCalendarClient externalCalendarClient; + private final OAuthTokenRepository oAuthTokenRepository; + + public ExternalCalendarService(final OAuthClient oAuthClient, final ExternalCalendarClient externalCalendarClient, + final OAuthTokenRepository oAuthTokenRepository) { + this.oAuthClient = oAuthClient; + this.externalCalendarClient = externalCalendarClient; + this.oAuthTokenRepository = oAuthTokenRepository; + } + + public ExternalCalendarsResponse findByMemberId(final Long memberId) { + OAuthToken oAuthToken = oAuthTokenRepository.findByMemberId(memberId) + .orElseThrow(NoSuchOAuthTokenException::new); + + OAuthAccessTokenResponse oAuthAccessTokenResponse = oAuthClient.getAccessToken(oAuthToken.getRefreshToken()); + String oAuthAccessToken = oAuthAccessTokenResponse.getAccessToken(); + + List externalCalendars = externalCalendarClient.getExternalCalendars(oAuthAccessToken); + + return new ExternalCalendarsResponse(externalCalendars); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendar.java b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendar.java new file mode 100644 index 00000000..48a76d1e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendar.java @@ -0,0 +1,23 @@ +package com.allog.dallog.domain.externalcalendar.dto; + +public class ExternalCalendar { + + private String calendarId; + private String summary; + + private ExternalCalendar() { + } + + public ExternalCalendar(final String calendarId, final String summary) { + this.calendarId = calendarId; + this.summary = summary; + } + + public String getCalendarId() { + return calendarId; + } + + public String getSummary() { + return summary; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendarsResponse.java b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendarsResponse.java new file mode 100644 index 00000000..6f45a9de --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/externalcalendar/dto/ExternalCalendarsResponse.java @@ -0,0 +1,19 @@ +package com.allog.dallog.domain.externalcalendar.dto; + +import java.util.List; + +public class ExternalCalendarsResponse { + + private List externalCalendars; + + private ExternalCalendarsResponse() { + } + + public ExternalCalendarsResponse(final List externalCalendars) { + this.externalCalendars = externalCalendars; + } + + public List getExternalCalendars() { + return externalCalendars; + } +} 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 new file mode 100644 index 00000000..38805d4a --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDao.java @@ -0,0 +1,61 @@ +package com.allog.dallog.domain.integrationschedule.dao; + +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.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 Collections.emptyList(); + } + + 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"); + String categoryType = 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/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java new file mode 100644 index 00000000..190c84b8 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedule.java @@ -0,0 +1,120 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import com.allog.dallog.domain.category.domain.Category; +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 { + + 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; + private final String title; + private final Period period; + private final String memo; + private final String categoryType; + + public IntegrationSchedule(final String id, final Long categoryId, final String title, + final LocalDateTime startDateTime, final LocalDateTime endDateTime, final String memo, + final String 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) { + this.id = id; + this.categoryId = categoryId; + this.title = title; + this.period = period; + this.memo = memo; + this.categoryType = categoryType; + } + + public boolean isLongTerms() { + return period.calculateDayDifference() >= ONE_DAY; + } + + public boolean isAllDays() { + return period.calculateDayDifference() < ONE_DAY + && period.calculateHourDifference() == MIDNIGHT_HOUR + && period.calculateMinuteDifference() == MIDNIGHT_MINUTE; + } + + public boolean isFewHours() { + return !isAllDays() + && 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) { + Category category = subscription.getCategory(); + return category.getId().equals(categoryId); + } + + public String getId() { + return id; + } + + public Long getCategoryId() { + return categoryId; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return period.getStartDateTime(); + } + + public LocalDateTime getEndDateTime() { + return period.getEndDateTime(); + } + + public Period getPeriod() { + return period; + } + + public String getMemo() { + return memo; + } + + public String getCategoryType() { + return categoryType; + } + + // 중복 일정을 판별하기 위한 equals & hashCode + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + IntegrationSchedule that = (IntegrationSchedule) o; + return Objects.equals(id, that.id) && Objects.equals(categoryId, that.categoryId) + && Objects.equals(title, that.title) && Objects.equals(period, that.period) + && Objects.equals(memo, that.memo) && Objects.equals(categoryType, that.categoryType); + } + + @Override + public int hashCode() { + return Objects.hash(id, categoryId, title, period, memo, categoryType); + } +} 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 new file mode 100644 index 00000000..70d33096 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleComparator.java @@ -0,0 +1,45 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import java.time.LocalDateTime; +import java.util.Comparator; + +public class IntegrationScheduleComparator implements Comparator { + + private static final int BEFORE = -1; + private static final int AFTER = 1; + private static final int SAME = 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)) { + return compareEndDateTime(firstSchedule, secondSchedule); + } + return SAME; + } + + 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; + } + return compareByTitle(firstSchedule, secondSchedule); + } + + private int compareByTitle(IntegrationSchedule firstSchedule, IntegrationSchedule secondSchedule) { + return firstSchedule.getTitle().compareTo(secondSchedule.getTitle()); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java new file mode 100644 index 00000000..b6d42044 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedules.java @@ -0,0 +1,24 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import java.util.ArrayList; +import java.util.List; + +public class IntegrationSchedules { + + private static final IntegrationScheduleComparator COMPARATOR = new IntegrationScheduleComparator(); + + private final List values; + + public IntegrationSchedules() { + this.values = new ArrayList<>(); + } + + public void add(final IntegrationSchedule integrationSchedule) { + values.add(integrationSchedule); + } + + public List getSortedValues() { + values.sort(COMPARATOR); + return List.copyOf(values); + } +} 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 new file mode 100644 index 00000000..769ae36c --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/Period.java @@ -0,0 +1,96 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +public class Period { + + private static final int ONE_HOUR = 60; + + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + + public Period(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + } + + public long calculateDayDifference() { + LocalDate startDate = LocalDate.from(startDateTime); + LocalDate endDate = LocalDate.from(endDateTime); + 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() { + LocalTime startTime = LocalTime.from(startDateTime); + LocalTime endTime = LocalTime.from(endDateTime); + return ChronoUnit.MINUTES.between(startTime, endTime) % ONE_HOUR; + } + + public List slice(final Period otherPeriod) { + if (isNotOverlapped(otherPeriod)) { + return List.of(this); + } + + return sliceByOtherPeriod(otherPeriod); + } + + private List sliceByOtherPeriod(final Period otherPeriod) { + List periods = new ArrayList<>(); + if (startDateTime.isBefore(otherPeriod.startDateTime)) { + periods.add(new Period(startDateTime, otherPeriod.startDateTime)); + } + + if (otherPeriod.endDateTime.isBefore(endDateTime)) { + periods.add(new Period(otherPeriod.endDateTime, endDateTime)); + } + + 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; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Period period = (Period) o; + return Objects.equals(startDateTime, period.startDateTime) && Objects.equals(endDateTime, period.endDateTime); + } + + @Override + public int hashCode() { + return Objects.hash(startDateTime, endDateTime); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java new file mode 100644 index 00000000..1605103c --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/ScheduleType.java @@ -0,0 +1,30 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import java.util.Arrays; +import java.util.function.Predicate; + +public enum ScheduleType { + + LONG_TERMS("longTerms", IntegrationSchedule::isLongTerms), + ALL_DAYS("allDays", IntegrationSchedule::isAllDays), + FEW_HOURS("fewHours", IntegrationSchedule::isFewHours); + + private final String name; + private final Predicate isMatch; + + ScheduleType(final String name, final Predicate isMatch) { + this.name = name; + this.isMatch = isMatch; + } + + public static ScheduleType from(final IntegrationSchedule integrationSchedule) { + return Arrays.stream(values()) + .filter(type -> type.isMatch.test(integrationSchedule)) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("일치하는 일정 종류가 존재하지 않습니다.")); + } + + public String getName() { + return name; + } +} 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 new file mode 100644 index 00000000..de5ad6c4 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/integrationschedule/domain/TypedSchedules.java @@ -0,0 +1,30 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TypedSchedules { + + private Map values; + + public TypedSchedules(final List integrationSchedules) { + initializeValues(); + for (IntegrationSchedule integrationSchedule : integrationSchedules) { + ScheduleType scheduleType = ScheduleType.from(integrationSchedule); + IntegrationSchedules sortedSchedules = values.get(scheduleType); + sortedSchedules.add(integrationSchedule); + } + } + + private void initializeValues() { + this.values = new HashMap<>(); + for (ScheduleType type : ScheduleType.values()) { + values.put(type, new IntegrationSchedules()); + } + } + + public IntegrationSchedules getSortedSchedules(final ScheduleType scheduleType) { + return values.get(scheduleType); + } +} 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 new file mode 100644 index 00000000..53cd1307 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/application/MemberService.java @@ -0,0 +1,79 @@ +package com.allog.dallog.domain.member.application; + +import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +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 com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@Service +public class MemberService { + + private final MemberRepository memberRepository; + private final SubscriptionRepository subscriptionRepository; + private final OAuthTokenRepository oAuthTokenRepository; + + public MemberService(final MemberRepository memberRepository, final SubscriptionRepository subscriptionRepository, + final OAuthTokenRepository oAuthTokenRepository) { + this.memberRepository = memberRepository; + this.subscriptionRepository = subscriptionRepository; + 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(getMember(id)); + } + + public MemberResponse findBySubscriptionId(final Long subscriptionId) { + Subscription subscription = subscriptionRepository.findById(subscriptionId) + .orElseThrow(NoSuchSubscriptionException::new); + + Member member = subscription.getMember(); + return new MemberResponse(member); + } + + @Transactional + public void update(final Long id, final MemberUpdateRequest request) { + Member member = getMember(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); + } + + public Member getByEmail(final String email) { + return memberRepository.findByEmail(email) + .orElseThrow(NoSuchMemberException::new); + } + + public boolean existsByEmail(final String email) { + return memberRepository.existsByEmail(email); + } + + public void validateExistsMember(final Long id) { + if (!memberRepository.existsById(id)) { + throw new NoSuchMemberException("존재하지 않는 회원입니다."); + } + } +} 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 new file mode 100644 index 00000000..17bf3518 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/Member.java @@ -0,0 +1,92 @@ +package com.allog.dallog.domain.member.domain; + +import com.allog.dallog.domain.common.BaseEntity; +import com.allog.dallog.domain.member.exception.InvalidMemberException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Table(name = "members") +@Entity +public class Member extends BaseEntity { + + private static final Pattern EMAIL_PATTERN = Pattern.compile("^[a-z0-9._-]+@[a-z]+[.]+[a-z]{2,3}$"); + private static final int MAX_DISPLAY_NAME_LENGTH = 10; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "email", nullable = false) + private String email; + + @Column(name = "display_name", nullable = false) + private String displayName; + + @Column(name = "profile_image_url", nullable = false) + private String profileImageUrl; + + @Enumerated(value = EnumType.STRING) + @Column(name = "social_type", nullable = false) + private SocialType socialType; + + protected Member() { + } + + public Member(final String email, final String displayName, final String profileImageUrl, + final SocialType socialType) { + validateEmail(email); + validateDisplayName(displayName); + + this.email = email; + this.displayName = displayName; + this.profileImageUrl = profileImageUrl; + this.socialType = socialType; + } + + private void validateEmail(final String email) { + Matcher matcher = EMAIL_PATTERN.matcher(email); + if (!matcher.matches()) { + throw new InvalidMemberException("이메일 형식이 올바르지 않습니다."); + } + } + + private void validateDisplayName(final String displayName) { + if (displayName.isEmpty() || displayName.length() > MAX_DISPLAY_NAME_LENGTH) { + throw new InvalidMemberException("이름 형식이 올바르지 않습니다."); + } + } + + public void change(final String displayName) { + validateDisplayName(displayName); + this.displayName = displayName; + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getDisplayName() { + return displayName; + } + + public String getProfileImageUrl() { + return profileImageUrl; + } + + public SocialType getSocialType() { + return socialType; + } +} 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 new file mode 100644 index 00000000..602d671b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/MemberRepository.java @@ -0,0 +1,11 @@ +package com.allog.dallog.domain.member.domain; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MemberRepository extends JpaRepository { + + Optional findByEmail(final String email); + + boolean existsByEmail(final String email); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/member/domain/SocialType.java b/backend/src/main/java/com/allog/dallog/domain/member/domain/SocialType.java new file mode 100644 index 00000000..4dcb0249 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/domain/SocialType.java @@ -0,0 +1,6 @@ +package com.allog.dallog.domain.member.domain; + +public enum SocialType { + + GOOGLE, GITHUB; +} diff --git a/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberResponse.java b/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberResponse.java new file mode 100644 index 00000000..84038b9e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberResponse.java @@ -0,0 +1,50 @@ +package com.allog.dallog.domain.member.dto; + +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.SocialType; + +public class MemberResponse { + + private Long id; + private String email; + private String displayName; + private String profileImageUrl; + private SocialType socialType; + + private MemberResponse() { + } + + public MemberResponse(final Long id, final String email, final String displayName, final String profileImageUrl, + final SocialType socialType) { + this.id = id; + this.email = email; + this.displayName = displayName; + this.profileImageUrl = profileImageUrl; + this.socialType = socialType; + } + + public MemberResponse(final Member member) { + this(member.getId(), member.getEmail(), member.getDisplayName(), member.getProfileImageUrl(), + member.getSocialType()); + } + + public Long getId() { + return id; + } + + public String getEmail() { + return email; + } + + public String getDisplayName() { + return displayName; + } + + public String getProfileImageUrl() { + return profileImageUrl; + } + + public SocialType getSocialType() { + return socialType; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberUpdateRequest.java b/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberUpdateRequest.java new file mode 100644 index 00000000..e3e9ad48 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/dto/MemberUpdateRequest.java @@ -0,0 +1,20 @@ +package com.allog.dallog.domain.member.dto; + +import javax.validation.constraints.NotBlank; + +public class MemberUpdateRequest { + + @NotBlank(message = "공백일 수 없습니다.") + private String displayName; + + private MemberUpdateRequest() { + } + + public MemberUpdateRequest(final String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/member/exception/InvalidMemberException.java b/backend/src/main/java/com/allog/dallog/domain/member/exception/InvalidMemberException.java new file mode 100644 index 00000000..e7dc859c --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/exception/InvalidMemberException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.member.exception; + +public class InvalidMemberException extends RuntimeException { + + public InvalidMemberException(final String message) { + super(message); + } + + public InvalidMemberException() { + this("잘못된 회원의 정보입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/member/exception/NoSuchMemberException.java b/backend/src/main/java/com/allog/dallog/domain/member/exception/NoSuchMemberException.java new file mode 100644 index 00000000..11193954 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/member/exception/NoSuchMemberException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.member.exception; + +public class NoSuchMemberException extends RuntimeException { + + public NoSuchMemberException(final String message) { + super(message); + } + + public NoSuchMemberException() { + this("존재하지 않는 회원입니다."); + } +} 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 new file mode 100644 index 00000000..e4771bc5 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/application/ScheduleService.java @@ -0,0 +1,68 @@ +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.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; + +@Transactional(readOnly = true) +@Service +public class ScheduleService { + + private final ScheduleRepository scheduleRepository; + private final CategoryService categoryService; + + public ScheduleService(final ScheduleRepository scheduleRepository, final CategoryService categoryService) { + this.scheduleRepository = scheduleRepository; + this.categoryService = categoryService; + } + + @Transactional + public ScheduleResponse save(final Long memberId, final Long categoryId, final ScheduleCreateRequest request) { + Category category = categoryService.getCategory(categoryId); + categoryService.validateCreatorBy(memberId, category); + validateCategoryType(category); + + 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); + + 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.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()); + 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 new file mode 100644 index 00000000..078552b1 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/Schedule.java @@ -0,0 +1,112 @@ +package com.allog.dallog.domain.schedule.domain; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.common.BaseEntity; +import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; +import java.time.LocalDateTime; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Table(name = "schedules") +@Entity +public class Schedule extends BaseEntity { + + private static final int MAX_TITLE_LENGTH = 20; + private static final int MAX_MEMO_LENGTH = 255; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "categories_id", nullable = false) + private Category category; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "start_date_time", nullable = false) + private LocalDateTime startDateTime; + + @Column(name = "end_date_time", nullable = false) + private LocalDateTime endDateTime; + + @Column(name = "memo", nullable = false) + private String memo; + + protected Schedule() { + } + + public Schedule(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; + this.memo = memo; + } + + public void change(final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, + final String memo) { + validateTitleLength(title); + validatePeriod(startDateTime, endDateTime); + validateMemoLength(memo); + this.title = title; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.memo = memo; + } + + private void validateTitleLength(final String title) { + if (title.length() > MAX_TITLE_LENGTH) { + throw new InvalidScheduleException("일정 제목의 길이는 20을 초과할 수 없습니다."); + } + } + + private void validatePeriod(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + if (startDateTime.isAfter(endDateTime)) { + throw new InvalidScheduleException("종료일시가 시작일시보다 이전일 수 없습니다."); + } + } + + private void validateMemoLength(final String memo) { + if (memo.length() > MAX_MEMO_LENGTH) { + throw new InvalidScheduleException("일정 메모의 길이는 255를 초과할 수 없습니다."); + } + } + + public Long getId() { + return id; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public String getMemo() { + return memo; + } + + public Category getCategory() { + return category; + } +} 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 new file mode 100644 index 00000000..a45e93bf --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/ScheduleRepository.java @@ -0,0 +1,9 @@ +package com.allog.dallog.domain.schedule.domain; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScheduleRepository extends JpaRepository { + + void deleteByCategoryIdIn(final List categoryIds); +} 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 new file mode 100644 index 00000000..4b39a6e8 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/domain/scheduler/Scheduler.java @@ -0,0 +1,40 @@ +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 java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class Scheduler { + + private final List schedules; + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + + public Scheduler(final List schedules, final LocalDateTime startDateTime, + final LocalDateTime endDate) { + this.schedules = schedules; + this.startDateTime = startDateTime; + this.endDateTime = endDate; + } + + public List getPeriods() { + List periods = new ArrayList<>(); + Period initialBasePeriod = new Period(startDateTime, endDateTime); + periods.add(initialBasePeriod); + + for (IntegrationSchedule schedule : schedules) { + slicePeriod(periods, schedule); + } + + return periods; + } + + private void slicePeriod(final List periods, final IntegrationSchedule schedule) { + for (Period period : List.copyOf(periods)) { + periods.remove(period); + periods.addAll(period.slice(schedule.getPeriod())); + } + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/DateRangeRequest.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/DateRangeRequest.java new file mode 100644 index 00000000..e3f56afa --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/DateRangeRequest.java @@ -0,0 +1,33 @@ +package com.allog.dallog.domain.schedule.dto.request; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +public class DateRangeRequest { + + private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm"; + + private LocalDateTime startDateTime; + private LocalDateTime endDateTime; + + public DateRangeRequest(final String startDateTime, final String endDateTime) { + this.startDateTime = LocalDateTime.parse(startDateTime, DateTimeFormatter.ofPattern(DATE_FORMAT)); + this.endDateTime = LocalDateTime.parse(endDateTime, DateTimeFormatter.ofPattern(DATE_FORMAT)); + } + + // TODO: 리팩토링 + public static DateRangeRequest of(final LocalDateTime startDateTime, final LocalDateTime endDateTime) { + String startDateTimeFormat = startDateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + String endDateTimeFormat = endDateTime.format(DateTimeFormatter.ofPattern(DATE_FORMAT)); + + return new DateRangeRequest(startDateTimeFormat, endDateTimeFormat); + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleCreateRequest.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleCreateRequest.java new file mode 100644 index 00000000..239434e4 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleCreateRequest.java @@ -0,0 +1,53 @@ +package com.allog.dallog.domain.schedule.dto.request; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.schedule.domain.Schedule; +import java.time.LocalDateTime; +import javax.validation.constraints.NotNull; +import org.springframework.format.annotation.DateTimeFormat; + +public class ScheduleCreateRequest { + + @NotNull(message = "Null일 수 없습니다.") + private String title; + + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime startDateTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime endDateTime; + + @NotNull(message = "Null일 수 없습니다.") + private String memo; + + private ScheduleCreateRequest() { + } + + public ScheduleCreateRequest(final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, + final String memo) { + this.title = title; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.memo = memo; + } + + public Schedule toEntity(final Category category) { + return new Schedule(category, title, startDateTime, endDateTime, memo); + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public String getMemo() { + return memo; + } +} 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 new file mode 100644 index 00000000..3744671b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/request/ScheduleUpdateRequest.java @@ -0,0 +1,48 @@ +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 String title; + + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime startDateTime; + + @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm") + private LocalDateTime endDateTime; + + @NotNull(message = "Null일 수 없습니다.") + private String memo; + + private ScheduleUpdateRequest() { + } + + public ScheduleUpdateRequest(final String title, final LocalDateTime startDateTime, final LocalDateTime endDateTime, + final String memo) { + this.title = title; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.memo = memo; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public String getMemo() { + return memo; + } +} 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 new file mode 100644 index 00000000..82299165 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponse.java @@ -0,0 +1,68 @@ +package com.allog.dallog.domain.schedule.dto.response; + +import com.allog.dallog.domain.integrationschedule.domain.IntegrationSchedule; +import com.allog.dallog.domain.subscription.domain.Color; +import java.time.LocalDateTime; + +public class MemberScheduleResponse { + + private final String id; + private final String title; + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + private final String memo; + private final Long categoryId; + private final String colorCode; + private final String categoryType; + + 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()); + } + + public MemberScheduleResponse(final String id, final String title, final LocalDateTime startDateTime, + final LocalDateTime endDateTime, final String memo, final Long categoryId, + final String colorCode, final String categoryType) { + this.id = id; + this.title = title; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.memo = memo; + this.categoryId = categoryId; + this.colorCode = colorCode; + this.categoryType = categoryType; + } + + public String getId() { + return id; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public String getMemo() { + return memo; + } + + public Long getCategoryId() { + return categoryId; + } + + public String getColorCode() { + return colorCode; + } + + public String getCategoryType() { + return categoryType; + } +} 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 new file mode 100644 index 00000000..0f63ec4c --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/MemberScheduleResponses.java @@ -0,0 +1,51 @@ +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.subscription.domain.Subscription; +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; + + public MemberScheduleResponses(final List longTerms, + final List allDays, + final List fewHours) { + this.longTerms = longTerms; + this.allDays = allDays; + this.fewHours = fewHours; + } + + public MemberScheduleResponses(final List 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 TypedSchedules typedSchedules) { + return typedSchedules.getSortedSchedules(scheduleType) + .getSortedValues() + .stream() + .map(schedule -> new MemberScheduleResponse(schedule, schedule.findSubscriptionColor(subscriptions))) + .collect(Collectors.toList()); + } + + public List getLongTerms() { + return longTerms; + } + + public List getAllDays() { + return allDays; + } + + public List getFewHours() { + return fewHours; + } +} 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 new file mode 100644 index 00000000..3d79d5e0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/PeriodResponse.java @@ -0,0 +1,23 @@ +package com.allog.dallog.domain.schedule.dto.response; + +import com.allog.dallog.domain.integrationschedule.domain.Period; +import java.time.LocalDateTime; + +public class PeriodResponse { + + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + + public PeriodResponse(final Period period) { + this.startDateTime = period.getStartDateTime(); + this.endDateTime = period.getEndDateTime(); + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/ScheduleResponse.java b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/ScheduleResponse.java new file mode 100644 index 00000000..ad1003ae --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/dto/response/ScheduleResponse.java @@ -0,0 +1,59 @@ +package com.allog.dallog.domain.schedule.dto.response; + +import com.allog.dallog.domain.schedule.domain.Schedule; +import java.time.LocalDateTime; + +public class ScheduleResponse { + + private final Long id; + private final Long categoryId; + private final String title; + private final LocalDateTime startDateTime; + private final LocalDateTime endDateTime; + private final String memo; + private final String categoryType; + + public ScheduleResponse(final Schedule schedule) { + this(schedule.getId(), schedule.getCategory().getId(), schedule.getTitle(), schedule.getStartDateTime(), + schedule.getEndDateTime(), schedule.getMemo(), schedule.getCategory().getCategoryType().name()); + } + + public ScheduleResponse(final Long id, final Long categoryId, final String title, final LocalDateTime startDateTime, + final LocalDateTime endDateTime, final String memo, final String categoryType) { + this.id = id; + this.categoryId = categoryId; + this.title = title; + this.startDateTime = startDateTime; + this.endDateTime = endDateTime; + this.memo = memo; + this.categoryType = categoryType; + } + + public Long getId() { + return id; + } + + public Long getCategoryId() { + return categoryId; + } + + public String getTitle() { + return title; + } + + public LocalDateTime getStartDateTime() { + return startDateTime; + } + + public LocalDateTime getEndDateTime() { + return endDateTime; + } + + public String getMemo() { + return memo; + } + + public String getCategoryType() { + return categoryType; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/exception/InvalidScheduleException.java b/backend/src/main/java/com/allog/dallog/domain/schedule/exception/InvalidScheduleException.java new file mode 100644 index 00000000..ac5c6a39 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/exception/InvalidScheduleException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.schedule.exception; + +public class InvalidScheduleException extends RuntimeException { + + public InvalidScheduleException(final String message) { + super(message); + } + + public InvalidScheduleException() { + this("잘못된 일정입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/schedule/exception/NoSuchScheduleException.java b/backend/src/main/java/com/allog/dallog/domain/schedule/exception/NoSuchScheduleException.java new file mode 100644 index 00000000..b3ad53b4 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/schedule/exception/NoSuchScheduleException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.schedule.exception; + +public class NoSuchScheduleException extends RuntimeException { + + public NoSuchScheduleException(final String message) { + super(message); + } + + public NoSuchScheduleException() { + this("존재하지 않는 일정입니다."); + } +} 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 new file mode 100644 index 00000000..85b3f064 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/application/SubscriptionService.java @@ -0,0 +1,132 @@ +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.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.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; +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.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@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; + + public SubscriptionService(final SubscriptionRepository subscriptionRepository, + final MemberRepository memberRepository, + final CategoryRepository categoryRepository) { + this.subscriptionRepository = subscriptionRepository; + this.memberRepository = memberRepository; + this.categoryRepository = categoryRepository; + } + + @Transactional + public SubscriptionResponse save(final Long memberId, final Long categoryId) { + validateAlreadyExists(memberId, categoryId); + + Member member = memberRepository.findById(memberId) + .orElseThrow(NoSuchMemberException::new); + Category category = categoryRepository.findById(categoryId) + .orElseThrow(NoSuchCategoryException::new); + validatePermission(memberId, category); + + Color color = Color.pickAny(PICK_RANDOM_STRATEGY); + Subscription subscription = subscriptionRepository.save(new Subscription(member, category, color)); + 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("구독 권한이 없는 카테고리입니다."); + } + } + + 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 = getSubscription(id); + + return new SubscriptionResponse(subscription); + } + + public List findByCategoryId(final Long categoryId) { + return subscriptionRepository.findByCategoryId(categoryId) + .stream() + .map(SubscriptionResponse::new) + .collect(Collectors.toList()); + } + + public List getAllByMemberId(final Long memberId) { + return subscriptionRepository.findByMemberId(memberId); + } + + @Transactional + public void update(final Long id, final Long memberId, final SubscriptionUpdateRequest request) { + validateSubscriptionPermission(id, memberId); + + Subscription subscription = getSubscription(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); + + validateSubscriptionPermission(id, memberId); + validateCategoryCreator(subscription.getCategory(), 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/Color.java b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java new file mode 100644 index 00000000..7dabec95 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Color.java @@ -0,0 +1,53 @@ +package com.allog.dallog.domain.subscription.domain; + +import com.allog.dallog.domain.subscription.exception.InvalidSubscriptionException; +import java.util.Arrays; + +public enum Color { + + COLOR_1("#AD1457"), + COLOR_2("#D81B60"), + COLOR_3("#D50000"), + COLOR_4("#E67C73"), + COLOR_5("#F4511E"), + COLOR_6("#EF6C00"), + COLOR_7("#F09300"), + COLOR_8("#F6BF26"), + COLOR_9("#E4C441"), + COLOR_10("#C0CA33"), + COLOR_11("#7CB342"), + COLOR_12("#33B679"), + COLOR_13("#0B8043"), + COLOR_14("#009688"), + COLOR_15("#039BE5"), + COLOR_16("#4285F4"), + COLOR_17("#3F51B5"), + COLOR_18("#7986CB"), + COLOR_19("#B39DDB"), + COLOR_20("#9E69AF"), + COLOR_21("#8E24AA"), + COLOR_22("#795548"), + COLOR_23("#616161"), + COLOR_24("#A79B8E"); + + private final String colorCode; + + Color(final String colorCode) { + this.colorCode = colorCode; + } + + public static Color pickAny(ColorPickerStrategy strategy) { + return Color.values()[strategy.pickNumber()]; + } + + public static Color from(final String colorCode) { + return Arrays.stream(Color.values()) + .filter(color -> color.getColorCode().equals(colorCode.toUpperCase())) + .findFirst() + .orElseThrow(() -> new InvalidSubscriptionException("(" + colorCode + ")는 사용할 수 없는 색상입니다.")); + } + + public String getColorCode() { + return 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 new file mode 100644 index 00000000..3365655f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/ColorPickerStrategy.java @@ -0,0 +1,7 @@ +package com.allog.dallog.domain.subscription.domain; + +@FunctionalInterface +public interface ColorPickerStrategy { + + int pickNumber(); +} 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 new file mode 100644 index 00000000..6379880d --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/Subscription.java @@ -0,0 +1,76 @@ +package com.allog.dallog.domain.subscription.domain; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.common.BaseEntity; +import com.allog.dallog.domain.member.domain.Member; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Table(name = "subscriptions") +@Entity +public class Subscription extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "members_id", nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "categories_id", nullable = false) + private Category category; + + @Enumerated(value = EnumType.STRING) + @Column(name = "color", nullable = false) + private Color color; + + @Column(name = "checked", nullable = false) + private boolean checked; + + protected Subscription() { + } + + public Subscription(final Member member, final Category category, final Color color) { + this.member = member; + this.category = category; + this.color = color; + this.checked = true; + } + + public void change(final Color color, final boolean checked) { + this.color = color; + this.checked = checked; + } + + public Long getId() { + return id; + } + + public Member getMember() { + return member; + } + + public Category getCategory() { + return category; + } + + public Color getColor() { + return color; + } + + public boolean isChecked() { + return checked; + } +} 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 new file mode 100644 index 00000000..f6217765 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepository.java @@ -0,0 +1,17 @@ +package com.allog.dallog.domain.subscription.domain; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SubscriptionRepository extends JpaRepository { + + boolean existsByMemberIdAndCategoryId(final Long memberId, final Long categoryId); + + List findByMemberId(final Long memberId); + + List findByCategoryId(final Long categoryId); + + boolean existsByIdAndMemberId(final Long id, final Long memberId); + + void deleteByCategoryIdIn(final List categoryIds); +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/request/SubscriptionUpdateRequest.java b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/request/SubscriptionUpdateRequest.java new file mode 100644 index 00000000..c1e62cbb --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/request/SubscriptionUpdateRequest.java @@ -0,0 +1,37 @@ +package com.allog.dallog.domain.subscription.dto.request; + +import com.allog.dallog.domain.subscription.domain.Color; +import com.fasterxml.jackson.annotation.JsonIgnore; +import javax.validation.constraints.NotBlank; + +public class SubscriptionUpdateRequest { + + @NotBlank(message = "공백일 수 없습니다.") + private String colorCode; + private boolean checked; + + private SubscriptionUpdateRequest() { + } + + public SubscriptionUpdateRequest(final Color color, final boolean checked) { + this(color.getColorCode(), checked); + } + + public SubscriptionUpdateRequest(final String colorCode, final boolean checked) { + this.colorCode = colorCode; + this.checked = checked; + } + + public String getColorCode() { + return colorCode; + } + + @JsonIgnore + public Color getColor() { + return Color.from(colorCode); + } + + public boolean isChecked() { + return checked; + } +} 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 new file mode 100644 index 00000000..6a9fff15 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionResponse.java @@ -0,0 +1,46 @@ +package com.allog.dallog.domain.subscription.dto.response; + +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.subscription.domain.Color; +import com.allog.dallog.domain.subscription.domain.Subscription; + +public class SubscriptionResponse { + + private Long id; + private CategoryResponse category; + private String colorCode; + + private boolean checked; + + private SubscriptionResponse() { + } + + public SubscriptionResponse(final Subscription subscription) { + this(subscription.getId(), new CategoryResponse(subscription.getCategory()), subscription.getColor(), + subscription.isChecked()); + } + + public SubscriptionResponse(final Long id, final CategoryResponse category, final Color color, + final boolean checked) { + this.id = id; + this.category = category; + this.colorCode = color.getColorCode(); + this.checked = checked; + } + + public Long getId() { + return id; + } + + public CategoryResponse getCategory() { + return category; + } + + public String getColorCode() { + return colorCode; + } + + public boolean isChecked() { + return checked; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionsResponse.java b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionsResponse.java new file mode 100644 index 00000000..25487189 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/dto/response/SubscriptionsResponse.java @@ -0,0 +1,19 @@ +package com.allog.dallog.domain.subscription.dto.response; + +import java.util.List; + +public class SubscriptionsResponse { + + private List subscriptions; + + private SubscriptionsResponse() { + } + + public SubscriptionsResponse(final List subscriptions) { + this.subscriptions = subscriptions; + } + + public List getSubscriptions() { + return subscriptions; + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/exception/ExistSubscriptionException.java b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/ExistSubscriptionException.java new file mode 100644 index 00000000..260a893b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/ExistSubscriptionException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.subscription.exception; + +public class ExistSubscriptionException extends RuntimeException { + + public ExistSubscriptionException(final String message) { + super(message); + } + + public ExistSubscriptionException() { + this("이미 존재하는 구독 정보 입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/exception/InvalidSubscriptionException.java b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/InvalidSubscriptionException.java new file mode 100644 index 00000000..f6677b69 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/InvalidSubscriptionException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.subscription.exception; + +public class InvalidSubscriptionException extends RuntimeException { + + public InvalidSubscriptionException(final String message) { + super(message); + } + + public InvalidSubscriptionException() { + this("유효하지 않은 구독 정보입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/domain/subscription/exception/NoSuchSubscriptionException.java b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/NoSuchSubscriptionException.java new file mode 100644 index 00000000..054aaf18 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/domain/subscription/exception/NoSuchSubscriptionException.java @@ -0,0 +1,12 @@ +package com.allog.dallog.domain.subscription.exception; + +public class NoSuchSubscriptionException extends RuntimeException { + + public NoSuchSubscriptionException(final String message) { + super(message); + } + + public NoSuchSubscriptionException() { + this("존재하지 않는 구독 정보입니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/global/config/JpaConfig.java b/backend/src/main/java/com/allog/dallog/global/config/JpaConfig.java new file mode 100644 index 00000000..6397a069 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/config/JpaConfig.java @@ -0,0 +1,9 @@ +package com.allog.dallog.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { +} diff --git a/backend/src/main/java/com/allog/dallog/global/config/PropertiesConfig.java b/backend/src/main/java/com/allog/dallog/global/config/PropertiesConfig.java new file mode 100644 index 00000000..f2160bb6 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/config/PropertiesConfig.java @@ -0,0 +1,10 @@ +package com.allog.dallog.global.config; + +import com.allog.dallog.global.config.properties.GoogleProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(GoogleProperties.class) +public class PropertiesConfig { +} diff --git a/backend/src/main/java/com/allog/dallog/global/config/WebConfig.java b/backend/src/main/java/com/allog/dallog/global/config/WebConfig.java new file mode 100644 index 00000000..12bb5e20 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/config/WebConfig.java @@ -0,0 +1,37 @@ +package com.allog.dallog.global.config; + +import com.allog.dallog.presentation.auth.AuthenticationPrincipalArgumentResolver; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + private final List allowOriginUrlPatterns; + private final AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver; + + public WebConfig(@Value("${cors.allow-origin.urls}") final List allowOriginUrlPatterns, + final AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver) { + this.allowOriginUrlPatterns = allowOriginUrlPatterns; + this.authenticationPrincipalArgumentResolver = authenticationPrincipalArgumentResolver; + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + String[] patterns = allowOriginUrlPatterns.stream() + .toArray(String[]::new); + + registry.addMapping("/**") + .allowedMethods("*") + .allowedOriginPatterns(patterns); + } + + @Override + public void addArgumentResolvers(List argumentResolvers) { + argumentResolvers.add(authenticationPrincipalArgumentResolver); + } +} diff --git a/backend/src/main/java/com/allog/dallog/global/config/properties/GoogleProperties.java b/backend/src/main/java/com/allog/dallog/global/config/properties/GoogleProperties.java new file mode 100644 index 00000000..ac040d3d --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/config/properties/GoogleProperties.java @@ -0,0 +1,70 @@ +package com.allog.dallog.global.config.properties; + +import java.util.List; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@ConfigurationProperties("oauth.google") +@ConstructorBinding +public class GoogleProperties { + + private final String clientId; + private final String clientSecret; + private final String redirectUri; + private final String oAuthEndPoint; + private final String responseType; + private final List scopes; + private final String tokenUri; + private final String accessType; + private final String prompt; + + public GoogleProperties(final String clientId, final String clientSecret, final String redirectUri, + final String oAuthEndPoint, final String responseType, final List scopes, + final String tokenUri, final String accessType, final String prompt) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.redirectUri = redirectUri; + this.oAuthEndPoint = oAuthEndPoint; + this.responseType = responseType; + this.scopes = scopes; + this.tokenUri = tokenUri; + this.accessType = accessType; + this.prompt = prompt; + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } + + public String getRedirectUri() { + return redirectUri; + } + + public String getOAuthEndPoint() { + return oAuthEndPoint; + } + + public String getResponseType() { + return responseType; + } + + public List getScopes() { + return scopes; + } + + public String getTokenUri() { + return tokenUri; + } + + public String getAccessType() { + return accessType; + } + + public String getPrompt() { + return prompt; + } +} 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 new file mode 100644 index 00000000..d023ac4e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/error/ControllerAdvice.java @@ -0,0 +1,116 @@ +package com.allog.dallog.global.error; + +import com.allog.dallog.domain.auth.exception.EmptyAuthorizationHeaderException; +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.InvalidCategoryException; +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; +import com.allog.dallog.domain.member.exception.InvalidMemberException; +import com.allog.dallog.domain.member.exception.NoSuchMemberException; +import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; +import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; +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 com.allog.dallog.global.error.dto.ErrorReportRequest; +import com.allog.dallog.global.error.dto.ErrorResponse; +import com.allog.dallog.infrastructure.oauth.exception.OAuthException; +import javax.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; + +@RestControllerAdvice +public class ControllerAdvice { + + private static final Logger log = LoggerFactory.getLogger(ControllerAdvice.class); + private static final String INVALID_DTO_FIELD_ERROR_MESSAGE_FORMAT = "%s 필드는 %s (전달된 값: %s)"; + + @ExceptionHandler({ + InvalidCategoryException.class, + InvalidMemberException.class, + InvalidScheduleException.class, + InvalidSubscriptionException.class, + ExistSubscriptionException.class, + DuplicatedExternalCategoryException.class + }) + public ResponseEntity handleInvalidData(final RuntimeException e) { + ErrorResponse errorResponse = new ErrorResponse(e.getMessage()); + return ResponseEntity.badRequest().body(errorResponse); + } + + @ExceptionHandler({ + EmptyAuthorizationHeaderException.class, + InvalidTokenException.class + }) + public ResponseEntity handleInvalidAuthorization(final RuntimeException e) { + ErrorResponse errorResponse = new ErrorResponse(e.getMessage()); + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(errorResponse); + } + + @ExceptionHandler(NoPermissionException.class) + public ResponseEntity handleNoPermission(final NoPermissionException e) { + ErrorResponse errorResponse = new ErrorResponse(e.getMessage()); + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(errorResponse); + } + + @ExceptionHandler({ + NoSuchCategoryException.class, + NoSuchMemberException.class, + NoSuchSubscriptionException.class, + NoSuchScheduleException.class, + NoSuchOAuthTokenException.class + }) + public ResponseEntity handleNoSuchData(final RuntimeException e) { + ErrorResponse errorResponse = new ErrorResponse(e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(errorResponse); + } + + @ExceptionHandler(OAuthException.class) + public ResponseEntity handleOAuthException(final RuntimeException e) { + log.error("OAuth 통신 과정에서 에러가 발생했습니다.", e); + ErrorResponse errorResponse = new ErrorResponse("OAuth 통신 과정에서 에러가 발생했습니다."); + return ResponseEntity.internalServerError().body(errorResponse); + } + + @ExceptionHandler(HttpMessageNotReadableException.class) + public ResponseEntity handleInvalidRequestBody() { + ErrorResponse errorResponse = new ErrorResponse("잘못된 형식의 Request Body 입니다."); + return ResponseEntity.badRequest().body(errorResponse); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity handleInvalidDtoField(final MethodArgumentNotValidException e) { + FieldError firstFieldError = e.getFieldErrors().get(0); + String errorMessage = String.format(INVALID_DTO_FIELD_ERROR_MESSAGE_FORMAT, firstFieldError.getField(), + firstFieldError.getDefaultMessage(), firstFieldError.getRejectedValue()); + + ErrorResponse errorResponse = new ErrorResponse(errorMessage); + return ResponseEntity.badRequest().body(errorResponse); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity handleTypeMismatch() { + ErrorResponse errorResponse = new ErrorResponse("잘못된 데이터 타입입니다."); + return ResponseEntity.badRequest().body(errorResponse); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnexpectedException(final Exception e, + final HttpServletRequest request) { + ErrorReportRequest errorReport = new ErrorReportRequest(request, e); + log.error(errorReport.getLogMessage(), e); + + ErrorResponse errorResponse = new ErrorResponse("예상하지 못한 서버 에러가 발생했습니다."); + return ResponseEntity.internalServerError().body(errorResponse); + } +} diff --git a/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorReportRequest.java b/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorReportRequest.java new file mode 100644 index 00000000..06000c30 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorReportRequest.java @@ -0,0 +1,31 @@ +package com.allog.dallog.global.error.dto; + +import javax.servlet.http.HttpServletRequest; + +public class ErrorReportRequest { + + private static final String ERROR_REPORT_FORMAT = "[%s] %s"; + + private final HttpServletRequest request; + private final Exception exception; + + public ErrorReportRequest(final HttpServletRequest request, final Exception exception) { + this.request = request; + this.exception = exception; + } + + public String getLogMessage() { + String requestUri = request.getRequestURI(); + String requestMethod = request.getMethod(); + + return String.format(ERROR_REPORT_FORMAT, requestMethod, requestUri); + } + + public HttpServletRequest getRequest() { + return request; + } + + public Exception getException() { + return exception; + } +} diff --git a/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorResponse.java b/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorResponse.java new file mode 100644 index 00000000..37032ddc --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/global/error/dto/ErrorResponse.java @@ -0,0 +1,14 @@ +package com.allog.dallog.global.error.dto; + +public class ErrorResponse { + + private final String message; + + public ErrorResponse(final String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/log/DiscordAppender.java b/backend/src/main/java/com/allog/dallog/infrastructure/log/DiscordAppender.java new file mode 100644 index 00000000..0f23218b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/log/DiscordAppender.java @@ -0,0 +1,107 @@ +package com.allog.dallog.infrastructure.log; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.classic.spi.IThrowableProxy; +import ch.qos.logback.classic.spi.StackTraceElementProxy; +import ch.qos.logback.core.UnsynchronizedAppenderBase; +import com.allog.dallog.infrastructure.log.dto.DiscordWebhookRequest; +import com.allog.dallog.infrastructure.log.dto.Embed; +import com.allog.dallog.infrastructure.log.dto.Field; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +public class DiscordAppender extends UnsynchronizedAppenderBase { + + private static final String TITLE_FORMAT = "[%s] %s"; + private static final String DESCRIPTION_FORMAT = "%s: %s"; + private static final RestTemplate CLIENT; + + static { + SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory(); + factory.setConnectTimeout(3000); + CLIENT = new RestTemplate(factory); + } + + private String username; + private String embedsColor; + private int stackTraceMaxSize; + private String webhookUri; + + @Override + protected void append(final ILoggingEvent eventObject) { + if (!Objects.isNull(webhookUri) && !webhookUri.isEmpty()) { + String title = getTitle(eventObject); + List embeds = getEmbeds(title, embedsColor, eventObject); + DiscordWebhookRequest request = new DiscordWebhookRequest(username, embeds); + + CLIENT.postForEntity(webhookUri, request, Void.class); + } + } + + private String getTitle(final ILoggingEvent eventObject) { + return String.format(TITLE_FORMAT, eventObject.getLevel(), eventObject.getMessage()); + } + + private List getEmbeds(final String title, final String embedsColor, final ILoggingEvent eventObject) { + if (Objects.isNull(eventObject.getThrowableProxy())) { + return List.of(new Embed(title, embedsColor)); + } + + IThrowableProxy throwableProxy = eventObject.getThrowableProxy(); + String description = getDescription(throwableProxy); + List fields = getFields(throwableProxy); + + return List.of(new Embed(title, description, embedsColor, fields)); + } + + private String getDescription(final IThrowableProxy throwableProxy) { + return String.format(DESCRIPTION_FORMAT, throwableProxy.getClassName(), throwableProxy.getMessage()); + } + + private List getFields(final IThrowableProxy throwableProxy) { + List stackTraces = Arrays.stream(throwableProxy.getStackTraceElementProxyArray()) + .map(StackTraceElementProxy::getSTEAsString) + .limit(stackTraceMaxSize) + .collect(Collectors.toList()); + + return stackTraces.stream() + .map(Field::from) + .collect(Collectors.toList()); + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public String getEmbedsColor() { + return embedsColor; + } + + public void setEmbedsColor(final String embedsColor) { + this.embedsColor = embedsColor; + } + + public int getStackTraceMaxSize() { + return stackTraceMaxSize; + } + + public void setStackTraceMaxSize(final int stackTraceMaxSize) { + this.stackTraceMaxSize = stackTraceMaxSize; + } + + public String getWebhookUri() { + return webhookUri; + } + + public void setWebhookUri(final String webhookUri) { + this.webhookUri = webhookUri; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/DiscordWebhookRequest.java b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/DiscordWebhookRequest.java new file mode 100644 index 00000000..484ebb8b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/DiscordWebhookRequest.java @@ -0,0 +1,25 @@ +package com.allog.dallog.infrastructure.log.dto; + +import java.util.List; + +public class DiscordWebhookRequest { + + private String username; + private List embeds; + + private DiscordWebhookRequest() { + } + + public DiscordWebhookRequest(final String username, final List embeds) { + this.username = username; + this.embeds = embeds; + } + + public String getUsername() { + return username; + } + + public List getEmbeds() { + return embeds; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Embed.java b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Embed.java new file mode 100644 index 00000000..76849155 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Embed.java @@ -0,0 +1,41 @@ +package com.allog.dallog.infrastructure.log.dto; + +import java.util.List; + +public class Embed { + + private String title; + private String description; + private String color; + private List fields; + + private Embed() { + } + + public Embed(final String title, final String color) { + this(title, null, color, null); + } + + public Embed(final String title, final String description, final String color, final List fields) { + this.title = title; + this.description = description; + this.color = color; + this.fields = fields; + } + + public String getTitle() { + return title; + } + + public String getDescription() { + return description; + } + + public String getColor() { + return color; + } + + public List getFields() { + return fields; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Field.java b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Field.java new file mode 100644 index 00000000..a80c41d4 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/log/dto/Field.java @@ -0,0 +1,29 @@ +package com.allog.dallog.infrastructure.log.dto; + +public class Field { + + private String name; + private String value; + + private Field() { + } + + private Field(final String name, final String value) { + this.name = name; + this.value = value; + } + + public static Field from(final String steAsString) { + String name = steAsString.substring(steAsString.indexOf("(") + 1, steAsString.indexOf(")")); + String value = steAsString.substring(0, steAsString.indexOf("(")); + return new Field(name, value); + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } +} 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 new file mode 100644 index 00000000..2fefc06e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleExternalCalendarClient.java @@ -0,0 +1,117 @@ +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.integrationschedule.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; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +@Component +public class GoogleExternalCalendarClient implements ExternalCalendarClient { + + private static final String CALENDAR_LIST_REQUEST_URI = "https://www.googleapis.com/calendar/v3/users/me/calendarList"; + private static final String CALENDAR_EVENTS_REQUEST_URI = "https://www.googleapis.com/calendar/v3/calendars/{calendarId}/events?singleEvents=true&timeMax={timeMax}&timeMin={timeMin}"; + private static final String ACCEPT_HEADER_NAME = "Accept"; + + private final RestTemplate restTemplate; + + public GoogleExternalCalendarClient(final RestTemplateBuilder restTemplateBuilder) { + this.restTemplate = restTemplateBuilder.build(); + } + + @Override + public List getExternalCalendars(final String accessToken) { + HttpEntity request = new HttpEntity<>(generateCalendarRequestHeaders(accessToken)); + GoogleCalendarListResponse response = fetchGoogleCalendarList(request).getBody(); + + return response.getItems() + .stream() + .map(item -> new ExternalCalendar(item.getId(), item.getSummary())) + .collect(Collectors.toList()); + } + + private ResponseEntity fetchGoogleCalendarList(final HttpEntity request) { + try { + return restTemplate.exchange(CALENDAR_LIST_REQUEST_URI, HttpMethod.GET, request, + GoogleCalendarListResponse.class); + } catch (RestClientException e) { + throw new OAuthException(e); + } + } + + @Override + public List getExternalCalendarSchedules(final String accessToken, + final Long internalCategoryId, + final String calendarId, + final String startDateTime, + final String endDateTime) { + HttpEntity request = new HttpEntity<>(generateCalendarRequestHeaders(accessToken)); + + Map uriVariables = generateEventsVariables(calendarId, startDateTime, endDateTime); + GoogleCalendarEventsResponse response = fetchGoogleCalendarEvents(uriVariables, request).getBody(); + + return response.getItems() + .stream() + .map(event -> parseIntegrationSchedule(internalCategoryId, event)) + .collect(Collectors.toList()); + } + + private HttpHeaders generateCalendarRequestHeaders(final String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + headers.set(ACCEPT_HEADER_NAME, MediaType.APPLICATION_JSON_VALUE); + return headers; + } + + private Map generateEventsVariables(final String externalCalendarId, final String startDateTime, + final String endDateTime) { + return Map.of( + "calendarId", externalCalendarId, + "timeMax", endDateTime + "Z", + "timeMin", startDateTime + "Z" + ); + } + + private ResponseEntity fetchGoogleCalendarEvents( + final Map uriVariables, final HttpEntity request) { + try { + return restTemplate.exchange(CALENDAR_EVENTS_REQUEST_URI, HttpMethod.GET, request, + GoogleCalendarEventsResponse.class, uriVariables); + } catch (RestClientException e) { + 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.name()); + } + + 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/client/GoogleOAuthClient.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleOAuthClient.java new file mode 100644 index 00000000..e5a106d0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/client/GoogleOAuthClient.java @@ -0,0 +1,123 @@ +package com.allog.dallog.infrastructure.oauth.client; + +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; +import com.allog.dallog.global.config.properties.GoogleProperties; +import com.allog.dallog.infrastructure.oauth.dto.GoogleTokenResponse; +import com.allog.dallog.infrastructure.oauth.dto.UserInfo; +import com.allog.dallog.infrastructure.oauth.exception.OAuthException; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +@Component +public class GoogleOAuthClient implements OAuthClient { + + private static final String JWT_DELIMITER = "\\."; + + private final GoogleProperties properties; + private final RestTemplate restTemplate; + private final ObjectMapper objectMapper; + + public GoogleOAuthClient(final GoogleProperties properties, final RestTemplateBuilder restTemplateBuilder, + final ObjectMapper objectMapper) { + this.properties = properties; + this.restTemplate = restTemplateBuilder.build(); + this.objectMapper = objectMapper; + } + + @Override + public OAuthMember getOAuthMember(final String code, final String redirectUri) { + GoogleTokenResponse googleTokenResponse = requestGoogleToken(code, redirectUri); + String payload = getPayload(googleTokenResponse.getIdToken()); + UserInfo userInfo = parseUserInfo(payload); + + String refreshToken = googleTokenResponse.getRefreshToken(); + return new OAuthMember(userInfo.getEmail(), userInfo.getName(), userInfo.getPicture(), refreshToken); + } + + private GoogleTokenResponse requestGoogleToken(final String code, final String redirectUri) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap params = generateTokenParams(code, redirectUri); + + HttpEntity> request = new HttpEntity<>(params, headers); + return fetchGoogleToken(request).getBody(); + } + + private MultiValueMap generateTokenParams(final String code, final String redirectUri) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", properties.getClientId()); + params.add("client_secret", properties.getClientSecret()); + params.add("code", code); + params.add("grant_type", "authorization_code"); + params.add("redirect_uri", redirectUri); + return params; + } + + private ResponseEntity fetchGoogleToken( + final HttpEntity> request) { + try { + return restTemplate.postForEntity(properties.getTokenUri(), request, GoogleTokenResponse.class); + } catch (RestClientException e) { + throw new OAuthException(e); + } + } + + private String getPayload(final String jwt) { + return jwt.split(JWT_DELIMITER)[1]; + } + + private UserInfo parseUserInfo(final String payload) { + String decodedPayload = decodeJwtPayload(payload); + try { + return objectMapper.readValue(decodedPayload, UserInfo.class); + } catch (JsonProcessingException e) { + throw new OAuthException("id 토큰을 읽을 수 없습니다."); + } + } + + private String decodeJwtPayload(final String payload) { + return new String(Base64.getUrlDecoder().decode(payload), StandardCharsets.UTF_8); + } + + @Override + public OAuthAccessTokenResponse getAccessToken(final String refreshToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + MultiValueMap params = generateAccessTokenParams(refreshToken); + + HttpEntity> request = new HttpEntity<>(params, headers); + return fetchGoogleAccessToken(request).getBody(); + } + + private MultiValueMap generateAccessTokenParams(final String refreshToken) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("client_id", properties.getClientId()); + params.add("client_secret", properties.getClientSecret()); + params.add("refresh_token", refreshToken); + params.add("grant_type", "refresh_token"); + return params; + } + + private ResponseEntity fetchGoogleAccessToken( + final HttpEntity> request) { + try { + return restTemplate.postForEntity(properties.getTokenUri(), request, OAuthAccessTokenResponse.class); + } catch (RestClientException e) { + throw new OAuthException(e); + } + } +} 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 new file mode 100644 index 00000000..cecbdbb0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventResponse.java @@ -0,0 +1,101 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.Objects; + +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; + 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() { + if (Objects.isNull(start.getDate())) { + return LocalDateTime.parse(start.getDateTime().substring(0, 19)); + } + + return LocalDateTime.of(LocalDate.parse(start.getDate()), LocalTime.MIN); + } + + public LocalDateTime getEndDateTime() { + if (Objects.isNull(end.getDate())) { + return LocalDateTime.parse(end.getDateTime().substring(0, 19)); + } + + 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; + } + + public String getDescription() { + return description; + } + + public String getLocation() { + return location; + } + + public GoogleDateFormat getStart() { + return start; + } + + 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 new file mode 100644 index 00000000..ffb3fcda --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarEventsResponse.java @@ -0,0 +1,70 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +import java.util.List; + +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; + 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 new file mode 100644 index 00000000..61d92a56 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarListResponse.java @@ -0,0 +1,44 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +import java.util.List; + +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; + 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 new file mode 100644 index 00000000..d43cf782 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleCalendarResponse.java @@ -0,0 +1,111 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +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; + 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() { + return id; + } + + public String getSummary() { + return summary; + } + + 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 new file mode 100644 index 00000000..cd30178d --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleDateFormat.java @@ -0,0 +1,29 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +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) { + this.date = date; + this.dateTime = dateTime; + this.timeZone = timeZone; + } + + public String getDate() { + return date; + } + + 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 new file mode 100644 index 00000000..ec979965 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/GoogleTokenResponse.java @@ -0,0 +1,52 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +import com.fasterxml.jackson.databind.PropertyNamingStrategies; +import com.fasterxml.jackson.databind.annotation.JsonNaming; + +@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; + this.refreshToken = refreshToken; + this.idToken = idToken; + this.expiresIn = expiresIn; + this.scope = scope; + this.tokenType = tokenType; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public String getIdToken() { + return idToken; + } + + public String getExpiresIn() { + return expiresIn; + } + + public String getScope() { + return scope; + } + + public String getTokenType() { + return tokenType; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/UserInfo.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/UserInfo.java new file mode 100644 index 00000000..44616426 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/dto/UserInfo.java @@ -0,0 +1,29 @@ +package com.allog.dallog.infrastructure.oauth.dto; + +public class UserInfo { + + private String email; + private String name; + private String picture; + + private UserInfo() { + } + + public UserInfo(final String email, final String name, final String picture) { + this.email = email; + this.name = name; + this.picture = picture; + } + + public String getEmail() { + return email; + } + + public String getName() { + return name; + } + + public String getPicture() { + return picture; + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/exception/OAuthException.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/exception/OAuthException.java new file mode 100644 index 00000000..cc5dbd47 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/exception/OAuthException.java @@ -0,0 +1,16 @@ +package com.allog.dallog.infrastructure.oauth.exception; + +public class OAuthException extends RuntimeException { + + public OAuthException(final Exception e) { + super(e); + } + + public OAuthException(final String message) { + super(message); + } + + public OAuthException() { + this("Oauth 서버와의 통신 과정에서 문제가 발생했습니다."); + } +} diff --git a/backend/src/main/java/com/allog/dallog/infrastructure/oauth/uri/GoogleOAuthUri.java b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/uri/GoogleOAuthUri.java new file mode 100644 index 00000000..28d153a0 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/infrastructure/oauth/uri/GoogleOAuthUri.java @@ -0,0 +1,26 @@ +package com.allog.dallog.infrastructure.oauth.uri; + +import com.allog.dallog.domain.auth.application.OAuthUri; +import com.allog.dallog.global.config.properties.GoogleProperties; +import org.springframework.stereotype.Component; + +@Component +public class GoogleOAuthUri implements OAuthUri { + + private final GoogleProperties properties; + + public GoogleOAuthUri(final GoogleProperties properties) { + this.properties = properties; + } + + @Override + public String generate(final String redirectUri) { + return properties.getOAuthEndPoint() + "?" + + "client_id=" + properties.getClientId() + "&" + + "redirect_uri=" + redirectUri + "&" + + "response_type=code&" + + "scope=" + String.join(" ", properties.getScopes()) + "&" + + "access_type=" + properties.getAccessType() + "&" + + "prompt=" + properties.getPrompt(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java new file mode 100644 index 00000000..7d18729f --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/CategoryController.java @@ -0,0 +1,77 @@ +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.CategoryCreateRequest; +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; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/categories") +@RestController +public class CategoryController { + + private final CategoryService categoryService; + private final CategorySubscriptionService categorySubscriptionService; + + public CategoryController(final CategoryService categoryService, + final CategorySubscriptionService categorySubscriptionService) { + 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); + return ResponseEntity.created(URI.create("/api/categories/" + categoryResponse.getId())).body(categoryResponse); + } + + @GetMapping + public ResponseEntity findAllByName(@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)); + } + + @PatchMapping("/{categoryId}") + public ResponseEntity update(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long categoryId, + @RequestBody final CategoryUpdateRequest request) { + categoryService.update(loginMember.getId(), categoryId, request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/{categoryId}") + public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long categoryId) { + categoryService.deleteById(loginMember.getId(), categoryId); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java new file mode 100644 index 00000000..239b0d94 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/ExternalCalendarController.java @@ -0,0 +1,44 @@ +package com.allog.dallog.presentation; + +import com.allog.dallog.domain.auth.dto.LoginMember; +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; +import java.net.URI; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/external-calendars/me") +@RestController +public class ExternalCalendarController { + + private final ExternalCalendarService externalCalendarService; + private final CategorySubscriptionService categorySubscriptionService; + + public ExternalCalendarController(final ExternalCalendarService externalCalendarService, + final CategorySubscriptionService categorySubscriptionService) { + this.externalCalendarService = externalCalendarService; + this.categorySubscriptionService = categorySubscriptionService; + } + + @GetMapping + public ResponseEntity getExternalCalendar( + @AuthenticationPrincipal final LoginMember loginMember) { + + return ResponseEntity.ok(externalCalendarService.findByMemberId(loginMember.getId())); + } + + @PostMapping + public ResponseEntity save(@AuthenticationPrincipal final LoginMember loginMember, + @RequestBody final ExternalCategoryCreateRequest request) { + CategoryResponse response = categorySubscriptionService.save(loginMember.getId(), request); + return ResponseEntity.created(URI.create("/api/categories/" + response.getId())).body(response); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/MemberController.java b/backend/src/main/java/com/allog/dallog/presentation/MemberController.java new file mode 100644 index 00000000..d07b7ba9 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/MemberController.java @@ -0,0 +1,47 @@ +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; +import com.allog.dallog.presentation.auth.AuthenticationPrincipal; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/members") +@RestController +public class MemberController { + + private final MemberService memberService; + private final RegisterService registerService; + + public MemberController(final MemberService memberService, final RegisterService registerService) { + this.memberService = memberService; + this.registerService = registerService; + } + + @GetMapping("/me") + public ResponseEntity findMe(@AuthenticationPrincipal final LoginMember loginMember) { + MemberResponse response = memberService.findById(loginMember.getId()); + return ResponseEntity.ok(response); + } + + @PatchMapping("/me") + public ResponseEntity update(@AuthenticationPrincipal LoginMember loginMember, + @RequestBody final MemberUpdateRequest request) { + memberService.update(loginMember.getId(), request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/me") + public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember) { + registerService.deleteByMemberId(loginMember.getId()); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java new file mode 100644 index 00000000..4d95e609 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/ScheduleController.java @@ -0,0 +1,72 @@ +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.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.MemberScheduleResponses; +import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; +import com.allog.dallog.presentation.auth.AuthenticationPrincipal; +import java.net.URI; +import javax.validation.Valid; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api") +@RestController +public class ScheduleController { + + private final ScheduleService scheduleService; + private final CalendarService calendarService; + + public ScheduleController(final ScheduleService scheduleService, final CalendarService calendarService) { + this.scheduleService = scheduleService; + this.calendarService = calendarService; + } + + @PostMapping("/categories/{categoryId}/schedules") + public ResponseEntity save(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long categoryId, + @Valid @RequestBody final ScheduleCreateRequest request) { + ScheduleResponse response = scheduleService.save(loginMember.getId(), categoryId, request); + return ResponseEntity.created(URI.create("/api/schedules/" + response.getId())).body(response); + } + + @GetMapping("/members/me/schedules") + public ResponseEntity findSchedulesByMemberId( + @AuthenticationPrincipal final LoginMember loginMember, @ModelAttribute DateRangeRequest request) { + MemberScheduleResponses response = calendarService.findSchedulesByMemberId(loginMember.getId(), request); + return ResponseEntity.ok(response); + } + + @GetMapping("/schedules/{scheduleId}") + public ResponseEntity findById(@PathVariable final Long scheduleId) { + ScheduleResponse response = scheduleService.findById(scheduleId); + return ResponseEntity.ok(response); + } + + @PatchMapping("/schedules/{scheduleId}") + public ResponseEntity update(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long scheduleId, + @Valid @RequestBody final ScheduleUpdateRequest request) { + scheduleService.update(scheduleId, loginMember.getId(), request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/schedules/{scheduleId}") + public ResponseEntity delete(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long scheduleId) { + scheduleService.deleteById(scheduleId, loginMember.getId()); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/SchedulerController.java b/backend/src/main/java/com/allog/dallog/presentation/SchedulerController.java new file mode 100644 index 00000000..f82c4f73 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/SchedulerController.java @@ -0,0 +1,30 @@ +package com.allog.dallog.presentation; + +import com.allog.dallog.domain.composition.application.SchedulerService; +import com.allog.dallog.domain.schedule.dto.request.DateRangeRequest; +import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; +import java.util.List; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/scheduler") +@RestController +public class SchedulerController { + + private final SchedulerService schedulerService; + + public SchedulerController(final SchedulerService schedulerService) { + this.schedulerService = schedulerService; + } + + @GetMapping("/categories/{categoryId}/available-periods") + public ResponseEntity> scheduleByCategory(@PathVariable final Long categoryId, + @ModelAttribute DateRangeRequest dateRange) { + List periods = schedulerService.getAvailablePeriods(categoryId, dateRange); + return ResponseEntity.ok(periods); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java new file mode 100644 index 00000000..2cdc5cc2 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/SubscriptionController.java @@ -0,0 +1,59 @@ +package com.allog.dallog.presentation; + +import com.allog.dallog.domain.auth.dto.LoginMember; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +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.presentation.auth.AuthenticationPrincipal; +import java.net.URI; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/members/me") +@RestController +public class SubscriptionController { + + private final SubscriptionService subscriptionService; + + public SubscriptionController(final SubscriptionService subscriptionService) { + this.subscriptionService = subscriptionService; + } + + @PostMapping("/categories/{categoryId}/subscriptions") + public ResponseEntity save(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long categoryId) { + SubscriptionResponse response = subscriptionService.save(loginMember.getId(), categoryId); + return ResponseEntity.created( + URI.create("/api/members/me/categories/" + categoryId + "/subscriptions/" + response.getId())) + .body(response); + } + + @GetMapping("/subscriptions") + public ResponseEntity findMine(@AuthenticationPrincipal final LoginMember loginMember) { + SubscriptionsResponse response = subscriptionService.findByMemberId(loginMember.getId()); + return ResponseEntity.ok(response); + } + + @PatchMapping("/subscriptions/{subscriptionId}") + public ResponseEntity update(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long subscriptionId, + @RequestBody final SubscriptionUpdateRequest request) { + subscriptionService.update(subscriptionId, loginMember.getId(), request); + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/subscriptions/{subscriptionId}") + public ResponseEntity deleteById(@AuthenticationPrincipal final LoginMember loginMember, + @PathVariable final Long subscriptionId) { + subscriptionService.deleteById(subscriptionId, loginMember.getId()); + return ResponseEntity.noContent().build(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/auth/AuthController.java b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthController.java new file mode 100644 index 00000000..41aeec7b --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthController.java @@ -0,0 +1,45 @@ +package com.allog.dallog.presentation.auth; + +import com.allog.dallog.domain.auth.application.AuthService; +import com.allog.dallog.domain.auth.dto.LoginMember; +import com.allog.dallog.domain.auth.dto.request.TokenRequest; +import com.allog.dallog.domain.auth.dto.response.OAuthUriResponse; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/auth") +@RestController +public class AuthController { + + private final AuthService authService; + + public AuthController(final AuthService authService) { + this.authService = authService; + } + + @GetMapping("/{oauthProvider}/oauth-uri") + public ResponseEntity generateLink(@PathVariable final String oauthProvider, + @RequestParam final String redirectUri) { + OAuthUriResponse oAuthUriResponse = new OAuthUriResponse(authService.generateGoogleLink(redirectUri)); + return ResponseEntity.ok(oAuthUriResponse); + } + + @PostMapping("/{oauthProvider}/token") + public ResponseEntity generateToken(@PathVariable final String oauthProvider, + @RequestBody final TokenRequest tokenRequest) { + TokenResponse tokenResponse = authService.generateToken(tokenRequest); + return ResponseEntity.ok(tokenResponse); + } + + @GetMapping("/validate/token") + public ResponseEntity validateToken(@AuthenticationPrincipal final LoginMember loginMember) { + return ResponseEntity.ok().build(); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipal.java b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipal.java new file mode 100644 index 00000000..33f6000e --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipal.java @@ -0,0 +1,11 @@ +package com.allog.dallog.presentation.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthenticationPrincipal { +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipalArgumentResolver.java b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipalArgumentResolver.java new file mode 100644 index 00000000..39515f70 --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthenticationPrincipalArgumentResolver.java @@ -0,0 +1,35 @@ +package com.allog.dallog.presentation.auth; + +import com.allog.dallog.domain.auth.application.AuthService; +import com.allog.dallog.domain.auth.dto.LoginMember; +import javax.servlet.http.HttpServletRequest; +import org.springframework.core.MethodParameter; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +@Component +public class AuthenticationPrincipalArgumentResolver implements HandlerMethodArgumentResolver { + + private final AuthService authService; + + public AuthenticationPrincipalArgumentResolver(final AuthService authService) { + this.authService = authService; + } + + @Override + public boolean supportsParameter(final MethodParameter parameter) { + return parameter.hasParameterAnnotation(AuthenticationPrincipal.class); + } + + @Override + public Object resolveArgument(final MethodParameter parameter, final ModelAndViewContainer mavContainer, + final NativeWebRequest webRequest, final WebDataBinderFactory binderFactory) { + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + String accessToken = AuthorizationExtractor.extract(request); + Long id = authService.extractMemberId(accessToken); + return new LoginMember(id); + } +} diff --git a/backend/src/main/java/com/allog/dallog/presentation/auth/AuthorizationExtractor.java b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthorizationExtractor.java new file mode 100644 index 00000000..2c660f7a --- /dev/null +++ b/backend/src/main/java/com/allog/dallog/presentation/auth/AuthorizationExtractor.java @@ -0,0 +1,28 @@ +package com.allog.dallog.presentation.auth; + +import com.allog.dallog.domain.auth.exception.EmptyAuthorizationHeaderException; +import com.allog.dallog.domain.auth.exception.InvalidTokenException; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import org.springframework.http.HttpHeaders; + +public class AuthorizationExtractor { + + private static final String BEARER_TYPE = "Bearer "; + + public static String extract(final HttpServletRequest request) { + String authorizationHeader = request.getHeader(HttpHeaders.AUTHORIZATION); + if (Objects.isNull(authorizationHeader)) { + throw new EmptyAuthorizationHeaderException(); + } + + validateAuthorizationFormat(authorizationHeader); + return authorizationHeader.substring(BEARER_TYPE.length()).trim(); + } + + private static void validateAuthorizationFormat(final String authorizationHeader) { + if (!authorizationHeader.toLowerCase().startsWith(BEARER_TYPE.toLowerCase())) { + throw new InvalidTokenException("token 형식이 잘못 되었습니다."); + } + } +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties deleted file mode 100644 index 8b137891..00000000 --- a/backend/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/src/main/resources/config b/backend/src/main/resources/config new file mode 160000 index 00000000..443f5d49 --- /dev/null +++ b/backend/src/main/resources/config @@ -0,0 +1 @@ +Subproject commit 443f5d49fc7f4e0122752e2ce1def3d27e9f92ba diff --git a/backend/src/main/resources/db/prod/schema.sql b/backend/src/main/resources/db/prod/schema.sql new file mode 100644 index 00000000..67d0d66f --- /dev/null +++ b/backend/src/main/resources/db/prod/schema.sql @@ -0,0 +1,68 @@ +CREATE TABLE IF NOT EXISTS members ( + id BIGINT AUTO_INCREMENT, + email VARCHAR(255) NOT NULL, + display_name VARCHAR(255) NOT NULL, + profile_image_url VARCHAR(255) NOT NULL, + social_type VARCHAR(255) NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id) +); + +CREATE TABLE IF NOT EXISTS categories ( + id BIGINT AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + members_id BIGINT NOT NULL, + category_type VARCHAR(255) NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (members_id) REFERENCES members (id) +); + +CREATE TABLE IF NOT EXISTS subscriptions ( + id BIGINT AUTO_INCREMENT, + color VARCHAR(255) NOT NULL, + checked boolean NOT NULL, + members_id BIGINT NOT NULL, + categories_id BIGINT NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (members_id) REFERENCES members (id), + FOREIGN KEY (categories_id) REFERENCES categories (id) +); + +CREATE TABLE IF NOT EXISTS schedules ( + id BIGINT AUTO_INCREMENT, + title VARCHAR(255) NOT NULL, + start_date_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + end_date_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + memo VARCHAR(255) NOT NULL, + categories_id BIGINT NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (categories_id) REFERENCES categories (id) +); + +CREATE TABLE IF NOT EXISTS oauth_tokens ( + id BIGINT AUTO_INCREMENT, + refresh_token VARCHAR(255) NOT NULL, + members_id BIGINT NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (members_id) REFERENCES members (id) +); + +CREATE TABLE IF NOT EXISTS external_category_details ( + id BIGINT AUTO_INCREMENT, + categories_id BIGINT NOT NULL, + external_id VARCHAR(255) NOT NULL, + created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + FOREIGN KEY (categories_id) REFERENCES categories (id) +); + diff --git a/backend/src/main/resources/logback-spring.xml b/backend/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..cfcc2772 --- /dev/null +++ b/backend/src/main/resources/logback-spring.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + ${FILE_LOG_PATTERN} + + ${LOG_PATH}/dallog.log + + ${LOG_FILE} + 10MB + 10 + 100MB + + + + + + + + ${USERNAME} + 15744574 + 5 + ${WEBHOOK_URI} + + + + + + ERROR + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/src/test/java/com/allog/dallog/DallogApplicationTests.java b/backend/src/test/java/com/allog/dallog/DallogApplicationTests.java deleted file mode 100644 index 06fb7334..00000000 --- a/backend/src/test/java/com/allog/dallog/DallogApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.allog.dallog; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class DallogApplicationTests { - - @Test - void contextLoads() { - } - -} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java new file mode 100644 index 00000000..77baa342 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/AcceptanceTest.java @@ -0,0 +1,25 @@ +package com.allog.dallog.acceptance; + +import com.allog.dallog.common.DatabaseCleaner; +import com.allog.dallog.common.config.ExternalApiConfig; +import io.restassured.RestAssured; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; + +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = ExternalApiConfig.class) +abstract class AcceptanceTest { + + @LocalServerPort + private int port; + + @Autowired + private DatabaseCleaner databaseCleaner; + + @BeforeEach + void setUp() { + RestAssured.port = port; + databaseCleaner.execute(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/AuthAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/AuthAcceptanceTest.java new file mode 100644 index 00000000..61dd0866 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/AuthAcceptanceTest.java @@ -0,0 +1,67 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.OAuth_인증_URI를_생성한다; +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성한다; +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.토큰이_유효한지_검증한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_401이_반환된다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.common.config.TokenConfig; +import com.allog.dallog.domain.auth.dto.response.OAuthUriResponse; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Import; + +@Import(TokenConfig.class) +@DisplayName("인증 관련 기능") +public class AuthAcceptanceTest extends AcceptanceTest { + + @DisplayName("구글 OAuth 인증 URI를 생성하여 반환한다.") + @Test + void 구글_OAuth_인증_URI를_생성하여_반환한다() { + // given & when + ExtractableResponse response = OAuth_인증_URI를_생성한다(GOOGLE_PROVIDER); + OAuthUriResponse oAuthUriResponse = response.as(OAuthUriResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(oAuthUriResponse.getoAuthUri()).contains("https://"); + }); + } + + @DisplayName("최초 사용자거나 기존에 존재하는 회원인 경우 200을 발급한다.") + @Test + void 최초_사용자거나_기존에_존재하는_회원인_경우_200을_발급한다() { + // given & when + ExtractableResponse response = 자체_토큰을_생성한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + TokenResponse tokenResponse = response.as(TokenResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(tokenResponse.getAccessToken()).isNotEmpty(); + }); + } + + @DisplayName("만료된 토큰으로 웹페이지를 로드하면 상태코드 401을 반환한다.") + @Test + void 만료된_토큰으로_웹페이지를_로드하면_상태코드_401을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = 토큰이_유효한지_검증한다(accessToken); + + // then + 상태코드_401이_반환된다(response); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/CategoryAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/CategoryAcceptanceTest.java new file mode 100644 index 00000000..640de049 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/CategoryAcceptanceTest.java @@ -0,0 +1,208 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.id를_통해_카테고리를_가져온다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.내가_등록한_카테고리를_삭제한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.내가_등록한_카테고리를_수정한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.내가_등록한_카테고리를_제목과_페이징을_통해_조회한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.새로운_카테고리를_등록한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.카테고리를_제목과_페이징을_통해_조회한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.카테고리를_페이징을_통해_조회한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_201이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_204가_반환된다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +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.후디_JPA_스터디_생성_요청; +import static com.allog.dallog.common.fixtures.CategoryFixtures.내_일정_생성_요청; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.domain.category.dto.response.CategoriesResponse; +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("카테고리 관련 기능") +public class CategoryAcceptanceTest extends AcceptanceTest { + + @DisplayName("정상적인 카테고리 정보를 등록하면 상태코드 201을 반환한다.") + @Test + void 정상적인_카테고리_정보를_등록하면_상태코드_201을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = 새로운_카테고리를_등록한다(accessToken, 매트_아고라_생성_요청); + + // then + 상태코드_201이_반환된다(response); + } + + @DisplayName("개인 카테고리를 생성하면 201을 반환한다.") + @Test + void 개인_카테고리를_생성하면_201을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = 새로운_카테고리를_등록한다(accessToken, 내_일정_생성_요청); + + // then + 상태코드_201이_반환된다(response); + } + + @DisplayName("카테고리를 등록하고 페이징을 통해 나누어 조회한다.") + @Test + void 카테고리를_등록하고_페이징을_통해_나누어_조회한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, BE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, FE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 매트_아고라_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 후디_JPA_스터디_생성_요청); + + // when + ExtractableResponse response = 카테고리를_페이징을_통해_조회한다(1, 3); + CategoriesResponse categoriesResponse = response.as(CategoriesResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(categoriesResponse.getPage()).isEqualTo(1); + assertThat(categoriesResponse.getCategories()).hasSize(2); + }); + } + + @DisplayName("카테고리를 등록하고 카테고리 제목과 페이징을 통해 나누어 조회한다.") + @Test + void 카테고리를_등록하고_카테고리_제목과_페이징을_통해_나누어_조회한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, BE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, FE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 매트_아고라_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 후디_JPA_스터디_생성_요청); + + // when + ExtractableResponse response = 카테고리를_제목과_페이징을_통해_조회한다("일", 0, 3); + CategoriesResponse categoriesResponse = response.as(CategoriesResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(categoriesResponse.getPage()).isEqualTo(0); + assertThat(categoriesResponse.getCategories()).hasSize(3); + }); + } + + @DisplayName("등록된 개인 카테고리는 카테고리 목록에서 조회할 수 없다.") + @Test + void 등록된_개인_카테고리는_카테고리_목록에서_조회할_수_없다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + /* 공개 카테고리 */ + 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, BE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, FE_일정_생성_요청); + /* 개인 카테고리 */ + 새로운_카테고리를_등록한다(accessToken, 내_일정_생성_요청); + + // when + ExtractableResponse response = 카테고리를_제목과_페이징을_통해_조회한다("", 0, 10); + CategoriesResponse categoriesResponse = response.as(CategoriesResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(categoriesResponse.getCategories()).hasSize(3); + }); + } + + @DisplayName("카테고리를 등록하고 내가 등록한 카테고리를 페이징을 통해 나누어 조회한다.") + @Test + void 카테고리를_등록하고_내가_등록한_카테고리를_페이징을_통해_나누어_조회한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, BE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, FE_일정_생성_요청); + + // when + ExtractableResponse response + = 내가_등록한_카테고리를_제목과_페이징을_통해_조회한다(accessToken, "", 0, 3); + CategoriesResponse categoriesResponse = response.as(CategoriesResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(categoriesResponse.getPage()).isEqualTo(0); + assertThat(categoriesResponse.getCategories()).hasSize(3); + }); + } + + @DisplayName("카테고리를 등록하고 내가 등록한 카테고리를 제목과 페이징을 통해 나누어 조회한다.") + @Test + void 카테고리를_등록하고_내가_등록한_카테고리를_제목과_페이징을_통해_나누어_조회한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, BE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, FE_일정_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 매트_아고라_생성_요청); + 새로운_카테고리를_등록한다(accessToken, 후디_JPA_스터디_생성_요청); + + // when + ExtractableResponse response + = 내가_등록한_카테고리를_제목과_페이징을_통해_조회한다(accessToken, "일", 0, 2); + CategoriesResponse categoriesResponse = response.as(CategoriesResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(categoriesResponse.getPage()).isEqualTo(0); + assertThat(categoriesResponse.getCategories()).hasSize(2); + }); + } + + @DisplayName("카테고리를 등록하고 내가 등록한 카테고리를 수정하면 상태코드 204를 반환한다.") + @Test + void 카테고리를_등록하고_내가_등록한_카테고리를_수정하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse savedCategory = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + String newCategoryName = "우테코 공통 일정"; + + // when + ExtractableResponse response = 내가_등록한_카테고리를_수정한다(accessToken, savedCategory.getId(), newCategoryName); + CategoryResponse categoryResponse = id를_통해_카테고리를_가져온다(savedCategory.getId()).as(CategoryResponse.class); + + // then + assertAll(() -> { + 상태코드_204가_반환된다(response); + assertThat(categoryResponse.getName()).isEqualTo(newCategoryName); + }); + } + + @DisplayName("카테고리를 등록하고 내가 등록한 카테고리를 삭제하면 상태코드 204를 반환한다.") + @Test + void 카테고리를_등록하고_내가_등록한_카테고리를_삭제하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse savedCategory = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + + // when + ExtractableResponse response = 내가_등록한_카테고리를_삭제한다(accessToken, savedCategory.getId()); + + // then + 상태코드_204가_반환된다(response); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/ExternalCalendarAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/ExternalCalendarAcceptanceTest.java new file mode 100644 index 00000000..27b680f3 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/ExternalCalendarAcceptanceTest.java @@ -0,0 +1,73 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_201이_반환된다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.우아한테크코스_생성_요청; +import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.우아한테크코스_이름; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +@DisplayName("외부 캘린더 관련 기능") +class ExternalCalendarAcceptanceTest extends AcceptanceTest { + + @DisplayName("자신의 외부 캘린더 리스트를 조회하면 200을 반환한다.") + @Test + void 자신의_외부_캘린더_리스트를_조회하면_200을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(accessToken) + .accept(MediaType.APPLICATION_JSON_VALUE) + .when().get("/api/external-calendars/me") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + ExternalCalendarsResponse externalCalendarsResponse = response.as(ExternalCalendarsResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(externalCalendarsResponse.getExternalCalendars()).hasSize(3); + }); + } + + @DisplayName("외부 캘린더를 추가하면 201을 반환한다.") + @Test + void 외부_캘린더를_추가하면_201을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .accept(MediaType.APPLICATION_JSON_VALUE) + .body(우아한테크코스_생성_요청) + .when().post("/api/external-calendars/me") + .then().log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract(); + CategoryResponse categoryResponse = response.as(CategoryResponse.class); + + // then + assertAll(() -> { + 상태코드_201이_반환된다(response); + assertThat(categoryResponse.getName()).isEqualTo(우아한테크코스_이름); + }); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/MemberAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/MemberAcceptanceTest.java new file mode 100644 index 00000000..efb9d351 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/MemberAcceptanceTest.java @@ -0,0 +1,80 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_204가_반환된다; +import static com.allog.dallog.acceptance.fixtures.MemberAcceptanceFixtures.자신의_정보를_조회한다; +import static com.allog.dallog.acceptance.fixtures.MemberAcceptanceFixtures.회원_탈퇴_한다; +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.MEMBER_프로필; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.domain.member.dto.MemberResponse; +import com.allog.dallog.domain.member.dto.MemberUpdateRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +@DisplayName("회원 관련 기능") +public class MemberAcceptanceTest extends AcceptanceTest { + + @DisplayName("등록된 회원이 자신의 정보를 조회하면 상태코드 200을 반환한다.") + @Test + void 등록된_회원이_자신의_정보를_조회하면_상태코드_200_을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = 자신의_정보를_조회한다(accessToken); + MemberResponse memberResponse = response.as(MemberResponse.class); + + // then + assertAll(() -> { + 상태코드_200이_반환된다(response); + assertThat(memberResponse.getEmail()).isEqualTo(MEMBER_이메일); + assertThat(memberResponse.getDisplayName()).isEqualTo(MEMBER_이름); + assertThat(memberResponse.getProfileImageUrl()).isEqualTo(MEMBER_프로필); + }); + } + + @DisplayName("등록된 회원이 자신의 이름을 변경하면 상태코드 204를 반환한다.") + @Test + void 등록된_회원이_자신의_이름을_변경하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + String 패트_이름 = "패트"; + MemberUpdateRequest request = new MemberUpdateRequest(패트_이름); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().patch("/api/members/me") + .then().log().all() + .extract(); + + // then + 상태코드_204가_반환된다(response); + } + + @DisplayName("등록된 회원이 성공적으로 회원 탈퇴하면 상태코드 204를 반환한다.") + @Test + void 등록된_회원이_성공적으로_회원_탈퇴하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + // when + ExtractableResponse response = 회원_탈퇴_한다(accessToken); + + // then + 상태코드_204가_반환된다(response); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java new file mode 100644 index 00000000..3cefdf3c --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/ScheduleAcceptanceTest.java @@ -0,0 +1,91 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.새로운_카테고리를_등록한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_201이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_204가_반환된다; +import static com.allog.dallog.acceptance.fixtures.ScheduleAcceptanceFixtures.새로운_일정을_등록한다; +import static com.allog.dallog.acceptance.fixtures.ScheduleAcceptanceFixtures.일정_아이디로_일정을_단건_조회한다; +import static com.allog.dallog.acceptance.fixtures.ScheduleAcceptanceFixtures.일정을_삭제한다; +import static com.allog.dallog.acceptance.fixtures.ScheduleAcceptanceFixtures.일정을_수정한다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; +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 com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; +import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("일정 관련 기능") +class ScheduleAcceptanceTest extends AcceptanceTest { + + @DisplayName("정상적인 일정정보를 등록하면 상태코드 201을 반환한다.") + @Test + void 정상적인_일정정보를_등록하면_상태코드_201을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + + // when + ExtractableResponse response = 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()); + + // then + 상태코드_201이_반환된다(response); + } + + @DisplayName("일정 ID로 일정을 단건조회_하면 상태코드 200을 반환한다.") + @Test + void 일정_ID로_일정을_단건조회_하면_상태코드_200을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + ScheduleResponse 알록달록_회의 = 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()).as(ScheduleResponse.class); + + // when + ExtractableResponse response = 일정_아이디로_일정을_단건_조회한다(accessToken, 알록달록_회의.getId()); + + // then + 상태코드_200이_반환된다(response); + } + + @DisplayName("일정을 수정하면 상태코드 204를 반환한다.") + @Test + void 일정을_수정하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + ScheduleResponse 알록달록_회의 = 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()).as(ScheduleResponse.class); + + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + + // when + ExtractableResponse response = 일정을_수정한다(accessToken, 알록달록_회의.getId(), 일정_수정_요청); + + // then + 상태코드_204가_반환된다(response); + } + + @DisplayName("일정을 삭제하면 상태코드 204를 반환한다.") + @Test + void 일정을_삭제하면_상태코드_204를_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + ScheduleResponse 알록달록_회의 = 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()).as(ScheduleResponse.class); + + // when + ExtractableResponse response = 일정을_삭제한다(accessToken, 알록달록_회의.getId()); + + // then + 상태코드_204가_반환된다(response); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/SchedulerAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/SchedulerAcceptanceTest.java new file mode 100644 index 00000000..d64e17f7 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/SchedulerAcceptanceTest.java @@ -0,0 +1,46 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CategoryAcceptanceFixtures.새로운_카테고리를_등록한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_200이_반환된다; +import static com.allog.dallog.acceptance.fixtures.ScheduleAcceptanceFixtures.새로운_일정을_등록한다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_생성_요청; + +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.MediaType; + +@DisplayName("일정 관련 기능") +public class SchedulerAcceptanceTest extends AcceptanceTest { + + @DisplayName("일정 조율을 받아오면 상태코드 200을 반환한다.") + @Test + void 일정_조율을_받아오면_상태코드_200을_반환한다() { + // given + String accessToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + + CategoryResponse 공통_일정_응답 = 새로운_카테고리를_등록한다(accessToken, 공통_일정_생성_요청).as(CategoryResponse.class); + + 새로운_일정을_등록한다(accessToken, 공통_일정_응답.getId()).as(ScheduleResponse.class); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .queryParam("startDateTime", "2022-07-01T00:00") + .queryParam("endDateTime", "2022-07-31T00:00") + .auth().oauth2(accessToken) + .when().get("/api/scheduler/categories/{categoryId}/available-periods", 공통_일정_응답.getId()) + .then().log().all() + .extract(); + + // then + 상태코드_200이_반환된다(response); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/SubscriptionAcceptanceTest.java b/backend/src/test/java/com/allog/dallog/acceptance/SubscriptionAcceptanceTest.java new file mode 100644 index 00000000..ac376396 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/SubscriptionAcceptanceTest.java @@ -0,0 +1,168 @@ +package com.allog.dallog.acceptance; + +import static com.allog.dallog.acceptance.fixtures.AuthAcceptanceFixtures.자체_토큰을_생성하고_토큰을_반환한다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_201이_반환된다; +import static com.allog.dallog.acceptance.fixtures.CommonAcceptanceFixtures.상태코드_204가_반환된다; +import static com.allog.dallog.acceptance.fixtures.SubscriptionAcceptanceFixtures.구독_목록을_조회한다; +import static com.allog.dallog.common.fixtures.AuthFixtures.GOOGLE_PROVIDER; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_CREATOR_인증_코드; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +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 org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +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; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +@DisplayName("구독 관련 기능") +public class SubscriptionAcceptanceTest extends AcceptanceTest { + + @DisplayName("인증된 회원이 카테고리를 구독하면 201을 반환한다.") + @Test + void 인증된_회원이_카테고리를_구독하면_201을_반환한다() { + // given + String memberToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + String creatorToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_CREATOR_인증_코드); + CategoryResponse 공통_일정 = 새로운_카테고리를_등록한다(creatorToken, 공통_일정_생성_요청); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(memberToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/api/members/me/categories/{categoryId}/subscriptions", 공통_일정.getId()) + .then().log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract(); + + // then + 상태코드_201이_반환된다(response); + } + + @DisplayName("인증된 회원이 구독 목록을 조회하면 200을 반환한다.") + @Test + void 인증된_회원이_구독_목록을_조회하면_200을_반환한다() { + // given + String memberToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + String creatorToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_CREATOR_인증_코드); + + CategoryResponse 공통_일정 = 새로운_카테고리를_등록한다(creatorToken, 공통_일정_생성_요청); + CategoryResponse BE_일정 = 새로운_카테고리를_등록한다(creatorToken, BE_일정_생성_요청); + CategoryResponse FE_일정 = 새로운_카테고리를_등록한다(creatorToken, FE_일정_생성_요청); + + 카테고리를_구독한다(memberToken, 공통_일정.getId()); + 카테고리를_구독한다(memberToken, BE_일정.getId()); + 카테고리를_구독한다(memberToken, FE_일정.getId()); + + // when + ExtractableResponse response = 구독_목록을_조회한다(memberToken); + SubscriptionsResponse subscriptionsResponse = response.as(SubscriptionsResponse.class); + + // then + assertAll(() -> { + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + assertThat(subscriptionsResponse.getSubscriptions()).hasSize(4); // 개인 카테고리 1 + given에서 등록한 카테고리 3 + }); + } + + @DisplayName("인증된 회원이 자신의 구독 정보를 수정할 경우 204를 반환한다.") + @Test + void 인증된_회원이_자신의_구독_정보를_수정할_경우_204를_반환한다() { + // given + String memberToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + String creatorToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_CREATOR_인증_코드); + + CategoryResponse 공통_일정 = 새로운_카테고리를_등록한다(creatorToken, 공통_일정_생성_요청); + + SubscriptionResponse subscriptionResponse = 카테고리를_구독한다(memberToken, 공통_일정.getId()); + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(Color.COLOR_1, true); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(memberToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().patch("/api/members/me/subscriptions/{subscriptionId}", subscriptionResponse.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()) + .extract(); + + SubscriptionsResponse subscriptionsResponse = 구독_목록을_조회한다(memberToken).as(SubscriptionsResponse.class); + + // then + List subscriptions = subscriptionsResponse.getSubscriptions(); + SubscriptionResponse foundSubscriptionResponse = subscriptions.stream() + .filter(subscription -> subscriptionResponse.getId().equals(subscription.getId())) + .findAny() + .get(); + + assertAll(() -> { + 상태코드_204가_반환된다(response); + assertThat(foundSubscriptionResponse.getColorCode()).isEqualTo(request.getColorCode()); + assertThat(foundSubscriptionResponse.isChecked()).isTrue(); + }); + } + + @DisplayName("구독을 취소할 경우 204를 반환한다.") + @Test + void 구독을_취소할_경우_204를_반환한다() { + // given + String memberToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_MEMBER_인증_코드); + String creatorToken = 자체_토큰을_생성하고_토큰을_반환한다(GOOGLE_PROVIDER, STUB_CREATOR_인증_코드); + + CategoryResponse 공통_일정 = 새로운_카테고리를_등록한다(creatorToken, 공통_일정_생성_요청); + CategoryResponse BE_일정 = 새로운_카테고리를_등록한다(creatorToken, BE_일정_생성_요청); + CategoryResponse FE_일정 = 새로운_카테고리를_등록한다(creatorToken, FE_일정_생성_요청); + + SubscriptionResponse subscriptionResponse = 카테고리를_구독한다(memberToken, 공통_일정.getId()); + 카테고리를_구독한다(memberToken, BE_일정.getId()); + 카테고리를_구독한다(memberToken, FE_일정.getId()); + + // when + ExtractableResponse response = RestAssured.given().log().all() + .auth().oauth2(memberToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().delete("/api/members/me/subscriptions/{subscriptionId}", subscriptionResponse.getId()) + .then().log().all() + .statusCode(HttpStatus.NO_CONTENT.value()) + .extract(); + + // then + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + private CategoryResponse 새로운_카테고리를_등록한다(final String accessToken, final CategoryCreateRequest request) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().post("/api/categories") + .then().log().all() + .extract() + .as(CategoryResponse.class); + } + + private SubscriptionResponse 카테고리를_구독한다(final String accessToken, final Long categoryId) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().post("/api/members/me/categories/{categoryId}/subscriptions", categoryId) + .then().log().all() + .statusCode(HttpStatus.CREATED.value()) + .extract() + .as(SubscriptionResponse.class); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/AuthAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/AuthAcceptanceFixtures.java new file mode 100644 index 00000000..fac4d4f7 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/AuthAcceptanceFixtures.java @@ -0,0 +1,53 @@ +package com.allog.dallog.acceptance.fixtures; + +import com.allog.dallog.domain.auth.dto.request.TokenRequest; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class AuthAcceptanceFixtures { + + public static ExtractableResponse OAuth_인증_URI를_생성한다(final String oauthProvider) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().get("/api/auth/{oauthProvider}/oauth-uri?redirectUri={redirectUri}", oauthProvider, + "https://dallog.me/oauth") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + public static ExtractableResponse 자체_토큰을_생성한다(final String oauthProvider, final String code) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(new TokenRequest(code, "https://dallog.me/oauth")) + .when().post("/api/auth/{oauthProvider}/token", oauthProvider) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + public static String 자체_토큰을_생성하고_토큰을_반환한다(final String oauthProvider, final String code) { + TokenResponse tokenResponse = RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(new TokenRequest(code, "https://dallog.me/oauth")) + .when().post("/api/auth/{oauthProvider}/token", oauthProvider) + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract() + .as(TokenResponse.class); + + return tokenResponse.getAccessToken(); + } + + public static ExtractableResponse 토큰이_유효한지_검증한다(final String accessToken) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .when().get("/api/auth/validate/token") + .then().log().all() + .extract(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CategoryAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CategoryAcceptanceFixtures.java new file mode 100644 index 00000000..e984b2c6 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CategoryAcceptanceFixtures.java @@ -0,0 +1,73 @@ +package com.allog.dallog.acceptance.fixtures; + +import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; +import com.allog.dallog.domain.category.dto.request.CategoryUpdateRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.MediaType; + +public class CategoryAcceptanceFixtures { + + public static ExtractableResponse 새로운_카테고리를_등록한다(final String accessToken, + final CategoryCreateRequest request) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().post("/api/categories") + .then().log().all() + .extract(); + } + + public static ExtractableResponse 카테고리를_페이징을_통해_조회한다(final int page, final int size) { + return RestAssured.given().log().all() + .when().get("/api/categories?page={page}&size={size}", page, size) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 카테고리를_제목과_페이징을_통해_조회한다(final String name, final int page, + final int size) { + return RestAssured.given().log().all() + .when().get("/api/categories?name={name}&page={page}&size={size}", name, page, size) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 내가_등록한_카테고리를_제목과_페이징을_통해_조회한다( + final String accessToken, final String name, final int page, final int size) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .when().get("/api/categories/me?name={name}&page={page}&size={size}", name, page, size) + .then().log().all() + .extract(); + } + + public static ExtractableResponse id를_통해_카테고리를_가져온다(final Long id) { + return RestAssured.given().log().all() + .when().get("/api/categories/{categoryId}", id) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 내가_등록한_카테고리를_수정한다(final String accessToken, final Long id, + final String name) { + CategoryUpdateRequest request = new CategoryUpdateRequest(name); + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .body(request) + .when().patch("/api/categories/{categoryId}", id) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 내가_등록한_카테고리를_삭제한다(final String accessToken, final Long id) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .when().delete("/api/categories/{categoryId}", id) + .then().log().all() + .extract(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CommonAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CommonAcceptanceFixtures.java new file mode 100644 index 00000000..8af91895 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/CommonAcceptanceFixtures.java @@ -0,0 +1,30 @@ +package com.allog.dallog.acceptance.fixtures; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; + +public class CommonAcceptanceFixtures { + + public static void 상태코드_200이_반환된다(final ExtractableResponse response) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value()); + } + + public static void 상태코드_201이_반환된다(final ExtractableResponse response) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.CREATED.value()); + } + + public static void 상태코드_204가_반환된다(final ExtractableResponse response) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.NO_CONTENT.value()); + } + + public static void 상태코드_404가_반환된다(final ExtractableResponse response) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.NOT_FOUND.value()); + } + + public static void 상태코드_401이_반환된다(final ExtractableResponse response) { + assertThat(response.statusCode()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/MemberAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/MemberAcceptanceFixtures.java new file mode 100644 index 00000000..44b246c8 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/MemberAcceptanceFixtures.java @@ -0,0 +1,28 @@ +package com.allog.dallog.acceptance.fixtures; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class MemberAcceptanceFixtures { + + public static ExtractableResponse 자신의_정보를_조회한다(final String token) { + return RestAssured.given().log().all() + .auth().oauth2(token) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().get("/api/members/me") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } + + public static ExtractableResponse 회원_탈퇴_한다(final String token) { + return RestAssured.given().log().all() + .auth().oauth2(token) + .when().delete("/api/members/me") + .then().log().all() + .extract(); + } +} 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 new file mode 100644 index 00000000..c2b49763 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/ScheduleAcceptanceFixtures.java @@ -0,0 +1,60 @@ +package com.allog.dallog.acceptance.fixtures; + +import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_메모; +import static com.allog.dallog.common.fixtures.ScheduleFixtures.알록달록_회의_제목; + +import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import java.util.HashMap; +import java.util.Map; +import org.springframework.http.MediaType; + +public class ScheduleAcceptanceFixtures { + + public static ExtractableResponse 새로운_일정을_등록한다(final String accessToken, final Long categoryId) { + + Map params = new HashMap(); + params.put("title", 알록달록_회의_제목); + params.put("startDateTime", "2022-07-04T13:00"); + params.put("endDateTime", "2022-07-05T16:00"); + params.put("memo", 알록달록_회의_메모); + + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .body(params) + .when().post("/api/categories/{categoryId}/schedules", categoryId) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 일정을_수정한다(final String accessToken, final Long scheduleId, final + ScheduleUpdateRequest request) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .body(request) + .when().patch("/api/schedules/{scheduleId}", scheduleId) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 일정을_삭제한다(final String accessToken, final Long scheduleId) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .when().delete("/api/schedules/{scheduleId}", scheduleId) + .then().log().all() + .extract(); + } + + public static ExtractableResponse 일정_아이디로_일정을_단건_조회한다(final String accessToken, final Long scheduleId) { + return RestAssured.given().log().all() + .contentType(MediaType.APPLICATION_JSON_VALUE) + .auth().oauth2(accessToken) + .when().get("/api/schedules/{scheduleId}", scheduleId) + .then().log().all() + .extract(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/acceptance/fixtures/SubscriptionAcceptanceFixtures.java b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/SubscriptionAcceptanceFixtures.java new file mode 100644 index 00000000..fe85ba32 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/acceptance/fixtures/SubscriptionAcceptanceFixtures.java @@ -0,0 +1,20 @@ +package com.allog.dallog.acceptance.fixtures; + +import io.restassured.RestAssured; +import io.restassured.response.ExtractableResponse; +import io.restassured.response.Response; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +public class SubscriptionAcceptanceFixtures { + + public static ExtractableResponse 구독_목록을_조회한다(final String accessToken) { + return RestAssured.given().log().all() + .auth().oauth2(accessToken) + .contentType(MediaType.APPLICATION_JSON_VALUE) + .when().get("/api/members/me/subscriptions") + .then().log().all() + .statusCode(HttpStatus.OK.value()) + .extract(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/DatabaseCleaner.java b/backend/src/test/java/com/allog/dallog/common/DatabaseCleaner.java new file mode 100644 index 00000000..7151314a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/DatabaseCleaner.java @@ -0,0 +1,39 @@ +package com.allog.dallog.common; + +import java.util.List; +import java.util.stream.Collectors; +import javax.persistence.EntityManager; +import javax.persistence.Table; +import javax.persistence.metamodel.Type; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class DatabaseCleaner { + + private final EntityManager entityManager; + private final List tableNames; + + public DatabaseCleaner(final EntityManager entityManager) { + this.entityManager = entityManager; + this.tableNames = entityManager.getMetamodel() + .getEntities() + .stream() + .map(Type::getJavaType) + .map(javaType -> javaType.getAnnotation(Table.class)) + .map(Table::name) + .collect(Collectors.toList()); + } + + @Transactional + public void execute() { + entityManager.flush(); + entityManager.createNativeQuery("SET foreign_key_checks = 0").executeUpdate(); + + for (String tableName : tableNames) { + entityManager.createNativeQuery("TRUNCATE TABLE " + tableName).executeUpdate(); + } + + entityManager.createNativeQuery("SET foreign_key_checks = 1").executeUpdate(); + } +} 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 new file mode 100644 index 00000000..d2370dcd --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/annotation/RepositoryTest.java @@ -0,0 +1,10 @@ +package com.allog.dallog.common.annotation; + +import com.allog.dallog.global.config.JpaConfig; +import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; +import org.springframework.context.annotation.Import; + +@DataJpaTest +@Import(JpaConfig.class) +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 new file mode 100644 index 00000000..5d24814d --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/annotation/ServiceTest.java @@ -0,0 +1,19 @@ +package com.allog.dallog.common.annotation; + +import com.allog.dallog.common.DatabaseCleaner; +import com.allog.dallog.common.config.ExternalApiConfig; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = ExternalApiConfig.class) +public class ServiceTest { + + @Autowired + private DatabaseCleaner databaseCleaner; + + @BeforeEach + void setUp() { + databaseCleaner.execute(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/config/ExternalApiConfig.java b/backend/src/test/java/com/allog/dallog/common/config/ExternalApiConfig.java new file mode 100644 index 00000000..774dd827 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/config/ExternalApiConfig.java @@ -0,0 +1,23 @@ +package com.allog.dallog.common.config; + +import com.allog.dallog.domain.auth.application.OAuthClient; +import com.allog.dallog.domain.externalcalendar.application.ExternalCalendarClient; +import com.allog.dallog.infrastructure.oauth.client.StubExternalCalendarClient; +import com.allog.dallog.infrastructure.oauth.client.StubOAuthClient; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class ExternalApiConfig { + + // SpringBootTest 환경에서 OAuthClient 실제 객체 대신 테스트 더블을 빈으로 등록하기 위한 코드 + @Bean + public OAuthClient oAuthClient() { + return new StubOAuthClient(); + } + + @Bean + public ExternalCalendarClient externalCalendarClient() { + return new StubExternalCalendarClient(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/config/TokenConfig.java b/backend/src/test/java/com/allog/dallog/common/config/TokenConfig.java new file mode 100644 index 00000000..8cc6cbd5 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/config/TokenConfig.java @@ -0,0 +1,17 @@ +package com.allog.dallog.common.config; + +import static com.allog.dallog.common.fixtures.AuthFixtures.더미_시크릿_키; + +import com.allog.dallog.domain.auth.application.StubTokenProvider; +import com.allog.dallog.domain.auth.application.TokenProvider; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; + +@TestConfiguration +public class TokenConfig { + + @Bean + public TokenProvider tokenProvider() { + return new StubTokenProvider(더미_시크릿_키); + } +} 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 new file mode 100644 index 00000000..3b166c3f --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/AuthFixtures.java @@ -0,0 +1,51 @@ +package com.allog.dallog.common.fixtures; + +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; + +public class AuthFixtures { + + public static final String GOOGLE_PROVIDER = "google"; + public static final String OAUTH_PROVIDER = "oauthProvider"; + + public static final String STUB_MEMBER_인증_코드 = "member authorization code"; + public static final String STUB_CREATOR_인증_코드 = "creator authorization code"; + + public static final String 더미_엑세스_토큰 = "aaaaa.bbbbb.ccccc"; + public static final String 토큰_정보 = "Bearer " + 더미_엑세스_토큰; + public static final String OAuth_로그인_링크 = "https://accounts.google.com/o/oauth2/v2/auth"; + + public static final String MEMBER_이메일 = "member@email.com"; + public static final String MEMBER_이름 = "member"; + public static final String MEMBER_프로필 = "/member.png"; + public static final String MEMBER_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.ccccccccc"; + + public static final String CREATOR_이메일 = "creator@email.com"; + public static final String CREATOR_이름 = "creator"; + public static final String CREATOR_프로필 = "/creator.png"; + public static final String CREATOR_REFRESH_TOKEN = "aaaaaaaaaa.bbbbbbbbbb.ccccccccc"; + + 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 MEMBER_인증_코드_토큰_요청() { + return new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); + } + + public static TokenResponse MEMBER_인증_코드_토큰_응답() { + 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/CategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java new file mode 100644 index 00000000..24e71dc3 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/CategoryFixtures.java @@ -0,0 +1,91 @@ +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.PERSONAL; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; +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.time.LocalDateTime; + +public class CategoryFixtures { + + /* 공통 일정 카테고리 */ + public static final String 공통_일정_이름 = "공통 일정"; + public static final CategoryCreateRequest 공통_일정_생성_요청 = new CategoryCreateRequest(공통_일정_이름, NORMAL); + + /* BE 일정 카테고리 */ + public static final String BE_일정_이름 = "BE 일정"; + public static final CategoryCreateRequest BE_일정_생성_요청 = new CategoryCreateRequest(BE_일정_이름, NORMAL); + + /* FE 일정 카테고리 */ + public static final String FE_일정_이름 = "FE 일정"; + public static final CategoryCreateRequest FE_일정_생성_요청 = new CategoryCreateRequest(FE_일정_이름, NORMAL); + + /* 매트 아고라 카테고리 */ + public static final String 매트_아고라_이름 = "매트 아고라"; + public static final CategoryCreateRequest 매트_아고라_생성_요청 = new CategoryCreateRequest(매트_아고라_이름, NORMAL); + + /* 후디 JPA 스터디 카테고리 */ + public static final String 후디_JPA_스터디_이름 = "후디 JPA 스터디"; + public static final CategoryCreateRequest 후디_JPA_스터디_생성_요청 = new CategoryCreateRequest(후디_JPA_스터디_이름, NORMAL); + + /* 내 일정 카테고리 */ + public static final String 내_일정_이름 = "내 일정"; + public static final CategoryCreateRequest 내_일정_생성_요청 = new CategoryCreateRequest(내_일정_이름, PERSONAL); + + /* 우아한테크코스 외부 일정 카테고리 */ + public static final String 우아한테크코스_이름 = "우아한테크코스"; + public static final CategoryCreateRequest 우아한테크코스_외부_일정_생성_요청 = new CategoryCreateRequest(우아한테크코스_이름, GOOGLE); + + public static Category 공통_일정(final Member creator) { + return new Category(공통_일정_이름, creator); + } + + public static Category BE_일정(final Member creator) { + return new Category(BE_일정_이름, creator); + } + + public static Category FE_일정(final Member creator) { + return new Category(FE_일정_이름, creator); + } + + public static Category 매트_아고라(final Member creator) { + return new Category(매트_아고라_이름, creator); + } + + public static Category 후디_JPA_스터디(final Member creator) { + return new Category(후디_JPA_스터디_이름, creator); + } + + public static Category 내_일정(final Member creator) { + return new Category(내_일정_이름, creator, PERSONAL); + } + + public static Category 우아한테크코스_일정(final Member creator) { + return new Category(우아한테크코스_이름, creator, GOOGLE); + } + + public static CategoryResponse 공통_일정_응답(final MemberResponse creatorResponse) { + return new CategoryResponse(1L, 공통_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + } + + public static CategoryResponse BE_일정_응답(final MemberResponse creatorResponse) { + return new CategoryResponse(2L, BE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + } + + public static CategoryResponse FE_일정_응답(final MemberResponse creatorResponse) { + return new CategoryResponse(3L, FE_일정_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + } + + public static CategoryResponse 매트_아고라_응답(final MemberResponse creatorResponse) { + return new CategoryResponse(4L, 매트_아고라_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + } + + public static CategoryResponse 후디_JPA_스터디_응답(final MemberResponse creatorResponse) { + return new CategoryResponse(5L, 후디_JPA_스터디_이름, NORMAL.name(), creatorResponse, LocalDateTime.now()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCalendarFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCalendarFixtures.java new file mode 100644 index 00000000..e5b7db10 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCalendarFixtures.java @@ -0,0 +1,14 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendar; + +public class ExternalCalendarFixtures { + + public static final ExternalCalendar 대한민국_공휴일 = new ExternalCalendar( + "ko.south_korea#holiday@group.v.calendar.google.com", "대한민국 공휴일"); + + public static final ExternalCalendar 우아한테크코스 = new ExternalCalendar( + "en.south_korea#holiday@group.v.calendar.google.com", "우아한테크코스"); + + public static final ExternalCalendar 내_일정 = new ExternalCalendar("example@email.com", "내 일정"); +} diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCategoryFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCategoryFixtures.java new file mode 100644 index 00000000..d05001b6 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/ExternalCategoryFixtures.java @@ -0,0 +1,19 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.category.dto.request.ExternalCategoryCreateRequest; + +public class ExternalCategoryFixtures { + + public static final String 대한민국_공휴일_이름 = "대한민국 공휴일"; + public static final String 우아한테크코스_이름 = "우아한테크코스"; + public static final String 내_일정_이름 = "내 일정"; + + public static final ExternalCategoryCreateRequest 대한민국_공휴일_생성_요청 = new ExternalCategoryCreateRequest( + "ko.south_korea#holiday@group.v.calendar.google.com", 대한민국_공휴일_이름); + + public static final ExternalCategoryCreateRequest 우아한테크코스_생성_요청 = new ExternalCategoryCreateRequest( + "en.south_korea#holiday@group.v.calendar.google.com", 우아한테크코스_이름); + + public static final ExternalCategoryCreateRequest 내_일정_생성_요청 = new ExternalCategoryCreateRequest( + "example@email.com", 내_일정_이름); +} 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 new file mode 100644 index 00000000..c6d7005f --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/IntegrationScheduleFixtures.java @@ -0,0 +1,25 @@ +package com.allog.dallog.common.fixtures; + +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; + +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()); + + 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()); + + 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()); + + 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()); +} diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/MemberFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/MemberFixtures.java new file mode 100644 index 00000000..359b8f99 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/MemberFixtures.java @@ -0,0 +1,59 @@ +package com.allog.dallog.common.fixtures; + +import static com.allog.dallog.domain.member.domain.SocialType.GOOGLE; + +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.dto.MemberResponse; + +public class MemberFixtures { + + /* 관리자 */ + public static final String 관리자_이메일 = "dallog.admin@gmail.com"; + public static final String 관리자_이름 = "관리자"; + public static final String 관리자_프로필 = "/admin.png"; + public static final MemberResponse 관리자_응답 = new MemberResponse(1L, 관리자_이메일, 관리자_이름, 관리자_프로필, GOOGLE); + + /* 파랑 */ + public static final String 파랑_이메일 = "parang@email.com"; + public static final String 파랑_이름 = "파랑"; + public static final String 파랑_프로필 = "/parang.png"; + public static final MemberResponse 파랑_응답 = new MemberResponse(2L, 파랑_이메일, 파랑_이름, 파랑_프로필, GOOGLE); + + /* 리버 */ + public static final String 리버_이메일 = "leaver@email.com"; + public static final String 리버_이름 = "리버"; + public static final String 리버_프로필 = "/leaver.png"; + public static final MemberResponse 리버_응답 = new MemberResponse(3L, 리버_이메일, 리버_이름, 리버_프로필, GOOGLE); + + /* 후디 */ + public static final String 후디_이메일 = "devhudi@gmail.com"; + public static final String 후디_이름 = "후디"; + public static final String 후디_프로필 = "/hudi.png"; + public static final MemberResponse 후디_응답 = new MemberResponse(4L, 후디_이메일, 후디_이름, 후디_프로필, GOOGLE); + + /* 매트 */ + public static final String 매트_이메일 = "dev.hyeonic@gmail.com"; + public static final String 매트_이름 = "매트"; + public static final String 매트_프로필 = "/mat.png"; + public static final MemberResponse 매트_응답 = new MemberResponse(5L, 매트_이메일, 매트_이름, 매트_프로필, GOOGLE); + + public static Member 관리자() { + return new Member(관리자_이메일, 관리자_이름, 관리자_프로필, GOOGLE); + } + + public static Member 파랑() { + return new Member(파랑_이메일, 파랑_이름, 파랑_프로필, GOOGLE); + } + + public static Member 리버() { + return new Member(리버_이메일, 리버_이름, 리버_프로필, GOOGLE); + } + + public static Member 후디() { + return new Member(후디_이메일, 후디_이름, 후디_프로필, GOOGLE); + } + + public static Member 매트() { + return new Member(매트_이메일, 매트_이름, 매트_프로필, GOOGLE); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthTokenFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthTokenFixtures.java new file mode 100644 index 00000000..86041645 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/OAuthTokenFixtures.java @@ -0,0 +1,13 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.auth.domain.OAuthToken; +import com.allog.dallog.domain.member.domain.Member; + +public class OAuthTokenFixtures { + + public static final String REFRESH_TOKEN = "adasdqwrwggsdfsdfaasfadfsdvsvzsdrw"; + + public static OAuthToken OAUTH_TOKEN(final Member member) { + return new OAuthToken(member, REFRESH_TOKEN); + } +} 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 new file mode 100644 index 00000000..4f478972 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/ScheduleFixtures.java @@ -0,0 +1,71 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.schedule.domain.Schedule; +import com.allog.dallog.domain.schedule.dto.request.ScheduleCreateRequest; +import com.allog.dallog.domain.schedule.dto.response.ScheduleResponse; +import java.time.LocalDateTime; + +public class ScheduleFixtures { + + /* 날짜 */ + public static final LocalDateTime 날짜_2022년_7월_1일_0시_0분 = LocalDateTime.of(2022, 7, 1, 0, 0); + 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월_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); + public static final LocalDateTime 날짜_2022년_7월_16일_18시_0분 = LocalDateTime.of(2022, 7, 16, 18, 0); + public static final LocalDateTime 날짜_2022년_7월_16일_20시_0분 = LocalDateTime.of(2022, 7, 16, 20, 0); + 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월_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월_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); + public static final LocalDateTime 날짜_2022년_8월_15일_23시_59분 = LocalDateTime.of(2022, 8, 15, 23, 59); + + /* 알록달록 회의 */ + public static final String 알록달록_회의_제목 = "알록달록 회의"; + public static final LocalDateTime 알록달록_회의_시작일시 = LocalDateTime.of(2022, 7, 15, 16, 0); + public static final LocalDateTime 알록달록_회의_종료일시 = LocalDateTime.of(2022, 7, 16, 16, 0); + public static final String 알록달록_회의_메모 = "알록달록 회의가 있어요"; + public static final ScheduleCreateRequest 알록달록_회의_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, + 알록달록_회의_종료일시, 알록달록_회의_메모); + public static final ScheduleResponse 알록달록_회의_응답 = new ScheduleResponse(1L, 1L, 알록달록_회의_제목, 알록달록_회의_시작일시, + 알록달록_회의_종료일시, 알록달록_회의_메모, "NORMAL"); + + /* 알록달록 회식 */ + public static final String 알록달록_회식_제목 = "알록달록 회식"; + public static final LocalDateTime 알록달록_회식_시작일시 = LocalDateTime.of(2022, 7, 7, 16, 0); + public static final LocalDateTime 알록달록_회식_종료일시 = LocalDateTime.of(2022, 7, 9, 16, 0); + public static final String 알록달록_회식_메모 = "알록달록 회식이 있어요"; + public static final ScheduleCreateRequest 알록달록_회식_생성_요청 = new ScheduleCreateRequest(알록달록_회식_제목, 알록달록_회식_시작일시, + 알록달록_회식_종료일시, 알록달록_회식_메모); + + /* 레벨 인터뷰 */ + public static final String 레벨_인터뷰_제목 = "레벨 인터뷰"; + public static final LocalDateTime 레벨_인터뷰_시작일시 = LocalDateTime.of(2022, 8, 7, 13, 0); + public static final LocalDateTime 레벨_인터뷰_종료일시 = LocalDateTime.of(2022, 8, 7, 15, 0); + public static final String 레벨_인터뷰_메모 = "레벨 인터뷰가 예정되어 있습니다."; + public static final ScheduleCreateRequest 레벨_인터뷰_생성_요청 = new ScheduleCreateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, + 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + + public static final String 매고라_제목 = "매고라"; + public static final String 매고라_메모 = "매고라가 예정되어 있습니다."; + + public static Schedule 알록달록_회의(final Category category) { + return new Schedule(category, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); + } + + public static Schedule 알록달록_회식(final Category category) { + return new Schedule(category, 알록달록_회식_제목, 알록달록_회식_시작일시, 알록달록_회식_종료일시, 알록달록_회식_메모); + } + + public static Schedule 레벨_인터뷰(final Category category) { + return new Schedule(category, 레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + } +} diff --git a/backend/src/test/java/com/allog/dallog/common/fixtures/SubscriptionFixtures.java b/backend/src/test/java/com/allog/dallog/common/fixtures/SubscriptionFixtures.java new file mode 100644 index 00000000..97cd231a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/common/fixtures/SubscriptionFixtures.java @@ -0,0 +1,35 @@ +package com.allog.dallog.common.fixtures; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.dto.response.CategoryResponse; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.subscription.domain.Color; +import com.allog.dallog.domain.subscription.domain.Subscription; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; + +public class SubscriptionFixtures { + + public static Subscription 색상1_구독(final Member member, final Category category) { + return new Subscription(member, category, Color.COLOR_1); + } + + public static SubscriptionResponse 색상1_구독_응답(final CategoryResponse categoryResponse) { + return new SubscriptionResponse(1L, categoryResponse, Color.COLOR_1, true); + } + + public static Subscription 색상2_구독(final Member member, final Category category) { + return new Subscription(member, category, Color.COLOR_2); + } + + public static SubscriptionResponse 색상2_구독_응답(final CategoryResponse categoryResponse) { + return new SubscriptionResponse(2L, categoryResponse, Color.COLOR_2, true); + } + + public static Subscription 색상3_구독(final Member member, final Category category) { + return new Subscription(member, category, Color.COLOR_3); + } + + public static SubscriptionResponse 색상3_구독_응답(final CategoryResponse categoryResponse) { + return new SubscriptionResponse(3L, categoryResponse, Color.COLOR_3, true); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/auth/application/AuthServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/auth/application/AuthServiceTest.java new file mode 100644 index 00000000..3de5700a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/auth/application/AuthServiceTest.java @@ -0,0 +1,75 @@ +package com.allog.dallog.domain.auth.application; + +import static com.allog.dallog.common.fixtures.AuthFixtures.MEMBER_이메일; +import static com.allog.dallog.common.fixtures.AuthFixtures.STUB_MEMBER_인증_코드; +import static org.assertj.core.api.Assertions.assertThat; + +import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.domain.auth.dto.request.TokenRequest; +import com.allog.dallog.domain.auth.dto.response.TokenResponse; +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; + +class AuthServiceTest extends ServiceTest { + + @Autowired + private AuthService authService; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("구글 로그인을 위한 링크를 생성한다.") + @Test + void 구글_로그인을_위한_링크를_생성한다() { + // given & when + String redirectUri = "https://dallog.me/oauth"; + String link = authService.generateGoogleLink(redirectUri); + + // then + assertThat(link).isNotEmpty(); + } + + @DisplayName("토큰 생성을 하면 OAuth 서버에서 인증 후 토큰을 반환한다") + @Test + void 토큰_생성을_하면_OAuth_서버에서_인증_후_토큰을_반환한다() { + // given & when + TokenRequest tokenRequest = new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); + TokenResponse actual = authService.generateToken(tokenRequest); + + // then + assertThat(actual.getAccessToken()).isNotEmpty(); + } + + @DisplayName("Authorization Code를 받으면 회원이 데이터베이스에 저장된다.") + @Test + void Authorization_Code를_받으면_회원이_데이터베이스에_저장된다() { + // given + TokenRequest tokenRequest = new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); + authService.generateToken(tokenRequest); + + // when & then + assertThat(memberRepository.existsByEmail(MEMBER_이메일)).isTrue(); + // SutbOAuthClient가 반환하는 OAuthMember의 이메일 + } + + @DisplayName("이미 가입된 회원에 대한 Authorization Code를 전달받으면 추가로 유저가 생성되지 않는다") + @Test + void 이미_가입된_회원에_대한_Authorization_Code를_전달받으면_추가로_유저가_생성되지_않는다() { + // 이미 가입된 유저가 소셜 로그인 버튼을 클릭했을 경우엔 회원가입 과정이 생략되고, 곧바로 access token이 발급되어야 한다. + + // given + TokenRequest tokenRequest = new TokenRequest(STUB_MEMBER_인증_코드, "https://dallog.me/oauth"); + authService.generateToken(tokenRequest); + + // when & then + authService.generateToken(tokenRequest); + List actual = memberRepository.findAll(); + + // then + assertThat(actual).hasSize(1); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/auth/application/JwtTokenProviderTest.java b/backend/src/test/java/com/allog/dallog/domain/auth/application/JwtTokenProviderTest.java new file mode 100644 index 00000000..08b938ef --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/auth/application/JwtTokenProviderTest.java @@ -0,0 +1,63 @@ +package com.allog.dallog.domain.auth.application; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.allog.dallog.domain.auth.exception.InvalidTokenException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class JwtTokenProviderTest { + + private static final String JWT_SECRET_KEY = "A".repeat(32); // Secret Key는 최소 32바이트 이상이어야함. + private static final int JWT_EXPIRE_LENGTH = 3600; + private static final String PAYLOAD = "payload"; + + private final JwtTokenProvider jwtTokenProvider = new JwtTokenProvider(JWT_SECRET_KEY, JWT_EXPIRE_LENGTH); + + @DisplayName("JWT 토큰을 생성한다.") + @Test + void JWT_토큰을_생성한다() { + // given & when + String actual = jwtTokenProvider.createToken(PAYLOAD); + + // then + assertThat(actual.split("\\.")).hasSize(3); + } + + @DisplayName("JWT 토큰의 Payload를 가져온다.") + @Test + void JWT_토큰의_Payload를_가져온다() { + // given + String token = jwtTokenProvider.createToken(PAYLOAD); + + // when + String actual = jwtTokenProvider.getPayload(token); + + // then + assertThat(actual).isEqualTo(PAYLOAD); + } + + @DisplayName("validateToken 메서드는 만료된 토큰을 전달하면 예외를 던진다.") + @Test + void validateToken_메서드는_만료된_토큰을_전달하면_예외를_던진다() { + // given + TokenProvider expiredJwtTokenProvider = new JwtTokenProvider(JWT_SECRET_KEY, 0); + String expiredToken = expiredJwtTokenProvider.createToken(PAYLOAD); + + // when & then + assertThatThrownBy(() -> jwtTokenProvider.validateToken(expiredToken)) + .isInstanceOf(InvalidTokenException.class); + } + + @DisplayName("validateToken 메서드는 유효하지 않은 토큰을 전달하면 예외를 던진다.") + @Test + void validateToken_메서드는_유효하지_않은_토큰을_전달하면_예외를_던진다() { + // given + String malformedToken = "malformed"; + + // when & then + assertThatThrownBy(() -> jwtTokenProvider.validateToken(malformedToken)) + .isInstanceOf(InvalidTokenException.class); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/auth/application/StubTokenProvider.java b/backend/src/test/java/com/allog/dallog/domain/auth/application/StubTokenProvider.java new file mode 100644 index 00000000..a4c8418b --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/auth/application/StubTokenProvider.java @@ -0,0 +1,61 @@ +package com.allog.dallog.domain.auth.application; + +import com.allog.dallog.domain.auth.exception.InvalidTokenException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import javax.crypto.SecretKey; + +public class StubTokenProvider implements TokenProvider { + + private final SecretKey key; + private final long validityInMilliseconds = 0; + + public StubTokenProvider(final String secretKey) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public String createToken(final String payload) { + Date now = new Date(); + Date validity = new Date(now.getTime() + validityInMilliseconds); + + return Jwts.builder() + .setSubject(payload) + .setIssuedAt(now) + .setExpiration(validity) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + @Override + public String getPayload(final String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody() + .getSubject(); + } + + @Override + public void validateToken(final String token) { + try { + Jws claims = Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token); + + claims.getBody() + .getExpiration() + .before(new Date()); + } catch (JwtException | IllegalArgumentException e) { + throw new InvalidTokenException("권한이 없습니다."); + } + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepositoryTest.java b/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepositoryTest.java new file mode 100644 index 00000000..3a86801a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenRepositoryTest.java @@ -0,0 +1,91 @@ +package com.allog.dallog.domain.auth.domain; + +import static com.allog.dallog.common.fixtures.MemberFixtures.매트; +import static com.allog.dallog.common.fixtures.OAuthTokenFixtures.REFRESH_TOKEN; +import static org.assertj.core.api.Assertions.assertThat; + +import com.allog.dallog.common.annotation.RepositoryTest; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class OAuthTokenRepositoryTest extends RepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private OAuthTokenRepository oAuthTokenRepository; + + @DisplayName("member id의 OAuthToken이 존재할 경우 true를 반환한다.") + @Test + void member_id의_OAuthToken이_존재할_경우_true를_반환한다() { + // given + Member 매트 = memberRepository.save(매트()); + + String refreshToken = REFRESH_TOKEN; + oAuthTokenRepository.save(new OAuthToken(매트, refreshToken)); + + // when + boolean actual = oAuthTokenRepository.existsByMemberId(매트.getId()); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("member id의 OAuthToken이 존재하지 않을 경우 false를 반환한다.") + @Test + void member_id의_OAuthToken이_존재하지_않을_경우_false를_반환한다() { + // given & when + boolean actual = oAuthTokenRepository.existsByMemberId(0L); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("member id의 OAuthToken이 존재할 경우 Optional은 비어있지 않다.") + @Test + void member_id의_OAuthToken이_존재할_경우_Optional은_비어있지_않다() { + // given + Member 매트 = memberRepository.save(매트()); + + String refreshToken = REFRESH_TOKEN; + oAuthTokenRepository.save(new OAuthToken(매트, refreshToken)); + + // when + Optional actual = oAuthTokenRepository.findByMemberId(매트.getId()); + + // then + assertThat(actual).isNotEmpty(); + } + + @DisplayName("member id의 OAuthToken이 존재하지 않을 경우 비어있다.") + @Test + void member_id의_OAuthToken이_존재하지_않을_경우_비어있다() { + // given & when + Optional actual = oAuthTokenRepository.findByMemberId(0L); + + // then + assertThat(actual).isEmpty(); + } + + @DisplayName("member id의 OAuthToke을 삭제한다.") + @Test + void member_id의_OAuthToken을_삭제한다() { + // given + Member 매트 = memberRepository.save(매트()); + + String refreshToken = REFRESH_TOKEN; + oAuthTokenRepository.save(new OAuthToken(매트, refreshToken)); + + // when + oAuthTokenRepository.deleteByMemberId(매트.getId()); + + // then + Optional actual = oAuthTokenRepository.findByMemberId(매트.getId()); + assertThat(actual).isEmpty(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenTest.java b/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenTest.java new file mode 100644 index 00000000..f7b7c309 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/auth/domain/OAuthTokenTest.java @@ -0,0 +1,40 @@ +package com.allog.dallog.domain.auth.domain; + +import static com.allog.dallog.common.fixtures.MemberFixtures.매트; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.allog.dallog.domain.member.domain.Member; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class OAuthTokenTest { + + @DisplayName("OAuth token을 생성한다.") + @Test + void OAuth_token을_생성한다() { + // given + Member 매트 = 매트(); + String refreshToken = "adasaegsfadasdasfgfgrgredksgdffa"; + + // when & then + assertDoesNotThrow(() -> new OAuthToken(매트, refreshToken)); + } + + @DisplayName("refresh token을 교체한다.") + @Test + void refresh_token을_교체한다() { + // given + Member 매트 = 매트(); + String refreshToken = "adasaegsfadasdasfgfgrgredksgdffa"; + OAuthToken oAuthToken = new OAuthToken(매트, refreshToken); + + String updatedRefreshToken = "dfgsbnskjglnafgkajfnakfjgngejlkrqgn"; + + // when + oAuthToken.change(updatedRefreshToken); + + // then + assertThat(oAuthToken.getRefreshToken()).isEqualTo(updatedRefreshToken); + } +} 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 new file mode 100644 index 00000000..36e4380a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/application/CategoryServiceTest.java @@ -0,0 +1,455 @@ +package com.allog.dallog.domain.category.application; + +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.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.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.레벨_인터뷰_생성_요청; +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 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.CategoryFixtures; +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.dto.request.CategoryCreateRequest; +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.InvalidCategoryException; +import com.allog.dallog.domain.category.exception.NoSuchCategoryException; +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; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionResponse; +import com.allog.dallog.domain.subscription.exception.NoSuchSubscriptionException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; + +class CategoryServiceTest extends ServiceTest { + + @Autowired + private CategoryService categoryService; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private SubscriptionService subscriptionService; + + @Autowired + private ScheduleService scheduleService; + + @Autowired + private MemberService memberService; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("새로운 카테고리를 생성한다.") + @Test + void 새로운_카테고리를_생성한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + // when + CategoryResponse response = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // then + assertThat(response.getName()).isEqualTo(공통_일정_이름); + } + + @DisplayName("새로운 개인 카테고리를 생성한다.") + @Test + void 새로운_개인_카테고리를_생성한다() { + // given + Member 후디 = memberRepository.save(후디()); + + // when + CategoryResponse 내_일정_응답 = categoryService.save(후디.getId(), 내_일정_생성_요청); + Category 내_일정 = categoryRepository.findById(내_일정_응답.getId()).get(); + + // then + assertAll(() -> { + assertThat(내_일정.getName()).isEqualTo(CategoryFixtures.내_일정_이름); + assertThat(내_일정.isPersonal()).isTrue(); + }); + } + + @DisplayName("새로운 카테고리를 생성 할 떄 이름이 공백이거나 길이가 20을 초과하는 경우 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"", "일이삼사오육칠팔구십일이삼사오육칠팔구십일", "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 카테고리"}) + void 새로운_카테고리를_생성_할_때_이름이_공백이거나_길이가_20을_초과하는_경우_예외를_던진다(final String name) { + // given + CategoryCreateRequest request = new CategoryCreateRequest(name, NORMAL); + Member 관리자 = memberRepository.save(관리자()); + + // when & then + assertThatThrownBy(() -> categoryService.save(관리자.getId(), request)) + .isInstanceOf(InvalidCategoryException.class); + } + + @DisplayName("새로운 외부 카테고리를 생성한다.") + @Test + void 새로운_외부_카테고리를_생성한다() { + // given + Member 후디 = memberRepository.save(후디()); + + // when + CategoryResponse 후디_대한민국_공휴일_카테고리_응답 = categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청); + Category 후디_대한민국_공휴일_카테고리 = categoryRepository.findById(후디_대한민국_공휴일_카테고리_응답.getId()).get(); + + // then + assertAll(() -> { + assertThat(후디_대한민국_공휴일_카테고리.getName()).isEqualTo(대한민국_공휴일_이름); + assertThat(후디_대한민국_공휴일_카테고리.getCategoryType()).isEqualTo(GOOGLE); + }); + } + + @DisplayName("중복된 외부 카테고리를 저장하는 경우 예외를 던진다.") + @Test + void 중복된_외부_카테고리를_저장하는_경우_예외를_던진다() { + // given + Member 후디 = memberRepository.save(후디()); + + // when + categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청); + + // then + assertThatThrownBy(() -> categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청)) + .isInstanceOf(DuplicatedExternalCategoryException.class); + } + + @DisplayName("페이지와 제목을 받아 해당하는 구간의 카테고리를 가져온다.") + @Test + void 페이지와_제목을_받아_해당하는_구간의_카테고리를_가져온다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Long 관리자_ID = 관리자.getId(); + categoryService.save(관리자_ID, 공통_일정_생성_요청); + categoryService.save(관리자_ID, BE_일정_생성_요청); + categoryService.save(관리자_ID, FE_일정_생성_요청); + categoryService.save(관리자_ID, 매트_아고라_생성_요청); + categoryService.save(관리자_ID, 후디_JPA_스터디_생성_요청); + + PageRequest request = PageRequest.of(0, 3); + + // when + CategoriesResponse response = categoryService.findNormalByName("일", request); + + // then + assertThat(response.getCategories()) + .hasSize(3) + .extracting(CategoryResponse::getName) + .contains(공통_일정_이름, BE_일정_이름, FE_일정_이름); + } + + @DisplayName("개인 카테고리는 전체 조회 대상에서 제외된다.") + @Test + void 개인_카테고리는_전체_조회_대상에서_제외된다() { + // given + MemberResponse 후디 = memberService.save(후디()); // 후디의 개인 카테고리가 생성된다 + MemberResponse 리버 = memberService.save(리버()); // 리버의 개인 카테고리가 생성된다 + categoryService.save(후디.getId(), 내_일정_생성_요청); + + // when + CategoriesResponse response = categoryService.findNormalByName("", PageRequest.of(0, 10)); + + // then + assertThat(response.getCategories()).hasSize(0); + } + + @DisplayName("회원 id와 페이지를 기반으로 카테고리를 가져온다.") + @Test + void 회원_id와_페이지를_기반으로_카테고리를_가져온다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Long 관리자_ID = 관리자.getId(); + categoryService.save(관리자_ID, 공통_일정_생성_요청); + categoryService.save(관리자_ID, BE_일정_생성_요청); + categoryService.save(관리자_ID, FE_일정_생성_요청); + categoryService.save(관리자_ID, 매트_아고라_생성_요청); + categoryService.save(관리자_ID, 후디_JPA_스터디_생성_요청); + + PageRequest request = PageRequest.of(1, 2); + + // when + CategoriesResponse response = categoryService.findMineByName(관리자_ID, "", request); + + // then + assertThat(response.getCategories()) + .hasSize(2) + .extracting(CategoryResponse::getName) + .contains(FE_일정_이름, 매트_아고라_이름); + } + + @DisplayName("회원 id와 제목과 페이지를 받아 해당하는 구간의 카테고리를 가져온다.") + @Test + void 회원_id와_제목과_페이지를_받아_해당하는_구간의_카테고리를_가져온다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Long 관리자_ID = 관리자.getId(); + categoryService.save(관리자_ID, 공통_일정_생성_요청); + categoryService.save(관리자_ID, BE_일정_생성_요청); + categoryService.save(관리자_ID, FE_일정_생성_요청); + categoryService.save(관리자_ID, 매트_아고라_생성_요청); + categoryService.save(관리자_ID, 후디_JPA_스터디_생성_요청); + + PageRequest request = PageRequest.of(0, 3); + + // when + CategoriesResponse response = categoryService.findMineByName(관리자_ID, "일", request); + + // then + assertThat(response.getCategories()) + .hasSize(3) + .extracting(CategoryResponse::getName) + .contains(공통_일정_이름, BE_일정_이름, FE_일정_이름); + } + + @DisplayName("id를 통해 카테고리를 단건 조회한다.") + @Test + void id를_통해_카테고리를_단건_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // when & then + CategoryResponse 조회한_공통_일정 = categoryService.findById(공통_일정.getId()); + + assertAll(() -> { + assertThat(조회한_공통_일정.getId()).isEqualTo(공통_일정.getId()); + assertThat(조회한_공통_일정.getName()).isEqualTo(공통_일정.getName()); + }); + } + + @DisplayName("id를 통해 카테고리를 단건 조회할 때 카테고리가 존재하지 않다면 예외가 발생한다.") + @Test + void id를_통해_카테고리를_단건_조회할_때_카테고리가_존재하지_않다면_예외가_발생한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // when & then + assertThatThrownBy(() -> categoryService.findById(공통_일정.getId() + 1)) + .isInstanceOf(NoSuchCategoryException.class); + } + + @DisplayName("회원과 카테고리 id를 통해 카테고리를 수정한다.") + @Test + void 회원과_카테고리_id를_통해_카테고리를_수정한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + String 우테코_공통_일정_이름 = "우테코 공통 일정"; + CategoryUpdateRequest categoryUpdateRequest = new CategoryUpdateRequest(우테코_공통_일정_이름); + + // when + categoryService.update(관리자.getId(), 공통_일정.getId(), categoryUpdateRequest); + Category category = categoryService.getCategory(공통_일정.getId()); + + //then + assertThat(category.getName()).isEqualTo(우테코_공통_일정_이름); + } + + @DisplayName("존재하지 않는 카테고리를 수정할 경우 예외를 던진다.") + @Test + void 존재하지_않는_카테고리를_수정할_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryUpdateRequest categoryUpdateRequest = new CategoryUpdateRequest(BE_일정_이름); + + // when & then + assertThatThrownBy(() -> categoryService.update(관리자.getId(), 공통_일정.getId() + 1, categoryUpdateRequest)) + .isInstanceOf(NoSuchCategoryException.class); + } + + @DisplayName("자신이 만들지 않은 카테고리를 수정할 경우 예외를 던진다.") + @Test + void 자신이_만들지_않은_카테고리를_수정할_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Member 매트 = memberRepository.save(매트()); + + CategoryResponse savedCategory = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryUpdateRequest categoryUpdateRequest = new CategoryUpdateRequest("우테코 공통 일정"); + + // when & then + assertThatThrownBy(() -> categoryService.update(매트.getId(), savedCategory.getId(), categoryUpdateRequest)) + .isInstanceOf(NoPermissionException.class); + } + + @DisplayName("회원과 카테고리 id를 통해 카테고리를 삭제한다.") + @Test + void 회원과_카테고리_id를_통해_카테고리를_삭제한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // when + categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + + //then + assertThatThrownBy(() -> categoryService.getCategory(공통_일정.getId())) + .isInstanceOf(NoSuchCategoryException.class); + } + + @DisplayName("존재하지 않는 카테고리를 삭제할 경우 예외를 던진다.") + @Test + void 존재하지_않는_카테고리를_삭제할_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // when & then + assertThatThrownBy(() -> categoryService.deleteById(관리자.getId(), 공통_일정.getId() + 1)) + .isInstanceOf(NoSuchCategoryException.class); + } + + @DisplayName("자신이 만들지 않은 카테고리를 삭제할 경우 예외를 던진다.") + @Test + void 자신이_만들지_않은_카테고리를_삭제할_경우_예외를_던진다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Member 매트 = memberRepository.save(매트()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + // when & then + assertThatThrownBy( + () -> categoryService.deleteById(매트.getId(), 공통_일정.getId())) + .isInstanceOf(NoPermissionException.class); + } + + @DisplayName("카테고리 삭제 시 연관된 일정 엔티티도 모두 제거된다.") + @Test + void 카테고리_삭제_시_연관된_일정_엔티티도_모두_제거된다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + ScheduleResponse 알록달록_회식 = scheduleService.save(관리자.getId(), 공통_일정.getId(), 알록달록_회식_생성_요청); + ScheduleResponse 레벨_인터뷰 = scheduleService.save(관리자.getId(), 공통_일정.getId(), 레벨_인터뷰_생성_요청); + + // when + categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + + // then + assertAll(() -> { + assertThatThrownBy(() -> scheduleService.findById(알록달록_회식.getId())) + .isInstanceOf(NoSuchScheduleException.class); + assertThatThrownBy(() -> scheduleService.findById(레벨_인터뷰.getId())) + .isInstanceOf(NoSuchScheduleException.class); + }); + } + + @DisplayName("카테고리 삭제 시 연관된 구독 엔티티도 모두 제거된다.") + @Test + void 카테고리_삭제_시_연관된_구독_엔티티도_모두_제거된다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + + Member 후디 = memberRepository.save(후디()); + SubscriptionResponse 구독 = subscriptionService.save(후디.getId(), 공통_일정.getId()); + + // when + categoryService.deleteById(관리자.getId(), 공통_일정.getId()); + + // then + assertThatThrownBy(() -> subscriptionService.findById(구독.getId())) + .isInstanceOf(NoSuchSubscriptionException.class); + } + + @DisplayName("개인 카테고리는 삭제할 수 없다.") + @Test + void 개인_카테고리는_삭제할_수_없다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 내_일정 = categoryService.save(관리자.getId(), 내_일정_생성_요청); + subscriptionService.save(관리자.getId(), 내_일정.getId()); + + // when & then + assertThatThrownBy(() -> categoryService.deleteById(관리자.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 외부_캘린더의_카테고리를_삭제한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + CategoryResponse 우아한테크코스_외부_일정 = categoryService.save(관리자.getId(), 우아한테크코스_외부_일정_생성_요청); + + // when + categoryService.deleteById(관리자.getId(), 우아한테크코스_외부_일정.getId()); + + // then + assertThatThrownBy(() -> categoryService.findById(우아한테크코스_외부_일정.getId())) + .isInstanceOf(NoSuchCategoryException.class); + } +} 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 new file mode 100644 index 00000000..c2be8d78 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryRepositoryTest.java @@ -0,0 +1,188 @@ +package com.allog.dallog.domain.category.domain; + +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.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.후디_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 org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import com.allog.dallog.common.annotation.RepositoryTest; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; +import java.util.List; +import java.util.Objects; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; + +class CategoryRepositoryTest extends RepositoryTest { + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("카테고리 이름과 타입과 페이징을 활용하여 해당하는 카테고리를 조회한다.") + @Test + void 카테고리_이름과_타입과_페이징을_활용하여_해당하는_카테고리를_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + categoryRepository.save(공통_일정(관리자)); + categoryRepository.save(BE_일정(관리자)); + categoryRepository.save(FE_일정(관리자)); + categoryRepository.save(매트_아고라(관리자)); + categoryRepository.save(후디_JPA_스터디(관리자)); + categoryRepository.save(내_일정(관리자)); + categoryRepository.save(우아한테크코스_일정(관리자)); + + PageRequest pageRequest = PageRequest.of(0, 5); + + // when + Slice actual = categoryRepository.findByNameContainingAndCategoryType("일", NORMAL, pageRequest); + + // then + assertThat(actual.getContent()).hasSize(3) + .extracting(Category::getName) + .contains(공통_일정_이름, BE_일정_이름, FE_일정_이름); + } + + @DisplayName("카테고리 이름 검색 결과가 존재하지 않는 경우 아무것도 조회 하지 않는다.") + @Test + void 카테고리_이름_검색_결과가_존재하지_않는_경우_아무것도_조회_하지_않는다() { + // given + Member 관리자 = memberRepository.save(관리자()); + categoryRepository.save(공통_일정(관리자)); + categoryRepository.save(BE_일정(관리자)); + categoryRepository.save(FE_일정(관리자)); + categoryRepository.save(매트_아고라(관리자)); + categoryRepository.save(후디_JPA_스터디(관리자)); + + PageRequest pageRequest = PageRequest.of(0, 5); + + // when + Slice actual = categoryRepository.findByNameContainingAndCategoryType("파랑", NORMAL, pageRequest); + + // then + assertThat(actual.getContent()).hasSize(0); + } + + @DisplayName("특정 멤버가 생성한 카테고리를 카테고리 이름과 페이징을 통해 조회한다.") + @Test + void 특정_멤버가_생성한_카테고리를_카테고리_이름과_페이징을_통해_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + categoryRepository.save(공통_일정(관리자)); + categoryRepository.save(BE_일정(관리자)); + categoryRepository.save(FE_일정(관리자)); + categoryRepository.save(매트_아고라(관리자)); + categoryRepository.save(후디_JPA_스터디(관리자)); + + Member 후디 = memberRepository.save(후디()); + categoryRepository.save(후디_JPA_스터디(후디)); + + PageRequest pageRequest = PageRequest.of(0, 8); + + // when + Slice categories = categoryRepository.findByMemberIdLikeCategoryName(관리자.getId(), "일", pageRequest); + + // then + assertAll(() -> { + assertThat(categories.getContent()).hasSize(3) + .extracting(Category::getName) + .containsExactlyInAnyOrder(공통_일정_이름, BE_일정_이름, FE_일정_이름); + assertThat( + categories.getContent().stream() + .map(Category::getCreatedAt) + .allMatch(Objects::nonNull)) + .isTrue(); + }); + } + + @DisplayName("특정 멤버가 생성한 카테고리를 조회한다.") + @Test + void 특정_멤버가_생성한_카테고리를_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + categoryRepository.save(공통_일정(관리자)); + categoryRepository.save(BE_일정(관리자)); + categoryRepository.save(FE_일정(관리자)); + + Member 후디 = memberRepository.save(후디()); + categoryRepository.save(후디_JPA_스터디(후디)); + + // when + List categories = categoryRepository.findByMemberId(관리자.getId()); + + // then + assertAll(() -> { + assertThat(categories).hasSize(3) + .extracting(Category::getName) + .containsExactlyInAnyOrder(공통_일정_이름, BE_일정_이름, FE_일정_이름); + assertThat( + categories.stream() + .map(Category::getCreatedAt) + .allMatch(Objects::nonNull)) + .isTrue(); + }); + } + + + @DisplayName("카테고리 id와 회원의 id가 모두 일치하는 카테고리가 있으면 true를 반환한다.") + @Test + void 카테고리_id와_회원의_id가_모두_일치하는_카테고리가_있으면_true를_반환한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + + // when + boolean actual = categoryRepository.existsByIdAndMemberId(공통_일정.getId(), 관리자.getId()); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("카테고리 id와 회원의 id가 모두 일치하는 카테고리가 없으면 false를 반환한다.") + @Test + void 카테고리_id와_회원의_id가_모두_일치하는_카테고리가_없으면_false를_반환한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + + // when + boolean actual = categoryRepository.existsByIdAndMemberId(공통_일정.getId(), 0L); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("특정 회원이 만든 카테고리를 모두 삭제한다") + @Test + void 특정_회원이_만든_카테고리를_모두_삭제한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + categoryRepository.save(공통_일정(관리자)); + categoryRepository.save(BE_일정(관리자)); + + PageRequest pageRequest = PageRequest.of(0, 2); + + // when + categoryRepository.deleteByMemberId(관리자.getId()); + + // then + assertThat(categoryRepository.findByMemberIdLikeCategoryName(관리자.getId(), "", pageRequest)) + .hasSize(0); + } +} 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 new file mode 100644 index 00000000..815935db --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTest.java @@ -0,0 +1,100 @@ +package com.allog.dallog.domain.category.domain; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; +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 org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.allog.dallog.domain.category.exception.InvalidCategoryException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class CategoryTest { + + @DisplayName("카테고리를 생성한다.") + @Test + void 카테고리를_생성한다() { + // given + String name = "BE 공식일정"; + + // when & then + assertDoesNotThrow(() -> new Category(name, 후디())); + } + + @DisplayName("카테고리 이름이 공백인 경우 예외를 던진다.") + @Test + void 카테고리_이름이_공백인_경우_예외를_던진다() { + // given + String name = ""; + + // when & then + assertThatThrownBy(() -> new Category(name, 후디())) + .isInstanceOf(InvalidCategoryException.class); + } + + @DisplayName("카테고리 이름의 길이가 20을 초과하는 경우 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"일이삼사오육칠팔구십일이삼사오육칠팔구십일", + "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 카테고리"}) + void 카테고리_이름의_길이가_20을_초과하는_경우_예외를_던진다(final String name) { + // given & when & then + assertThatThrownBy(() -> new Category(name, 후디())) + .isInstanceOf(InvalidCategoryException.class); + } + + @DisplayName("개인 카테고리의 이름을 수정하는 경우 예외를 던진다.") + @Test + void 개인_카테고리의_이름을_수정하는_경우_예외를_던진다() { + // given + Category 내_일정 = 내_일정(관리자()); + + // when & then + assertThatThrownBy(() -> 내_일정.changeName("바꿀 이름")) + .isInstanceOf(InvalidCategoryException.class); + } + + @DisplayName("제공된 멤버의 ID와 카테고리를 생성한 멤버의 ID가 일치하지 않으면 false를 반환한다.") + @Test + void 제공된_멤버의_ID와_카테고리를_생성한_멤버의_ID가_일치하지_않으면_false를_반환한다() { + // given + Category BE_일정 = BE_일정(관리자()); + + // when + boolean actual = BE_일정.isCreator(999L); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("개인 카테고리면 true를 반환한다.") + @Test + void 개인_카테고리면_true를_반환한다() { + // given + Category 내_일정 = 내_일정(관리자()); + + // when + boolean actual = 내_일정.isPersonal(); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("외부 연동 카테고리면 true를 반환한다.") + @Test + void 외부_연동_카테고리면_true를_반환한다() { + // given + Category 우아한테크코스_일정 = 우아한테크코스_일정(관리자()); + + // when + boolean actual = 우아한테크코스_일정.isExternal(); + + // then + assertThat(actual).isTrue(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTypeTest.java b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTypeTest.java new file mode 100644 index 00000000..43572c79 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/category/domain/CategoryTypeTest.java @@ -0,0 +1,36 @@ +package com.allog.dallog.domain.category.domain; + +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.domain.category.exception.NoSuchCategoryException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class CategoryTypeTest { + + @DisplayName("카테고리 종류를 가져온다.") + @ParameterizedTest + @EnumSource + void 카테고리_종류를_가져온다(final CategoryType categoryType) { + // given & when & then + assertAll(() -> { + assertThat(CategoryType.from(categoryType.name())).isEqualTo(categoryType); + assertThat(CategoryType.from(categoryType.name().toLowerCase())).isEqualTo(categoryType); + }); + } + + @DisplayName("존재하지 않는 카테고리 종류인 경우 예외를 던진다.") + @Test + void 존재하지_않는_카테고리_종류인_경우_예외를_던진다() { + // given + String notExistingCategoryType = "존재하지 않는 카테고리 종류"; + + // when & then + assertThatThrownBy(() -> CategoryType.from(notExistingCategoryType)) + .isInstanceOf(NoSuchCategoryException.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 new file mode 100644 index 00000000..52762016 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CalendarServiceTest.java @@ -0,0 +1,192 @@ +package com.allog.dallog.domain.composition.application; + +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분; +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.auth.domain.OAuthToken; +import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +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; +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; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class CalendarServiceTest extends ServiceTest { + + @Autowired + private CalendarService calendarService; + + @Autowired + private MemberService memberService; + + @Autowired + private CategoryService categoryService; + + @Autowired + private OAuthTokenRepository oAuthTokenRepository; + + @Autowired + private ExternalCategoryDetailRepository externalCategoryDetailRepository; + + @Autowired + private SubscriptionService subscriptionService; + + @Autowired + private ScheduleService scheduleService; + + @DisplayName("시작일시와 종료일시로 유저의 캘린더를 일정 유형에 따라 분류하고 정렬하여 반환한다.") + @Test + void 시작일시와_종료일시로_유저의_캘린더를_일정_유형에_따라_분류하고_정렬하여_반환한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + oAuthTokenRepository.save(new OAuthToken(memberService.getMember(후디.getId()), "wegwefaasdasdasda")); + + CategoryResponse BE_일정_응답 = categoryService.save(후디.getId(), BE_일정_생성_요청); + Category BE_일정 = categoryService.getCategory(BE_일정_응답.getId()); + + subscriptionService.save(후디.getId(), BE_일정.getId()); + + /* 장기간 일정 */ + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("장기간 첫번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_8월_15일_14시_0분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("장기간 두번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("장기간 세번째", 날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("장기간 네번째", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_15일_16시_0분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("장기간 다섯번째", 날짜_2022년_7월_31일_0시_0분, 날짜_2022년_8월_15일_17시_0분, "")); + + /* 종일 일정 */ + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("종일 첫번째", 날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("종일 두번째", 날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("종일 세번째", 날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분, "")); + + /* 몇시간 일정 */ + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("몇시간 첫번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("몇시간 두번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("몇시간 세번째", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(후디.getId(), BE_일정.getId(), + new ScheduleCreateRequest("몇시간 네번째", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_18시_0분, "")); + + CategoryResponse 우아한테크코스_외부_일정_응답 = categoryService.save(후디.getId(), 우아한테크코스_외부_일정_생성_요청); + Category 우아한테크코스 = categoryService.getCategory(우아한테크코스_외부_일정_응답.getId()); + externalCategoryDetailRepository.save(new ExternalCategoryDetail(우아한테크코스, "dfggsdfasdasadsgs")); + + subscriptionService.save(후디.getId(), 우아한테크코스.getId()); + + // when + MemberScheduleResponses memberScheduleResponses = calendarService.findSchedulesByMemberId(후디.getId(), + 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 + void 카테고리를_구독하는_유저들의_모든_내부_일정을_가져온다() { + // given + MemberResponse 관리자 = memberService.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + + /* 카테고리에 일정 추가 */ + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); + scheduleService.save(관리자.getId(), FE_일정.getId(), + new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleService.save(관리자.getId(), 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()); + subscriptionService.save(매트.getId(), FE_일정.getId()); + subscriptionService.save(리버.getId(), FE_일정.getId()); + + // when + List actual = calendarService.getSchedulesOfSubscriberIds( + List.of(후디.getId(), 파랑.getId(), 매트.getId(), 리버.getId()), + 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"); + } +} 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 new file mode 100644 index 00000000..975e3fee --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/CategorySubscriptionServiceTest.java @@ -0,0 +1,58 @@ +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 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.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 CategorySubscriptionServiceTest extends ServiceTest { + + @Autowired + private CategorySubscriptionService categorySubscriptionService; + + @Autowired + private MemberService memberService; + + @Autowired + private SubscriptionService subscriptionService; + + @DisplayName("카테고리 생성 시 자동으로 구독한다.") + @Test + void 카테고리_생성_시_자동으로_구독한다() { + // given + MemberResponse 파랑 = memberService.save(파랑()); + + // when + categorySubscriptionService.save(파랑.getId(), 공통_일정_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑.getId()); + + // then + assertThat(subscriptions).hasSize(1); + } + + @DisplayName("외부 카테고리 생성 시 자동으로 구독한다.") + @Test + void 외부_카테고리_생성_시_자동으로_구독한다() { + // given + MemberResponse 파랑 = memberService.save(파랑()); + + // when + categorySubscriptionService.save(파랑.getId(), 대한민국_공휴일_생성_요청); + + List subscriptions = subscriptionService.getAllByMemberId(파랑.getId()); + + // then + assertThat(subscriptions).hasSize(1); + } +} 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 new file mode 100644 index 00000000..90200807 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/RegisterServiceTest.java @@ -0,0 +1,77 @@ +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); + }); + } +} 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 new file mode 100644 index 00000000..f8294715 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/composition/application/SchedulerServiceTest.java @@ -0,0 +1,186 @@ +package com.allog.dallog.domain.composition.application; + +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분; +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 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.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; +import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +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; +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; + +class SchedulerServiceTest extends ServiceTest { + + @Autowired + private SchedulerService schedulerService; + + @Autowired + private ScheduleService scheduleService; + + @Autowired + private CategoryService categoryService; + + @Autowired + private SubscriptionService subscriptionService; + + @Autowired + private MemberService memberService; + + @DisplayName("비어있는 기간 목록을 반환한다.") + @Test + void 비어있는_기간_목록을_반환한다() { + // given + /* 관리자 및 카테고리 생성 */ + MemberResponse 관리자 = memberService.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + + /* 카테고리에 일정 추가 */ + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); + scheduleService.save(관리자.getId(), FE_일정.getId(), + new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleService.save(관리자.getId(), 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()); + subscriptionService.save(매트.getId(), FE_일정.getId()); + subscriptionService.save(리버.getId(), FE_일정.getId()); + + // when + List actual = schedulerService.getAvailablePeriods(공통_일정.getId(), + new DateRangeRequest("2022-07-01T00:00", "2022-08-31T00:00")); + + // then + assertAll(() -> { + 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년_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)); + }); + } + + @DisplayName("체크하지 않은 구독은 일정 산정 대상에서 제외된다.") + @Test + void 체크하지_않은_구독은_일정_산정_대상에서_제외된다() { + // given + /* 관리자 및 카테고리 생성 */ + MemberResponse 관리자 = memberService.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + + /* 카테고리에 일정 추가 */ + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 1", 날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 2", 날짜_2022년_7월_10일_11시_59분, 날짜_2022년_7월_15일_16시_0분, "")); + scheduleService.save(관리자.getId(), 공통_일정.getId(), + new ScheduleCreateRequest("공통 일정 3", 날짜_2022년_7월_16일_16시_0분, 날짜_2022년_7월_16일_16시_1분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 1", 날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 2", 날짜_2022년_7월_16일_20시_0분, 날짜_2022년_7월_20일_0시_0분, "")); + scheduleService.save(관리자.getId(), BE_일정.getId(), + new ScheduleCreateRequest("백엔드 일정 3", 날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분, "")); + scheduleService.save(관리자.getId(), FE_일정.getId(), + new ScheduleCreateRequest("프론트엔드 일정 1", 날짜_2022년_7월_27일_11시_59분, 날짜_2022년_7월_31일_0시_0분, "")); + scheduleService.save(관리자.getId(), 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(), + new SubscriptionUpdateRequest(Color.COLOR_1, false)); + subscriptionService.update(리버_FE_일정_구독.getId(), 리버.getId(), + new SubscriptionUpdateRequest(Color.COLOR_1, false)); + + // when + List actual = schedulerService.getAvailablePeriods(공통_일정.getId(), + new DateRangeRequest("2022-07-01T00:00", "2022-08-31T00:00")); + + // then + assertAll(() -> { + assertThat(actual).hasSize(6); + 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분); + 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분, + LocalDateTime.of(2022, 8, 31, 0, 0)); + }); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarServiceTest.java b/backend/src/test/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarServiceTest.java new file mode 100644 index 00000000..6357a0a2 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/externalcalendar/application/ExternalCalendarServiceTest.java @@ -0,0 +1,40 @@ +package com.allog.dallog.domain.externalcalendar.application; + +import static com.allog.dallog.common.fixtures.OAuthTokenFixtures.OAUTH_TOKEN; +import static org.assertj.core.api.Assertions.assertThat; + +import com.allog.dallog.common.annotation.ServiceTest; +import com.allog.dallog.common.fixtures.MemberFixtures; +import com.allog.dallog.domain.auth.domain.OAuthTokenRepository; +import com.allog.dallog.domain.externalcalendar.dto.ExternalCalendarsResponse; +import com.allog.dallog.domain.member.domain.Member; +import com.allog.dallog.domain.member.domain.MemberRepository; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class ExternalCalendarServiceTest extends ServiceTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private OAuthTokenRepository oAuthTokenRepository; + + @Autowired + private ExternalCalendarService externalCalendarService; + + @DisplayName("member id를 활용하여 외부 캘린더 리스트를 조회한다.") + @Test + void member_id를_활용하여_외부_캘린더_리스트를_조회한다() { + // given + Member 매트 = memberRepository.save(MemberFixtures.매트()); + oAuthTokenRepository.save(OAUTH_TOKEN(매트)); + + // when + ExternalCalendarsResponse actual = externalCalendarService.findByMemberId(매트.getId()); + + // then + assertThat(actual.getExternalCalendars()).hasSize(3); + } +} 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 new file mode 100644 index 00000000..00d341ea --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/dao/IntegrationScheduleDaoTest.java @@ -0,0 +1,358 @@ +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.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; + +@SpringBootTest +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/domain/integrationschedule/domain/IntegrationScheduleTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java new file mode 100644 index 00000000..213af9a4 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationScheduleTest.java @@ -0,0 +1,131 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +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 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; + +class IntegrationScheduleTest { + + @DisplayName("일정을 생성한다.") + @Test + void 일정을_생성한다() { + // given + String id = "1"; + Long categoryId = 1L; + + // when & then + assertDoesNotThrow( + () -> new IntegrationSchedule(id, categoryId, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모, + CategoryType.NORMAL.name())); + } + + @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 다르면 true를 반환한다.") + @Test + void LongTerm인지_확인_할_떄_일정의_시작일시와_종료일시가_다르면_true를_반환한다() { + // given + String id = "1"; + 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()); + + // when + boolean actual = integrationSchedule.isLongTerms(); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("LongTerm인지 확인 할 떄, 일정의 시작일시와 종료일시가 같으면 false를 반환한다.") + @Test + void LongTerm인지_확인_할_때_일정의_시작일시와_종료일시가_다르면_false를_반환한다() { + // given + String id = "1"; + 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()); + + // 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, 1, 23, 59), 알록달록_회의_메모, CategoryType.NORMAL.name()); + + // when + boolean actual = integrationSchedule.isAllDays(); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("AllDays인지 확인 할 떄, 일정의 시작일시와 종료일시가 같지만 자정이 아니면 false를 반환한다.") + @Test + 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), 알록달록_회의_메모, CategoryType.NORMAL.name()); + + // when + boolean actual = integrationSchedule.isAllDays(); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("FewHours인지 확인 할 떄, 일정의 시작일시와 종료일시가 같고 자정이 아니면 true를 반환한다.") + @Test + 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), 알록달록_회의_메모, CategoryType.NORMAL.name()); + + // when + boolean actual = integrationSchedule.isFewHours(); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("FewHours인지 확인 할 떄, 일정의 시작일시와 종료일시가 같지만 자정이면 false를 반환한다.") + @Test + 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), 알록달록_회의_메모, CategoryType.NORMAL.name()); + + // when + boolean actual = integrationSchedule.isFewHours(); + + // then + assertThat(actual).isFalse(); + } +} 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 new file mode 100644 index 00000000..6e0a05f1 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/IntegrationSchedulesTest.java @@ -0,0 +1,98 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +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; + +class IntegrationSchedulesTest { + + @DisplayName("겹치는 일정이 하나도 없을 때, 일정 시작일시가 빠른 순서대로 정렬된다.") + @Test + void 겹치는_일정이_하나도_없을_때_일정_시작일시가_빠른_순서대로_정렬된다() { + // given + 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()); + + IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", + LocalDateTime.of(2022, 3, 3, 0, 0), + 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.NORMAL.name()); + + // when + IntegrationSchedules integrationSchedules = new IntegrationSchedules(); + integrationSchedules.add(세번째로_정렬되어야_하는_일정); + integrationSchedules.add(두번째로_정렬되어야_하는_일정); + integrationSchedules.add(첫번째로_정렬되어야_하는_일정); + + // then + assertThat(integrationSchedules.getSortedValues()) + .extracting(IntegrationSchedule::getTitle) + .containsExactly("일정1", "일정2", "일정3"); + } + + @DisplayName("일정의 시작일시가 겹친다면, 일정 종료일시가 느린 순서대로 정렬된다.") + @Test + void 일정의_시작일시가_겹친다면_일정_종료일시가_느린_순서대로_정렬된다() { + // given + 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()); + + IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "일정2", + LocalDateTime.of(2022, 3, 1, 0, 0), + 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.NORMAL.name()); + + // when + IntegrationSchedules integrationSchedules = new IntegrationSchedules(); + integrationSchedules.add(두번째로_정렬되어야_하는_일정); + integrationSchedules.add(세번째로_정렬되어야_하는_일정); + integrationSchedules.add(첫번째로_정렬되어야_하는_일정); + + // then + assertThat(integrationSchedules.getSortedValues()) + .extracting(IntegrationSchedule::getTitle) + .containsExactly("일정1", "일정2", "일정3"); + } + + @DisplayName("일정의 시작일시가 겹치고, 종료일시도 겹칠때는 일정의 제목을 사전기준 오름차순으로 정렬된다.") + @Test + void 일정의_시작일시가_겹치고_종료일시도_겹칠때는_일정의_제목을_사전기준_오름차순으로_정렬된다() { + // given + 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()); + + IntegrationSchedule 두번째로_정렬되어야_하는_일정 = new IntegrationSchedule("2", categoryId, "나", + LocalDateTime.of(2022, 3, 1, 0, 0), + 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.NORMAL.name()); + + // when + IntegrationSchedules integrationSchedules = new IntegrationSchedules(); + integrationSchedules.add(세번째로_정렬되어야_하는_일정); + integrationSchedules.add(두번째로_정렬되어야_하는_일정); + integrationSchedules.add(첫번째로_정렬되어야_하는_일정); + + // then + assertThat(integrationSchedules.getSortedValues()) + .extracting(IntegrationSchedule::getTitle) + .containsExactly("가", "나", "다"); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java new file mode 100644 index 00000000..42fe1469 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/integrationschedule/domain/PeriodTest.java @@ -0,0 +1,174 @@ +package com.allog.dallog.domain.integrationschedule.domain; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PeriodTest { + + @DisplayName("시작일시와 종료일시의 날짜 차이를 반환한다.") + @Test + void 시작일시와_종료일시의_날짜_차이를_반환한다() { + // given + 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 dayDifference = period.calculateDayDifference(); + + // then + assertThat(dayDifference).isEqualTo(2); + } + + @DisplayName("시작일시와 종료일시의 시간 차이를 반환한다.") + @Test + void 시작일시와_종료일시의_시간_차이를_반환한다() { + // given + LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 11, 0); + LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 23, 0); + + Period period = new Period(startDateTime, endDateTime); + + // when + long hourDifference = period.calculateHourDifference(); + + // then + assertThat(hourDifference).isEqualTo(12); + } + + @DisplayName("시작일시와 종료일시의 분 차이를 반환한다.") + @Test + void 시작일시와_종료일시의_분_차이를_반환한다() { + // given + LocalDateTime startDateTime = LocalDateTime.of(2022, 1, 2, 11, 17); + LocalDateTime endDateTime = LocalDateTime.of(2022, 1, 4, 11, 19); + + Period period = new Period(startDateTime, endDateTime); + + // when + long minuteDifference = period.calculateMinuteDifference(); + + // then + assertThat(minuteDifference).isEqualTo(2); + } + + @DisplayName("기간 뺄셈시 상대 기간이 우측에 걸쳐있을 때의 결과를 계산한다.") + @Test + void 기간_뺄셈시_상대_기간이_우측에_걸쳐있을_때의_결과를_계산한다() { + // given + LocalDateTime baseStartDateTime = LocalDateTime.of(2022, 8, 1, 0, 0); + LocalDateTime baseEndDateTime = LocalDateTime.of(2022, 8, 2, 23, 59); + Period basePeriod = new Period(baseStartDateTime, baseEndDateTime); + + LocalDateTime otherStartDateTime = LocalDateTime.of(2022, 8, 1, 18, 0); + LocalDateTime otherEndDateTime = LocalDateTime.of(2022, 8, 3, 18, 0); + Period otherPeriod = new Period(otherStartDateTime, otherEndDateTime); + + // when + List actual = basePeriod.slice(otherPeriod); + + // then + assertAll(() -> { + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getStartDateTime()).isEqualTo(baseStartDateTime); + assertThat(actual.get(0).getEndDateTime()).isEqualTo(otherStartDateTime); + }); + } + + @DisplayName("기간 뺄셈시 상대 기간이 좌측에 걸쳐있을 때의 결과를 계산한다.") + @Test + void 기간_뺄셈시_상대_기간이_좌측에_걸쳐있을_때의_결과를_계산한다() { + // given + LocalDateTime baseStartDateTime = LocalDateTime.of(2022, 8, 2, 0, 0); + LocalDateTime baseEndDateTime = LocalDateTime.of(2022, 8, 3, 23, 59); + Period basePeriod = new Period(baseStartDateTime, baseEndDateTime); + + LocalDateTime otherStartDateTime = LocalDateTime.of(2022, 8, 1, 18, 0); + LocalDateTime otherEndDateTime = LocalDateTime.of(2022, 8, 2, 18, 0); + Period otherPeriod = new Period(otherStartDateTime, otherEndDateTime); + + // when + List actual = basePeriod.slice(otherPeriod); + + // then + assertAll(() -> { + assertThat(actual).hasSize(1); + assertThat(actual.get(0).getStartDateTime()).isEqualTo(otherEndDateTime); + assertThat(actual.get(0).getEndDateTime()).isEqualTo(baseEndDateTime); + }); + } + + @DisplayName("기간 뺄셈시 상대 기간이 안쪽에 포함될때 결과를 계산한다.") + @Test + void 기간_뺄셈시_상대_기간이_안쪽에_포함될때_결과를_계산한다() { + // given + LocalDateTime baseStartDateTime = LocalDateTime.of(2022, 8, 1, 0, 0); + LocalDateTime baseEndDateTime = LocalDateTime.of(2022, 8, 3, 23, 59); + Period basePeriod = new Period(baseStartDateTime, baseEndDateTime); + + LocalDateTime otherStartDateTime = LocalDateTime.of(2022, 8, 1, 18, 0); + LocalDateTime otherEndDateTime = LocalDateTime.of(2022, 8, 2, 18, 0); + Period otherPeriod = new Period(otherStartDateTime, otherEndDateTime); + + // when + List actual = basePeriod.slice(otherPeriod); + + // then + assertAll(() -> { + assertThat(actual).hasSize(2); + assertThat(actual.get(0).getStartDateTime()).isEqualTo(baseStartDateTime); + assertThat(actual.get(0).getEndDateTime()).isEqualTo(otherStartDateTime); + assertThat(actual.get(1).getStartDateTime()).isEqualTo(otherEndDateTime); + assertThat(actual.get(1).getEndDateTime()).isEqualTo(baseEndDateTime); + }); + } + + @DisplayName("기간 뺄셈시 상대 기간과 완벽히 일치하면 빈 리스트를 반환한다.") + @Test + void 기간_뺄셈시_상대_기간과_완벽히_일치하면_빈_리스트를_반환한다() { + // given + LocalDateTime baseStartDateTime = LocalDateTime.of(2022, 8, 1, 0, 0); + LocalDateTime baseEndDateTime = LocalDateTime.of(2022, 8, 3, 23, 59); + Period basePeriod = new Period(baseStartDateTime, baseEndDateTime); + + LocalDateTime otherStartDateTime = LocalDateTime.of(2022, 8, 1, 0, 0); + LocalDateTime otherEndDateTime = LocalDateTime.of(2022, 8, 3, 23, 59); + Period otherPeriod = new Period(otherStartDateTime, otherEndDateTime); + + // when + List actual = basePeriod.slice(otherPeriod); + + // then + assertAll(() -> { + assertThat(actual).hasSize(0); + }); + } + + @DisplayName("기간 뺄셈시 상대 기간과 겹치지 않으면 자기자신을 리스트로 반환한다.") + @Test + void 기간_뺄셈시_상대_기간과_겹치지_않으면_자기자신을_리스트로_반환한다() { + // given + LocalDateTime baseStartDateTime = LocalDateTime.of(2022, 8, 1, 0, 0); + LocalDateTime baseEndDateTime = LocalDateTime.of(2022, 8, 2, 0, 0); + Period basePeriod = new Period(baseStartDateTime, baseEndDateTime); + + LocalDateTime otherStartDateTime = LocalDateTime.of(2022, 8, 3, 0, 0); + LocalDateTime otherEndDateTime = LocalDateTime.of(2022, 8, 3, 23, 59); + Period otherPeriod = new Period(otherStartDateTime, otherEndDateTime); + + // when + List actual = basePeriod.slice(otherPeriod); + + // then + assertAll(() -> { + assertThat(actual).hasSize(1); + assertThat(actual.get(0)).isEqualTo(basePeriod); + }); + } +} 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 new file mode 100644 index 00000000..7dcea0a5 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/member/application/MemberServiceTest.java @@ -0,0 +1,127 @@ +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; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class MemberServiceTest extends ServiceTest { + + @Autowired + private MemberService memberService; + + @DisplayName("회원을 저장한다.") + @Test + void 회원을_저장한다() { + // given & when + MemberResponse 파랑 = memberService.save(파랑()); + + // then + 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를_통해_회원을_단건_조회한다() { + // given + MemberResponse 파랑 = memberService.save(파랑()); + + // when & then + assertThat(memberService.findById(파랑.getId())) + .usingRecursiveComparison() + .isEqualTo(파랑); + } + + @DisplayName("회원의 이름을 수정한다.") + @Test + void 회원의_이름을_수정한다() { + // given + MemberResponse 매트 = memberService.save(매트()); + + String 패트_이름 = "패트"; + MemberUpdateRequest 매트_수정_요청 = new MemberUpdateRequest(패트_이름); + + // when + memberService.update(매트.getId(), 매트_수정_요청); + + // then + MemberResponse actual = memberService.findById(매트.getId()); + assertThat(actual.getDisplayName()).isEqualTo(패트_이름); + } + + @DisplayName("회원을 제거한다.") + @Test + void 회원을_제거한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + + // when + memberService.deleteById(후디.getId()); + + // then + 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); + } +} 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 new file mode 100644 index 00000000..e63ffef3 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberRepositoryTest.java @@ -0,0 +1,47 @@ +package com.allog.dallog.domain.member.domain; + +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 com.allog.dallog.common.annotation.RepositoryTest; +import com.allog.dallog.domain.category.domain.CategoryRepository; +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; + +class MemberRepositoryTest extends RepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private SubscriptionRepository subscriptionRepository; + + @DisplayName("이메일을 통해 회원을 찾는다.") + @Test + void 이메일을_통해_회원을_찾는다() { + // given + Member 파랑 = memberRepository.save(파랑()); + + // when + Member actual = memberRepository.findByEmail(파랑_이메일).get(); + + // then + assertThat(actual.getId()).isEqualTo(파랑.getId()); + } + + @DisplayName("중복된 이메일이 존재하는 경우 true를 반환한다.") + @Test + void 중복된_이메일이_존재하는_경우_true를_반환한다() { + // given + memberRepository.save(파랑()); + + // when & then + assertThat(memberRepository.existsByEmail(파랑_이메일)).isTrue(); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberTest.java b/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberTest.java new file mode 100644 index 00000000..60f8d0ea --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/member/domain/MemberTest.java @@ -0,0 +1,69 @@ +package com.allog.dallog.domain.member.domain; + +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.assertDoesNotThrow; + +import com.allog.dallog.domain.member.exception.InvalidMemberException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class MemberTest { + + @DisplayName("회원을 생성한다.") + @Test + void 회원을_생성한다() { + // given & when & then + assertDoesNotThrow(() -> new Member(파랑_이메일, 파랑_이름, 파랑_프로필, SocialType.GOOGLE)); + } + + @DisplayName("회원의 email이 형식이 맞지 않으면 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"dev.hyeonic@", "dev.hyeonicgmail.com", "dev.hyeonic@gmail", "@gmail.com", "dev.hyeonic"}) + void 회원의_email이_형식이_맞지_않으면_예외를_던진다(final String email) { + // given & when & then + assertThatThrownBy(() -> new Member(email, 파랑_이름, 파랑_프로필, SocialType.GOOGLE)) + .isInstanceOf(InvalidMemberException.class); + } + + @DisplayName("회원의 이름이 1 ~ 10 사이가 아닌 경우 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"", "일이삼사오육칠팔구십일"}) + void 회원의_이름이_1_에서_10_사이가_아닌_경우_예외를_던진다(final String displayName) { + // given & when & then + assertThatThrownBy(() -> new Member(파랑_이메일, displayName, 파랑_프로필, SocialType.GOOGLE)) + .isInstanceOf(InvalidMemberException.class); + } + + @DisplayName("회원의 이름을 변경한다.") + @Test + void 회원의_이름을_변경한다() { + // given + Member member = 매트(); + String 패트_이름 = "패트"; + + // when + member.change(패트_이름); + + // then + assertThat(member.getDisplayName()).isEqualTo(패트_이름); + } + + @DisplayName("변경하기 위한 회원의 이름이 1 ~ 10 사이가 아닌 경우 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"", "일이삼사오육칠팔구십일"}) + void 변경하기_위한_회원의_이름이_1_에서_10_사이가_아닌_경우_예외를_던진다(final String displayName) { + // given + Member member = 매트(); + + // when & then + assertThatThrownBy(() -> member.change(displayName)) + .isInstanceOf(InvalidMemberException.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 new file mode 100644 index 00000000..ec9653dc --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/application/ScheduleServiceTest.java @@ -0,0 +1,292 @@ +package com.allog.dallog.domain.schedule.application; + +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분; +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 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.exception.NoPermissionException; +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; +import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; +import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; +import java.time.LocalDateTime; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class ScheduleServiceTest extends ServiceTest { + + @Autowired + private ScheduleService scheduleService; + + @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(), 알록달록_회의_생성_요청); + + // then + assertThat(알록달록_회의.getTitle()).isEqualTo(알록달록_회의_제목); + } + + @DisplayName("새로운 일정을 생성 할 떄 일정 제목의 길이가 50을 초과하는 경우 예외를 던진다.") + @Test + void 새로운_일정을_생성_할_때_일정_제목의_길이가_50을_초과하는_경우_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + + String 잘못된_일정_제목 = "일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일"; + ScheduleCreateRequest 잘못된_일정_생성_요청 = new ScheduleCreateRequest(잘못된_일정_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, + 알록달록_회의_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 잘못된_일정_생성_요청)). + isInstanceOf(InvalidScheduleException.class); + } + + @DisplayName("새로운 일정을 생성 할 떄 일정 메모의 길이가 255를 초과하는 경우 예외를 던진다.") + @Test + void 새로운_일정을_생성_할_때_일정_메모의_길이가_255를_초과하는_경우_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + + String 잘못된_일정_메모 = "1".repeat(256); + ScheduleCreateRequest 잘못된_일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, + 잘못된_일정_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 잘못된_일정_생성_요청)). + isInstanceOf(InvalidScheduleException.class); + } + + @DisplayName("새로운 일정을 생성 할 떄 종료일시가 시작일시 이전이라면 예외를 던진다.") + @Test + void 새로운_일정을_생성_할_때_종료일시가_시작일시_이전이라면_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + + LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; + LocalDateTime 종료일시 = 날짜_2022년_7월_1일_0시_0분; + ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(후디.getId(), BE_일정.getId(), 일정_생성_요청)). + isInstanceOf(InvalidScheduleException.class); + } + + @DisplayName("일정 생성 요청자가 카테고리의 생성자가 아닌경우 예외를 던진다") + @Test + void 일정_생성_요청자가_카테고리의_생성자가_아닌경우_예외를_던진다() { + // given + MemberResponse 리버 = memberService.save(리버()); + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + + LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; + LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; + ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(리버.getId(), BE_일정.getId(), 일정_생성_요청)). + isInstanceOf(NoPermissionException.class); + } + + @DisplayName("일정 생성시 전달한 카테고리가 존재하지 않는다면 예외를 던진다.") + @Test + void 일정_생성시_전달한_카테고리가_존재하지_않는다면_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + + LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; + LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; + ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(후디.getId(), 0L, 일정_생성_요청)). + isInstanceOf(NoSuchCategoryException.class); + } + + @DisplayName("일정 생성시 전달한 카테고리가 외부 연동 카테고리라면 예외를 던진다.") + @Test + void 일정_생성시_전달한_카테고리가_외부_연동_카테고리라면_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse 대한민국_공휴일 = categoryService.save(후디.getId(), 대한민국_공휴일_생성_요청); + + LocalDateTime 시작일시 = 날짜_2022년_7월_15일_16시_0분; + LocalDateTime 종료일시 = 날짜_2022년_7월_31일_0시_0분; + ScheduleCreateRequest 일정_생성_요청 = new ScheduleCreateRequest(알록달록_회의_제목, 시작일시, 종료일시, 알록달록_회의_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.save(후디.getId(), 대한민국_공휴일.getId(), 일정_생성_요청)). + isInstanceOf(NoPermissionException.class); + } + + @DisplayName("일정의 ID로 단건 일정을 조회한다.") + @Test + void 일정의_ID로_단건_일정을_조회한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + // when + ScheduleResponse response = scheduleService.findById(알록달록_회의.getId()); + + // then + assertAll(() -> { + assertThat(response.getId()).isEqualTo(알록달록_회의.getId()); + assertThat(response.getTitle()).isEqualTo(알록달록_회의_제목); + assertThat(response.getStartDateTime()).isEqualTo(알록달록_회의_시작일시); + assertThat(response.getEndDateTime()).isEqualTo(알록달록_회의_종료일시); + assertThat(response.getMemo()).isEqualTo(알록달록_회의_메모); + }); + } + + @DisplayName("존재하지 않는 일정 ID로 단건 일정을 조회하면 예외를 던진다.") + @Test + void 존재하지_않는_일정_ID로_단건_일정을_조회하면_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + Long 잘못된_아이디 = 0L; + + // when & then + assertThatThrownBy(() -> scheduleService.findById(잘못된_아이디)); + } + + @DisplayName("일정을 수정한다.") + @Test + void 일정을_수정한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + + // when + scheduleService.update(기존_일정.getId(), 후디.getId(), 일정_수정_요청); + + // then + ScheduleResponse actual = scheduleService.findById(기존_일정.getId()); + assertAll( + () -> { + assertThat(actual.getId()).isEqualTo(기존_일정.getId()); + assertThat(actual.getTitle()).isEqualTo(레벨_인터뷰_제목); + assertThat(actual.getStartDateTime()).isEqualTo(레벨_인터뷰_시작일시); + assertThat(actual.getEndDateTime()).isEqualTo(레벨_인터뷰_종료일시); + assertThat(actual.getMemo()).isEqualTo(레벨_인터뷰_메모); + } + ); + } + + @DisplayName("일정 수정 시 일정의 카테고리에 대한 권한이 없을 경우 예외가 발생한다.") + @Test + void 일정_수정_시_일정의_카테고리에_대한_권한이_없을_경우_예외가_발생한다() { + // given + MemberResponse 리버 = memberService.save(리버()); + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.update(기존_일정.getId(), 리버.getId(), 일정_수정_요청)) + .isInstanceOf(NoPermissionException.class); + } + + @DisplayName("일정 수정 시 존재하지 않은 일정일 경우 예외가 발생한다.") + @Test + void 일정_수정_시_존재하지_않은_일정일_경우_예외가_발생한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 기존_일정 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + ScheduleUpdateRequest 일정_수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + + // when & then + assertThatThrownBy(() -> scheduleService.update(기존_일정.getId() + 1, 후디.getId(), 일정_수정_요청)) + .isInstanceOf(NoSuchScheduleException.class); + } + + @DisplayName("일정을 삭제한다.") + @Test + void 일정을_삭제한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + // when + scheduleService.deleteById(알록달록_회의.getId(), 후디.getId()); + + // then + assertThatThrownBy(() -> scheduleService.findById(알록달록_회의.getId())) + .isInstanceOf(NoSuchScheduleException.class); + } + + @DisplayName("일정 삭제 시 일정의 카테고리에 대한 권한이 없을 경우 예외가 발생한다.") + @Test + void 일정_삭제_시_일정의_카테고리에_대한_권한이_없을_경우_예외가_발생한다() { + // given + MemberResponse 리버 = memberService.save(리버()); + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + // when & then + assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId(), 리버.getId())) + .isInstanceOf(NoPermissionException.class); + } + + @DisplayName("일정 삭제 시 존재하지 않은 일정일 경우 예외가 발생한다.") + @Test + void 일정_삭제_시_존재하지_않은_일정일_경우_예외가_발생한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + ScheduleResponse 알록달록_회의 = scheduleService.save(후디.getId(), BE_일정.getId(), 알록달록_회의_생성_요청); + + // when & then + assertThatThrownBy(() -> scheduleService.deleteById(알록달록_회의.getId() + 1, 후디.getId())) + .isInstanceOf(NoSuchScheduleException.class); + } +} 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 new file mode 100644 index 00000000..3ac9064a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleRepositoryTest.java @@ -0,0 +1,78 @@ +package com.allog.dallog.domain.schedule.domain; + +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.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년_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.알록달록_회식_메모; +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.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 java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class ScheduleRepositoryTest extends RepositoryTest { + + @Autowired + private ScheduleRepository scheduleRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private MemberRepository memberRepository; + + @DisplayName("특정 카테고리들에 속한 일정을 전부 삭제한다") + @Test + void 특정_카테고리들에_속한_일정을_전부_삭제한다() { + // given + Member 관리자 = 관리자(); + memberRepository.save(관리자); + + Category BE_일정 = BE_일정(관리자); + Category FE_일정 = FE_일정(관리자); + Category 공통_일정 = 공통_일정(관리자); + categoryRepository.save(BE_일정); + categoryRepository.save(FE_일정); + categoryRepository.save(공통_일정); + + Schedule 알록달록_회의_BE = new Schedule(BE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, + 알록달록_회의_메모); + Schedule 알록달록_회식_BE = new Schedule(BE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, + 알록달록_회식_메모); + Schedule 알록달록_회의_FE = new Schedule(FE_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, + 알록달록_회의_메모); + Schedule 알록달록_회식_FE = new Schedule(FE_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, + 알록달록_회식_메모); + Schedule 알록달록_회의_공통 = new Schedule(공통_일정, 알록달록_회의_제목, 날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분, + 알록달록_회의_메모); + Schedule 알록달록_회식_공통 = new Schedule(공통_일정, 알록달록_회식_제목, 날짜_2022년_8월_15일_14시_0분, 날짜_2022년_8월_15일_17시_0분, + 알록달록_회식_메모); + + scheduleRepository.save(알록달록_회의_BE); + scheduleRepository.save(알록달록_회식_BE); + scheduleRepository.save(알록달록_회의_FE); + scheduleRepository.save(알록달록_회식_FE); + scheduleRepository.save(알록달록_회의_공통); + scheduleRepository.save(알록달록_회식_공통); + + // when + scheduleRepository.deleteByCategoryIdIn(List.of(BE_일정.getId(), FE_일정.getId(), 공통_일정.getId())); + + // then + assertThat(scheduleRepository.findAll()).hasSize(0); + } +} 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 new file mode 100644 index 00000000..2b456c5d --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/ScheduleTest.java @@ -0,0 +1,55 @@ +package com.allog.dallog.domain.schedule.domain; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.BE_일정; +import static com.allog.dallog.common.fixtures.MemberFixtures.관리자; +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.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.schedule.exception.InvalidScheduleException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class ScheduleTest { + + @DisplayName("일정을 생성한다.") + @Test + void 일정을_생성한다() { + // given + Category BE_일정_카테고리 = BE_일정(관리자()); + + // when & then + assertDoesNotThrow(() -> new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모)); + } + + @DisplayName("일정 제목의 길이가 50을 초과하는 경우 예외를 던진다.") + @ParameterizedTest + @ValueSource(strings = {"일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일이삼사오육칠팔구십일일이삼사오육칠팔구십일", + "알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 알록달록 회의"}) + void 일정_제목의_길이가_50을_초과하는_경우_예외를_던진다(final String 잘못된_일정_제목) { + //given + Category BE_일정_카테고리 = BE_일정(관리자()); + + // when & then + assertThatThrownBy(() -> new Schedule(BE_일정_카테고리, 잘못된_일정_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모)) + .isInstanceOf(InvalidScheduleException.class); + } + + @DisplayName("일정 메모의 길이가 255를 초과하는 경우 예외를 던진다.") + @Test + void 일정_메모의_길이가_255를_초과하는_경우_예외를_던진다() { + // given + String 잘못된_메모 = "1".repeat(256); + Category BE_일정_카테고리 = BE_일정(관리자()); + + // when & then + assertThatThrownBy(() -> new Schedule(BE_일정_카테고리, 알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 잘못된_메모)) + .isInstanceOf(InvalidScheduleException.class); + } +} 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 new file mode 100644 index 00000000..d25c0648 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/schedule/domain/scheduler/SchedulerTest.java @@ -0,0 +1,81 @@ +package com.allog.dallog.domain.schedule.domain.scheduler; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정; +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월_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 org.assertj.core.api.Assertions.assertThat; +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 java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SchedulerTest { + + @DisplayName("겹치지 않는 기간을 계산한다.") + @Test + void 겹치지_않는_기간을_계산한다() { + // given + /* 사람들의 일정 목록 */ + Category 공통_일정 = 공통_일정(관리자()); + String 일정_제목 = "일정 제목"; + String 일정_메모 = "일정 메모"; + + IntegrationSchedule 일정1 = new IntegrationSchedule("1", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_7일_16시_0분, + 날짜_2022년_7월_10일_0시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정2 = new IntegrationSchedule("2", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_10일_11시_59분, + 날짜_2022년_7월_15일_16시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정3 = new IntegrationSchedule("3", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_16시_0분, + 날짜_2022년_7월_16일_16시_1분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정4 = new IntegrationSchedule("4", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_18시_0분, + 날짜_2022년_7월_16일_20시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정5 = new IntegrationSchedule("5", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_16일_20시_0분, + 날짜_2022년_7월_20일_0시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정6 = new IntegrationSchedule("6", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_20일_11시_59분, + 날짜_2022년_7월_27일_0시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정7 = new IntegrationSchedule("7", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_27일_11시_59분, + 날짜_2022년_7월_31일_0시_0분, 일정_메모, "NORMAL"); + IntegrationSchedule 일정8 = new IntegrationSchedule("8", 공통_일정.getId(), 일정_제목, 날짜_2022년_7월_31일_0시_0분, + 날짜_2022년_8월_15일_14시_0분, 일정_메모, "NORMAL"); + + List 일정_목록 = List.of(일정1, 일정2, 일정3, 일정4, 일정5, 일정6, 일정7, 일정8); + + // when + LocalDateTime startDateTime = LocalDateTime.of(2022, 7, 1, 0, 0); + LocalDateTime endDateTime = LocalDateTime.of(2022, 8, 31, 0, 0); + Scheduler scheduler = new Scheduler(일정_목록, startDateTime, endDateTime); + List actual = scheduler.getPeriods(); + + // then + assertAll(() -> { + assertThat(actual).hasSize(7); + assertThat(actual).containsExactly( + new Period(날짜_2022년_7월_1일_0시_0분, 날짜_2022년_7월_7일_16시_0분), + new Period(날짜_2022년_7월_10일_0시_0분, 날짜_2022년_7월_10일_11시_59분), + new Period(날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분), + new Period(날짜_2022년_7월_16일_16시_1분, 날짜_2022년_7월_16일_18시_0분), + new Period(날짜_2022년_7월_20일_0시_0분, 날짜_2022년_7월_20일_11시_59분), + new Period(날짜_2022년_7월_27일_0시_0분, 날짜_2022년_7월_27일_11시_59분), + new Period(날짜_2022년_8월_15일_14시_0분, endDateTime) + ); + }); + } +} 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 new file mode 100644 index 00000000..d0d815fc --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/application/SubscriptionServiceTest.java @@ -0,0 +1,222 @@ +package com.allog.dallog.domain.subscription.application; + + +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; + +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.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; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; +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 org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.springframework.beans.factory.annotation.Autowired; + +class SubscriptionServiceTest extends ServiceTest { + + @Autowired + private MemberService memberService; + + @Autowired + private CategoryService categoryService; + + @Autowired + private SubscriptionService subscriptionService; + + @DisplayName("새로운 구독을 생성한다.") + @Test + void 새로운_구독을_생성한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + + // when + SubscriptionResponse response = subscriptionService.save(후디.getId(), BE_일정.getId()); + + // then + assertThat(response.getCategory().getName()).isEqualTo(BE_일정_이름); + } + + @DisplayName("자신이 생성하지 않은 개인 카테고리를 구독시 예외가 발생한다.") + @Test + void 자신이_생성하지_않은_개인_카테고리를_구독시_예외가_발생한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse 후디_개인_학습_일정 = categoryService.save(후디.getId(), 내_일정_생성_요청); + + MemberResponse 매트 = memberService.save(매트()); + + // when & then + assertThatThrownBy(() -> subscriptionService.save(매트.getId(), 후디_개인_학습_일정.getId())) + .isInstanceOf(NoPermissionException.class) + .hasMessage("구독 권한이 없는 카테고리입니다."); + } + + @DisplayName("이미 존재하는 구독 정보를 저장할 경우 예외를 던진다.") + @Test + void 이미_존재하는_구독_정보를_저장할_경우_예외를_던진다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + subscriptionService.save(후디.getId(), BE_일정.getId()); + + // when & then + assertThatThrownBy(() -> subscriptionService.save(후디.getId(), BE_일정.getId())) + .isInstanceOf(ExistSubscriptionException.class); + } + + @DisplayName("구독 id를 기반으로 단건 조회한다.") + @Test + void 구독_id를_기반으로_단건_조회한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + SubscriptionResponse 빨간색_구독 = subscriptionService.save(후디.getId(), BE_일정.getId()); + + // when + SubscriptionResponse foundResponse = subscriptionService.findById(빨간색_구독.getId()); + + // then + assertAll(() -> { + assertThat(foundResponse.getId()).isEqualTo(빨간색_구독.getId()); + assertThat(foundResponse.getCategory().getId()).isEqualTo(BE_일정.getId()); + }); + } + + @DisplayName("존재하지 않는 구독 정보인 경우 예외를 던진다.") + @Test + void 존재하지_않는_구독_정보인_경우_예외를_던진다() { + // given & when & then + assertThatThrownBy(() -> subscriptionService.findById(0L)) + .isInstanceOf(NoSuchSubscriptionException.class); + } + + @DisplayName("회원 정보를 기반으로 구독 정보를 조회한다.") + @Test + void 회원_정보를_기반으로_구독_정보를_조회한다() { + // given + MemberResponse 관리자 = memberService.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + + MemberResponse 후디 = memberService.save(후디()); + subscriptionService.save(후디.getId(), 공통_일정.getId()); + subscriptionService.save(후디.getId(), BE_일정.getId()); + subscriptionService.save(후디.getId(), FE_일정.getId()); + + // when + SubscriptionsResponse subscriptionsResponse = subscriptionService.findByMemberId(후디.getId()); + + // then + assertThat(subscriptionsResponse.getSubscriptions()).hasSize(3); + } + + @DisplayName("구독 정보를 수정한다.") + @Test + void 구독_정보를_수정한다() { + // given + MemberResponse 후디 = memberService.save(후디()); + CategoryResponse BE_일정 = categoryService.save(후디.getId(), BE_일정_생성_요청); + SubscriptionResponse response = subscriptionService.save(후디.getId(), BE_일정.getId()); + Color color = Color.COLOR_1; + + // when + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(color, true); + subscriptionService.update(response.getId(), 후디.getId(), request); + + // then + assertAll(() -> { + assertThat(request.getColor()).isEqualTo(color); + assertThat(request.isChecked()).isTrue(); + }); + } + + @DisplayName("구독 정보 수정 시 존재하지 않는 색상인 경우 예외를 던진다.") + @ParameterizedTest + @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()); + + // when + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(colorCode, true); + + // then + assertThatThrownBy(() -> subscriptionService.update(response.getId(), 후디.getId(), request)) + .isInstanceOf(InvalidSubscriptionException.class); + } + + @DisplayName("구독 정보를 삭제한다.") + @Test + void 구독_정보를_삭제한다() { + // given + MemberResponse 관리자 = memberService.save(관리자()); + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + CategoryResponse BE_일정 = categoryService.save(관리자.getId(), BE_일정_생성_요청); + CategoryResponse FE_일정 = categoryService.save(관리자.getId(), FE_일정_생성_요청); + + MemberResponse 후디 = memberService.save(후디()); + SubscriptionResponse response = subscriptionService.save(후디.getId(), 공통_일정.getId()); + subscriptionService.save(후디.getId(), BE_일정.getId()); + subscriptionService.save(후디.getId(), FE_일정.getId()); + + // when + subscriptionService.deleteById(response.getId(), 후디.getId()); + + // then + assertThat(subscriptionService.findByMemberId(후디.getId()).getSubscriptions()).hasSize(2); + } + + @DisplayName("자신의 구독 정보가 아닌 구독을 삭제할 경우 예외를 던진다.") + @Test + void 자신의_구독_정보가_아닌_구독을_삭제할_경우_예외를_던진다() { + // given + MemberResponse 관리자 = memberService.save(관리자()); + MemberResponse 파랑 = memberService.save(파랑()); + + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + SubscriptionResponse 공통_일정_구독 = subscriptionService.save(파랑.getId(), 공통_일정.getId()); + + // when & then + assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자.getId())) + .isInstanceOf(NoPermissionException.class); + } + + @DisplayName("자신이 만든 카테고리에 대한 구독을 삭제할 경우 예외를 던진다") + @Test + void 자신이_만든_카테고리에_대한_구독을_삭제할_경우_예외를_던진다() { + // given + MemberResponse 관리자 = memberService.save(관리자()); + + CategoryResponse 공통_일정 = categoryService.save(관리자.getId(), 공통_일정_생성_요청); + SubscriptionResponse 공통_일정_구독 = subscriptionService.save(관리자.getId(), 공통_일정.getId()); + + // when & then + assertThatThrownBy(() -> subscriptionService.deleteById(공통_일정_구독.getId(), 관리자.getId())) + .isInstanceOf(NoPermissionException.class); + } +} 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 new file mode 100644 index 00000000..cf6aeae8 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/ColorTest.java @@ -0,0 +1,53 @@ +package com.allog.dallog.domain.subscription.domain; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.allog.dallog.domain.subscription.exception.InvalidSubscriptionException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; + +class ColorTest { + + @DisplayName("랜덤으로 색상을 가져온다.") + @Test + void 랜덤으로_색상을_가져온다() { + // given + ColorPickerStrategy testStrategy = () -> 0; + + // when & then + assertThat(Color.pickAny(testStrategy)).isEqualTo(Color.COLOR_1); + } + + @DisplayName("color code에 맞는 색상을 가져온다.") + @ParameterizedTest + @EnumSource + void color_code에_맞는_색상을_가져온다(final Color color) { + // given & when & then + assertThat(Color.from(color.getColorCode())).isEqualTo(color); + } + + @DisplayName("소문자로 들어온 color code도 가져온다.") + @ParameterizedTest + @EnumSource + void 소문자로_들어온_color_code도_가져온다(final Color color) { + // given + String lowerColorCode = color.getColorCode().toLowerCase(); + + // when & then + assertThat(Color.from(lowerColorCode)).isEqualTo(color); + } + + @DisplayName("존재하지 않는 color code인 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"#asdfe", "#adfqwerse"}) + void 존재하지_않는_color_code인_경우_예외가_발생한다(final String colorCode) { + // given & when & then + assertThatThrownBy(() -> Color.from(colorCode)) + .isInstanceOf(InvalidSubscriptionException.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 new file mode 100644 index 00000000..2061e292 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionRepositoryTest.java @@ -0,0 +1,165 @@ +package com.allog.dallog.domain.subscription.domain; + +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.SubscriptionFixtures.색상1_구독; +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 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 java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; + +class SubscriptionRepositoryTest extends RepositoryTest { + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private SubscriptionRepository subscriptionRepository; + + @DisplayName("존재하지 않는 카테고리를 확인할 경우 true를 반환한다.") + @Test + void 존재하지_않는_카테고리를_확인할_경우_true를_반환한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + + Member 매트 = memberRepository.save(매트()); + + // when + boolean actual = subscriptionRepository.existsByMemberIdAndCategoryId(매트.getId(), 공통_일정.getId()); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("이미 존재하는 카테고리를 확인할 경우 true를 반환한다.") + @Test + void 이미_존재하는_카테고리를_확인할_경우_true를_반환한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + + Member 매트 = memberRepository.save(매트()); + subscriptionRepository.save(색상1_구독(매트, 공통_일정)); + + // when + boolean actual = subscriptionRepository.existsByMemberIdAndCategoryId(매트.getId(), 공통_일정.getId()); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("회원 정보를 기반으로 구독 정보를 조회한다.") + @Test + void 회원_정보를_기반으로_구독_정보를_조회한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + Category BE_일정 = categoryRepository.save(BE_일정(관리자)); + Category FE_일정 = categoryRepository.save(FE_일정(관리자)); + + Member 후디 = memberRepository.save(후디()); + subscriptionRepository.save(색상1_구독(후디, 공통_일정)); + subscriptionRepository.save(색상2_구독(후디, BE_일정)); + subscriptionRepository.save(색상3_구독(후디, FE_일정)); + + // when + List subscriptions = subscriptionRepository.findByMemberId(후디.getId()); + + // then + assertThat(subscriptions).hasSize(3); + } + + @DisplayName("회원의 구독 정보가 존재하지 않는 경우 빈 리스트가 조회된다.") + @Test + void 회원의_구독_정보가_존재하지_않는_경우_빈_리스트가_조회된다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + // when + List subscriptions = subscriptionRepository.findByMemberId(관리자.getId()); + + // then + assertThat(subscriptions).isEmpty(); + } + + @DisplayName("회원의 특정 구독 정보 여부를 확인한다.") + @Test + void 회원의_특정_구독_정보_여부를_확인한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + Category 공통_일정 = categoryRepository.save(공통_일정(관리자)); + + Member 후디 = memberRepository.save(후디()); + Subscription 색상1_구독 = 색상1_구독(후디, 공통_일정); + subscriptionRepository.save(색상1_구독); + + // when + boolean actual = subscriptionRepository.existsByIdAndMemberId(색상1_구독.getId(), 후디.getId()); + + // then + assertThat(actual).isTrue(); + } + + @DisplayName("회원의 존재하지 않는 구독 정보 여부를 확인한다.") + @Test + void 회원의_존재하지_않는_구독_정보_여부를_확인한다() { + // given + Member 관리자 = memberRepository.save(관리자()); + + // when + boolean actual = subscriptionRepository.existsByIdAndMemberId(0L, 관리자.getId()); + + // then + assertThat(actual).isFalse(); + } + + @DisplayName("특정 카테고리들에 속한 구독을 전부 삭제한다") + @Test + void 특정_카테고리들에_속한_구독을_전부_삭제한다() { + // given + Member 관리자 = 관리자(); + memberRepository.save(관리자); + Member 파랑 = 파랑(); + memberRepository.save(파랑); + + Category BE_일정 = BE_일정(관리자); + Category FE_일정 = FE_일정(관리자); + Category 공통_일정 = 공통_일정(관리자); + categoryRepository.save(BE_일정); + categoryRepository.save(FE_일정); + categoryRepository.save(공통_일정); + + subscriptionRepository.save(색상1_구독(관리자, BE_일정)); + subscriptionRepository.save(색상2_구독(관리자, FE_일정)); + subscriptionRepository.save(색상3_구독(관리자, 공통_일정)); + subscriptionRepository.save(색상1_구독(파랑, BE_일정)); + subscriptionRepository.save(색상2_구독(파랑, FE_일정)); + subscriptionRepository.save(색상3_구독(파랑, 공통_일정)); + + // when + subscriptionRepository.deleteByCategoryIdIn(List.of( + BE_일정.getId(), FE_일정.getId(), 공통_일정.getId() + )); + + // then + assertThat(subscriptionRepository.findAll()).hasSize(0); + } +} diff --git a/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionTest.java b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionTest.java new file mode 100644 index 00000000..5045321d --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/domain/subscription/domain/SubscriptionTest.java @@ -0,0 +1,74 @@ +package com.allog.dallog.domain.subscription.domain; + +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.common.fixtures.SubscriptionFixtures.색상1_구독; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.member.domain.Member; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class SubscriptionTest { + + @DisplayName("구독을 생성한다.") + @Test + void 구독을_생성한다() { + // given + Member 후디 = 후디(); + Category 후디_JPA_스터디 = 후디_JPA_스터디(후디); + Color color = Color.COLOR_1; + + // when & then + assertDoesNotThrow(() -> new Subscription(후디, 후디_JPA_스터디, color)); + } + + @DisplayName("구독이 생성되면 기본적으로 체크된다.") + @Test + void 구독이_생성되면_기본적으로_체크된다() { + // given + Member 매트 = 매트(); + Category 매트_아고라 = 매트_아고라(매트); + Color color = Color.COLOR_1; + + // when + Subscription actual = new Subscription(매트, 매트_아고라, color); + + // then + assertThat(actual.isChecked()).isTrue(); + } + + @DisplayName("구독의 색 정보를 수정한다.") + @Test + void 구독의_색_정보를_수정한다() { + // given + Member 매트 = 매트(); + Category 매트_아고라 = 매트_아고라(매트); + + // when + Subscription actual = 색상1_구독(매트, 매트_아고라); + actual.change(Color.COLOR_1, actual.isChecked()); + + // then + assertThat(actual.getColor()).isEqualTo(Color.COLOR_1); + } + + @DisplayName("구독의 체크 유무를 수정한다.") + @Test + void 구독의_체크_유무를_수정한다() { + // given + Member 매트 = 매트(); + Category 매트_아고라 = 매트_아고라(매트); + + // when + Subscription actual = 색상1_구독(매트, 매트_아고라); + actual.change(actual.getColor(), !actual.isChecked()); + + // then + assertThat(actual.isChecked()).isFalse(); + } +} 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 new file mode 100644 index 00000000..d26f7013 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubExternalCalendarClient.java @@ -0,0 +1,29 @@ +package com.allog.dallog.infrastructure.oauth.client; + +import static com.allog.dallog.common.fixtures.ExternalCalendarFixtures.내_일정; +import static com.allog.dallog.common.fixtures.ExternalCalendarFixtures.대한민국_공휴일; +import static com.allog.dallog.common.fixtures.ExternalCalendarFixtures.우아한테크코스; +import static com.allog.dallog.common.fixtures.IntegrationScheduleFixtures.레벨3_방학; +import static com.allog.dallog.common.fixtures.IntegrationScheduleFixtures.포수타; + +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 java.util.List; + +public class StubExternalCalendarClient implements ExternalCalendarClient { + + @Override + public List getExternalCalendars(final String accessToken) { + return List.of(대한민국_공휴일, 우아한테크코스, 내_일정); + } + + @Override + public List getExternalCalendarSchedules(final String accessToken, + final Long internalCategoryId, + final String externalCalendarId, + final String startDateTime, + final String endDateTime) { + return List.of(포수타, 레벨3_방학); + } +} 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 new file mode 100644 index 00000000..0b8654a5 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/infrastructure/oauth/client/StubOAuthClient.java @@ -0,0 +1,30 @@ +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.domain.auth.application.OAuthClient; +import com.allog.dallog.domain.auth.dto.OAuthMember; +import com.allog.dallog.domain.auth.dto.response.OAuthAccessTokenResponse; + +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(); + } + + @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); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java new file mode 100644 index 00000000..c253c6ee --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/AuthControllerTest.java @@ -0,0 +1,120 @@ +package com.allog.dallog.presentation; + +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; +import static com.allog.dallog.common.fixtures.AuthFixtures.OAuth_로그인_링크; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +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; +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.infrastructure.oauth.exception.OAuthException; +import com.allog.dallog.presentation.auth.AuthController; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +@WebMvcTest(AuthController.class) +class AuthControllerTest extends ControllerTest { + + @MockBean + private AuthService authService; + + @DisplayName("OAuth 소셜 로그인을 위한 링크와 상태코드 200을 반환한다.") + @Test + void OAuth_소셜_로그인을_위한_링크와_상태코드_200을_반환한다() throws Exception { + // given + given(authService.generateGoogleLink(any())).willReturn(OAuth_로그인_링크); + + // when & then + mockMvc.perform(get("/api/auth/{oauthProvider}/oauth-uri?redirectUri={redirectUri}", OAUTH_PROVIDER, + "https://dallog.me/oauth")) + .andDo(print()) + .andDo(document("auth/link", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("oauthProvider").description("OAuth 로그인 제공자") + ), + requestParameters( + parameterWithName("redirectUri").description("OAuth Redirect URI") + ), + responseFields( + fieldWithPath("oAuthUri").type(JsonFieldType.STRING).description("OAuth 소셜 로그인 링크") + ) + )) + .andExpect(status().isOk()); + } + + @DisplayName("OAuth 로그인을 하면 token과 상태코드 200을 반환한다.") + @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 + mockMvc.perform(post("/api/auth/{oauthProvider}/token", OAUTH_PROVIDER) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(MEMBER_인증_코드_토큰_요청()))) + .andDo(print()) + .andDo(document("auth/token", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("oauthProvider").description("OAuth 로그인 제공자") + ), + requestFields( + fieldWithPath("code").type(JsonFieldType.STRING).description("OAuth 로그인 인증 코드"), + fieldWithPath("redirectUri").type(JsonFieldType.STRING) + .description("OAuth Redirect URI") + ) + )) + .andExpect(status().isOk()); + } + + @DisplayName("OAuth 로그인 과정에서 Resource Server 에러가 발생하면 상태코드 500을 반환한다.") + @Test + void OAuth_로그인_과정에서_Resource_Server_에러가_발생하면_상태코드_500을_반환한다() throws Exception { + // given + given(authService.generateToken(any())).willThrow(new OAuthException()); + + // when & then + mockMvc.perform(post("/api/auth/{oauthProvider}/token", OAUTH_PROVIDER) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(MEMBER_인증_코드_토큰_요청()))) + .andDo(print()) + .andDo(document("auth/exception/token", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("oauthProvider").description("OAuth 로그인 제공자") + ), + requestFields( + fieldWithPath("code").type(JsonFieldType.STRING).description("OAuth 로그인 인증 코드"), + fieldWithPath("redirectUri").type(JsonFieldType.STRING) + .description("OAuth Redirect URI") + ) + )) + .andExpect(status().isInternalServerError()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java new file mode 100644 index 00000000..780bf34f --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/CategoryControllerTest.java @@ -0,0 +1,438 @@ +package com.allog.dallog.presentation; + +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.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.후디_JPA_스터디; +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.domain.category.domain.CategoryType.NORMAL; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willThrow; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +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.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +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.category.application.CategoryService; +import com.allog.dallog.domain.category.domain.Category; +import com.allog.dallog.domain.category.dto.request.CategoryCreateRequest; +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.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; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; + +@WebMvcTest(CategoryController.class) +class CategoryControllerTest extends ControllerTest { + + private static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + private static final String AUTHORIZATION_HEADER_VALUE = "Bearer aaaaaaaa.bbbbbbbb.cccccccc"; + private static final String INVALID_CATEGORY_NAME = "20글자를 초과하는 유효하지 않은 카테고리 이름"; + private static final String CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE = "카테고리 이름의 길이는 20을 초과할 수 없습니다."; + + @MockBean + private AuthService authService; + + @MockBean + private CategoryService categoryService; + + @MockBean + private CategorySubscriptionService categorySubscriptionService; + + @DisplayName("카테고리를 생성한다.") + @Test + void 카테고리를_생성한다() throws Exception { + // given + CategoryResponse 카테고리 = BE_일정_응답(후디_응답); + given(categorySubscriptionService.save(any(), any(CategoryCreateRequest.class))).willReturn(카테고리); + + // when & then + mockMvc.perform(post("/api/categories") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(BE_일정_생성_요청)) + ) + .andDo(print()) + .andDo(document("categories/save", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰")) + ) + ) + .andExpect(status().isCreated()); + } + + @DisplayName("잘못된 이름 형식으로 카테고리를 생성하면 400 Bad Request가 발생한다.") + @Test + void 잘못된_이름_형식으로_카테고리를_생성하면_400_Bad_Request가_발생한다() throws Exception { + // given + CategoryCreateRequest 잘못된_카테고리_생성_요청 = new CategoryCreateRequest(INVALID_CATEGORY_NAME, NORMAL); + + willThrow(new InvalidCategoryException(CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE)) + .given(categorySubscriptionService) + .save(any(), any(CategoryCreateRequest.class)); + + // when & then + mockMvc.perform(post("/api/categories") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(잘못된_카테고리_생성_요청)) + ) + .andDo(print()) + .andDo(document("categories/save/badRequest", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰")) + ) + ) + .andExpect(status().isBadRequest()); + } + + @DisplayName("생성된 카테고리를 전부 조회한다.") + @Test + void 생성된_카테고리를_전부_조회한다() throws Exception { + // given + int page = 0; + int size = 10; + + List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자()), 후디_JPA_스터디(후디()), 매트_아고라(매트())); + CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); + given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); + + // when & then + mockMvc.perform(get("/api/categories?page={page}&size={size}", page, size) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("categories/findAll", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParameters( + parameterWithName("page").description("페이지 번호"), + parameterWithName("size").description("페이지 크기") + ) + ) + ) + .andExpect(status().isOk()); + } + + @DisplayName("카테고리 제목을 활용하여 조회한다.") + @Test + void 카테고리_제목을_활용하여_조회한다() throws Exception { + // given + int page = 0; + int size = 10; + + List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); + CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); + given(categoryService.findNormalByName(any(), any())).willReturn(categoriesResponse); + + // when & then + mockMvc.perform(get("/api/categories?name={name}&page={page}&size={size}", "E", page, size) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("categories/findAllLikeName", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParameters( + parameterWithName("name").description("카테고리 검색어"), + parameterWithName("page").description("페이지 번호"), + parameterWithName("size").description("페이지 크기") + ) + ) + ) + .andExpect(status().isOk()); + } + + @DisplayName("내 카테고리를 전부 조회한다.") + @Test + void 내_카테고리를_전부_조회한다() throws Exception { + // given + int page = 0; + int size = 10; + + List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); + CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); + given(categoryService.findMineByName(any(), any(), any())).willReturn(categoriesResponse); + + // when & then + mockMvc.perform(get("/api/categories/me?name={name}&page={page}&size={size}", "", page, size) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + ) + .andDo(print()) + .andDo(document("categories/findMine", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParameters( + parameterWithName("name").description("카테고리 검색어"), + parameterWithName("page").description("페이지 번호"), + parameterWithName("size").description("페이지 크기") + ) + ) + ) + .andExpect(status().isOk()); + } + + @DisplayName("내 카테고리를 제목을 활용하여 조회한다.") + @Test + void 내_카테고리를_제목을_활용하여_조회한다() throws Exception { + // given + int page = 0; + int size = 10; + + List 일정_목록 = List.of(공통_일정(관리자()), BE_일정(관리자()), FE_일정(관리자())); + CategoriesResponse categoriesResponse = new CategoriesResponse(page, 일정_목록); + given(categoryService.findMineByName(any(), any(), any())).willReturn(categoriesResponse); + + // when & then + mockMvc.perform(get("/api/categories/me?name={name}&page={page}&size={size}", "E", page, size) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + ) + .andDo(print()) + .andDo(document("categories/findMineLikeName", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParameters( + parameterWithName("name").description("카테고리 검색어"), + parameterWithName("page").description("페이지 번호"), + parameterWithName("size").description("페이지 크기") + ) + ) + ) + .andExpect(status().isOk()); + } + + @DisplayName("카테고리 ID로 카테고리를 단건 조회한다.") + @Test + void 카테고리_ID로_카테고리를_단건_조회한다() throws Exception { + // given + Long categoryId = 1L; + CategoryResponse BE_일정_응답 = BE_일정_응답(후디_응답); + given(categoryService.findById(any())).willReturn(BE_일정_응답); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("categories/findById", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isOk()); + } + + @DisplayName("카테고리 ID로 카테고리를 단건 조회시 존재하지 않으면 404 Not Found가 발생한다.") + @Test + void 카테고리_ID로_카테고리를_단건_조회시_존재하지_않으면_404_Not_Found를_반환한다() throws Exception { + // given + Long categoryId = 1L; + given(categoryService.findById(any())) + .willThrow(new NoSuchCategoryException()); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.get("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("categories/findById/notFound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isNotFound()); + } + + @DisplayName("카테고리를 수정한다.") + @Test + void 카테고리를_수정한다() throws Exception { + // given + Long categoryId = 1L; + willDoNothing() + .given(categoryService) + .update(any(), any(), any()); + CategoryUpdateRequest 카테고리_수정_요청 = new CategoryUpdateRequest(BE_일정_이름); + + // when & then + mockMvc.perform(patch("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .content(objectMapper.writeValueAsString(카테고리_수정_요청)) + ) + .andDo(print()) + .andDo(document("categories/update", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isNoContent()); + } + + @DisplayName("카테고리 수정 시 존재하지 않으면 404 Not Found가 발생한다.") + @Test + void 카테고리_수정_시_존재하지_않으면_404_Not_Found를_반환한다() throws Exception { + // given + Long categoryId = 1L; + willThrow(NoSuchCategoryException.class) + .willDoNothing() + .given(categoryService) + .update(any(), any(), any()); + CategoryUpdateRequest 카테고리_수정_요청 = new CategoryUpdateRequest(BE_일정_이름); + + // when & then + mockMvc.perform(patch("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .content(objectMapper.writeValueAsString(카테고리_수정_요청)) + ) + .andDo(print()) + .andDo(document("categories/update/notFound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isNotFound()); + } + + @DisplayName("잘못된 이름 형식으로 카테고리를 수정하면 400 Bad Request가 발생한다.") + @Test + void 잘못된_이름_형식으로_카테고리를_수정하면_400_Bad_Request가_발생한다() throws Exception { + // given + Long categoryId = 1L; + willThrow(new InvalidCategoryException(CATEGORY_NAME_OVER_LENGTH_EXCEPTION_MESSAGE)) + .willDoNothing() + .given(categoryService) + .update(any(), any(), any()); + CategoryUpdateRequest 카테고리_수정_요청 = new CategoryUpdateRequest(INVALID_CATEGORY_NAME); + + // when & then + mockMvc.perform(patch("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .content(objectMapper.writeValueAsString(카테고리_수정_요청)) + ) + .andDo(print()) + .andDo(document("categories/update/badRequest", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isBadRequest()); + } + + @DisplayName("카테고리를 제거한다.") + @Test + void 카테고리를_제거한다() throws Exception { + // given + Long categoryId = 1L; + willDoNothing() + .given(categoryService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(delete("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + ) + .andDo(print()) + .andDo(document("categories/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isNoContent()); + } + + @DisplayName("카테고리 제거 시 존재하지 않으면 404 Not Found가 발생한다") + @Test + void 카테고리_제거_시_존재하지_않으면_404_Not_Found가_발생한다() throws Exception { + // given + Long categoryId = 1L; + willThrow(new NoSuchCategoryException("존재하지 않는 카테고리를 삭제할 수 없습니다.")) + .willDoNothing() + .given(categoryService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(delete("/api/categories/{categoryId}", categoryId) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + ) + .andDo(print()) + .andDo(document("categories/delete/notFound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ) + ) + ) + .andExpect(status().isNotFound()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java new file mode 100644 index 00000000..fac37efe --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/ControllerTest.java @@ -0,0 +1,19 @@ +package com.allog.dallog.presentation; + +import com.allog.dallog.common.config.ExternalApiConfig; +import com.fasterxml.jackson.databind.ObjectMapper; +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.web.servlet.MockMvc; + +@AutoConfigureRestDocs +@Import(ExternalApiConfig.class) +abstract class ControllerTest { + + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java new file mode 100644 index 00000000..14fde50a --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/ExternalCalendarControllerTest.java @@ -0,0 +1,134 @@ +package com.allog.dallog.presentation; + +import static com.allog.dallog.common.fixtures.CategoryFixtures.공통_일정_응답; +import static com.allog.dallog.common.fixtures.ExternalCalendarFixtures.대한민국_공휴일; +import static com.allog.dallog.common.fixtures.ExternalCalendarFixtures.우아한테크코스; +import static com.allog.dallog.common.fixtures.ExternalCategoryFixtures.우아한테크코스_생성_요청; +import static com.allog.dallog.common.fixtures.MemberFixtures.후디_응답; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +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.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.category.dto.request.ExternalCategoryCreateRequest; +import com.allog.dallog.domain.category.exception.DuplicatedExternalCategoryException; +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; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +@WebMvcTest(ExternalCalendarController.class) +class ExternalCalendarControllerTest 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 ExternalCalendarService externalCalendarService; + + @MockBean + private CategorySubscriptionService categorySubscriptionService; + + @DisplayName("외부 캘린더의 일정을 조회하면 상태코드 200을 반환한다.") + @Test + void 외부_캘린더의_일정을_조회하면_상태코드_200을_반환한다() throws Exception { + // given + List ExternalCalendars = List.of(대한민국_공휴일, 우아한테크코스, 대한민국_공휴일); + given(externalCalendarService.findByMemberId(any())).willReturn( + new ExternalCalendarsResponse(ExternalCalendars)); + + // when & then + mockMvc.perform(get("/api/external-calendars/me") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("external-calendars/get", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ) + )) + .andExpect(status().isOk()); + } + + @DisplayName("외부 캘린더를 카테고리로 저장하면 상태코드 201을 반환한다.") + @Test + void 외부_캘린더를_카테고리로_저장하면_상태코드_201을_반환한다() throws Exception { + // given + given(categorySubscriptionService.save(any(), any(ExternalCategoryCreateRequest.class))).willReturn( + 공통_일정_응답(후디_응답)); + + // when & then + mockMvc.perform(post("/api/external-calendars/me") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) + ) + .andDo(print()) + .andDo(document("external-calendars/save", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ), + requestFields( + fieldWithPath("externalId").type(JsonFieldType.STRING).description("외부 캘린더 id"), + fieldWithPath("name").type(JsonFieldType.STRING).description("캘린더 이름") + ))) + .andExpect(status().isCreated()); + } + + @DisplayName("외부 캘린더를 카테고리로 저장하면 상태코드 201을 반환한다.") + @Test + void 외부_캘린더를_중복하여_저장하면_상태코드_400을_반환한다() throws Exception { + // given + willThrow(new DuplicatedExternalCategoryException()) + .given(categorySubscriptionService) + .save(any(), any(ExternalCategoryCreateRequest.class)); + + // when & then + mockMvc.perform(post("/api/external-calendars/me") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(우아한테크코스_생성_요청)) + ) + .andDo(print()) + .andDo(document("external-calendars/duplicated-save", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ), + requestFields( + fieldWithPath("externalId").type(JsonFieldType.STRING).description("외부 캘린더 id"), + fieldWithPath("name").type(JsonFieldType.STRING).description("캘린더 이름") + ))) + .andExpect(status().isBadRequest()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java new file mode 100644 index 00000000..d060262d --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/MemberControllerTest.java @@ -0,0 +1,148 @@ +package com.allog.dallog.presentation; + +import static com.allog.dallog.common.fixtures.AuthFixtures.더미_엑세스_토큰; +import static com.allog.dallog.common.fixtures.AuthFixtures.토큰_정보; +import static com.allog.dallog.common.fixtures.MemberFixtures.파랑_응답; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +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.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; +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; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +@WebMvcTest(MemberController.class) +class MemberControllerTest 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 MemberService memberService; + + @MockBean + private RegisterService registerService; + + @DisplayName("자신의 회원 정보를 조회한다.") + @Test + void 자신의_회원_정보를_조회한다() throws Exception { + //given + given(memberService.findById(파랑_응답.getId())).willReturn(파랑_응답); + given(authService.extractMemberId(더미_엑세스_토큰)).willReturn(파랑_응답.getId()); + + // when & then + mockMvc.perform(get("/api/members/me") + .header(AUTHORIZATION_HEADER_NAME, 토큰_정보) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("members/me", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ) + )) + .andExpect(status().isOk()); + } + + @DisplayName("존재하지 않는 회원의 정보를 조회하려고 하면 예외를 발생한다.") + @Test + void 존재하지_않는_회원의_정보를_조회하려고_하면_예외를_발생한다() throws Exception { + // given + given(memberService.findById(0L)).willThrow(new NoSuchMemberException()); + + // when & then + mockMvc.perform(get("/api/members/me") + .header(AUTHORIZATION_HEADER_NAME, 토큰_정보) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + ) + .andDo(print()) + .andDo(document("members/exception/notfound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ) + )) + .andExpect(status().isNotFound()); + } + + @DisplayName("등록된 회원이 자신의 이름을 수정한다.") + @Test + void 등록된_회원이_자신의_이름을_수정한다() throws Exception { + // given + willDoNothing() + .given(memberService) + .update(any(), any()); + MemberUpdateRequest 회원_수정_요청 = new MemberUpdateRequest("패트"); + + // when & then + mockMvc.perform(patch("/api/members/me") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .content(objectMapper.writeValueAsString(회원_수정_요청)) + ) + .andDo(print()) + .andDo(document("members/update", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ), + requestFields( + fieldWithPath("displayName").type(JsonFieldType.STRING).description("수정할 이름") + ))) + .andExpect(status().isNoContent()); + } + + @DisplayName("등록된 회원이 회원탈퇴 한다.") + @Test + void 등록된_회원이_회원탈퇴_한다() throws Exception { + // given + willDoNothing() + .given(registerService) + .deleteByMemberId(any()); + + // when & then + mockMvc.perform(delete("/api/members/me") + .accept(MediaType.APPLICATION_JSON) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + ) + .andDo(print()) + .andDo(document("members/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isNoContent()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java new file mode 100644 index 00000000..07e224ef --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/ScheduleControllerTest.java @@ -0,0 +1,368 @@ +package com.allog.dallog.presentation; + +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 org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willThrow; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +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.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +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.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +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.dto.request.ScheduleCreateRequest; +import com.allog.dallog.domain.schedule.dto.request.ScheduleUpdateRequest; +import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponse; +import com.allog.dallog.domain.schedule.dto.response.MemberScheduleResponses; +import com.allog.dallog.domain.schedule.exception.NoSuchScheduleException; +import com.allog.dallog.domain.subscription.domain.Color; +import java.time.LocalDateTime; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; + +@WebMvcTest(ScheduleController.class) +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 IntegrationScheduleDao integrationScheduleDao; + + @MockBean + private ExternalCalendarClient externalCalendarClient; + + @DisplayName("일정 정보를 등록하면 상태코드 201을 반환한다.") + @Test + void 일정_정보를_등록하면_상태코드_201을_반환한다() throws Exception { + // given + Long categoryId = 1L; + ScheduleCreateRequest request = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); + + given(scheduleService.save(any(), any(), any())).willReturn(알록달록_회의_응답); + + // when & then + mockMvc.perform(post("/api/categories/{categoryId}/schedules", categoryId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("schedules/save", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isCreated()); + } + + @DisplayName("일정 정보를 등록할때 해당 카테고리에 권한이 없으면 403을 반환한다.") + @Test + void 일정_정보를_등록할때_해당_카테고리에_권한이_없으면_403을_반환한다() throws Exception { + // given + Long categoryId = 1L; + ScheduleCreateRequest request = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); + + given(scheduleService.save(any(), any(), any())).willThrow(new NoPermissionException()); + + // when & then + mockMvc.perform(post("/api/categories/{categoryId}/schedules", categoryId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("schedules/save/forbidden", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isForbidden()); + } + + @DisplayName("일정 생성시 전달한 카테고리가 존재하지 않는다면 404를 반환한다.") + @Test + void 일정_생성시_전달한_카테고리가_존재하지_않는다면_404를_반환한다() throws Exception { + // given + Long categoryId = 0L; + ScheduleCreateRequest request = new ScheduleCreateRequest(알록달록_회의_제목, 알록달록_회의_시작일시, 알록달록_회의_종료일시, 알록달록_회의_메모); + + given(scheduleService.save(any(), any(), any())).willThrow(new NoSuchCategoryException()); + + // when & then + mockMvc.perform(post("/api/categories/{categoryId}/schedules", categoryId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("schedules/save/notfound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isNotFound()); + } + + @DisplayName("일정을 단건 조회 하면 상태코드 200을 반환한다") + @Test + void 일정을_단건_조회_하면_상태코드_200을_반환한다() throws Exception { + // given + Long scheduleId = 1L; + + given(scheduleService.findById(scheduleId)).willReturn(알록달록_회의_응답); + + // when & then + mockMvc.perform(get("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("schedules/findone", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isOk()); + } + + @DisplayName("일정을 단건 조회 할 때 일정이 존재하지 않으면 상태코드 404를 반환한다.") + @Test + void 일정을_단건_조회_할_때_일정이_존재하지_않으면_상태코드_404를_반환한다() throws Exception { + // given + Long scheduleId = 1L; + + given(scheduleService.findById(scheduleId)).willThrow(new NoSuchScheduleException()); + + // when & then + mockMvc.perform(get("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("schedules/findone/notfound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isNotFound()); + } + + + @DisplayName("일정을 수정하는데 성공하면 204를 반환한다.") + @Test + void 일정을_수정하는데_성공하면_204를_반환한다() throws Exception { + // given + Long scheduleId = 1L; + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + willDoNothing() + .given(scheduleService) + .update(any(), any(), any()); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.patch("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(수정_요청))) + .andDo(print()) + .andDo(document("schedules/update", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("scheduleId").description("일정 ID") + ) + )) + .andExpect(status().isNoContent()); + } + + @DisplayName("일정을 수정하는데 해당 일정의 카테고리에 대한 권한이 없다면 403을 반환한다.") + @Test + void 일정을_수정하는데_해당_일정의_카테고리에_대한_권한이_없다면_403을_반환한다() throws Exception { + // given + Long scheduleId = 1L; + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + willThrow(new NoPermissionException()) + .given(scheduleService) + .update(any(), any(), any()); + + // when & then + mockMvc.perform(patch("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(수정_요청))) + .andDo(print()) + .andDo(document("schedules/update/forbidden", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isForbidden()); + } + + @DisplayName("일정을 수정하는데 일정이 존재하지 않는 경우 404를 반환한다") + @Test + void 일정을_수정하는데_일정이_존재하지_않는_경우_404를_반환한다() throws Exception { + // given + Long scheduleId = 1L; + ScheduleUpdateRequest 수정_요청 = new ScheduleUpdateRequest(레벨_인터뷰_제목, 레벨_인터뷰_시작일시, 레벨_인터뷰_종료일시, 레벨_인터뷰_메모); + willThrow(new NoSuchScheduleException()) + .given(scheduleService) + .update(any(), any(), any()); + + // when & then + mockMvc.perform(patch("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(수정_요청))) + .andDo(print()) + .andDo(document("schedules/update/notfound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isNotFound()); + } + + @DisplayName("일정을 제거하는데 성공하면 204를 반환한다.") + @Test + void 일정을_제거하는데_성공하면_204를_반환한다() throws Exception { + // given + Long scheduleId = 1L; + willDoNothing() + .given(scheduleService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.delete("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) + .andDo(print()) + .andDo(document("schedules/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("scheduleId").description("일정 ID") + ) + )) + .andExpect(status().isNoContent()); + } + + @DisplayName("일정을 제거하는데 해당 일정의 카테고리에 대한 권한이 없다면 403을 반환한다.") + @Test + void 일정을_제거하는데_해당_일정의_카테고리에_대한_권한이_없다면_403을_반환한다() throws Exception { + // given + Long scheduleId = 1L; + willThrow(new NoPermissionException()) + .given(scheduleService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) + .andDo(print()) + .andDo(document("schedules/delete/forbidden", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isForbidden()); + } + + @DisplayName("일정을 제거하는데 일정이 존재하지 않는 경우 404를 반환한다") + @Test + void 일정을_제거하는데_일정이_존재하지_않는_경우_404를_반환한다() throws Exception { + // given + Long scheduleId = 1L; + willThrow(new NoSuchScheduleException()) + .given(scheduleService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(delete("/api/schedules/{scheduleId}", scheduleId) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) + .andDo(print()) + .andDo(document("schedules/delete/notfound", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()) + )) + .andExpect(status().isNotFound()); + } + + @DisplayName("회원의 일정 목록을 정상적으로 조회하면 200을 반환한다.") + @Test + void 회원의_일정_목록을_정상적으로_조회하면_200을_반환한다() throws Exception { + // given + String startDate = "2022-07-31T00:00"; + String endDate = "2022-09-03T00:00"; + + MemberScheduleResponse 장기간_일정_1 = new MemberScheduleResponse("1L", "장기간 일정 1", + LocalDateTime.of(2022, 8, 1, 0, 0), + LocalDateTime.of(2022, 8, 3, 0, 0), "장기간 일정 1의 메모", 1L, Color.COLOR_1.getColorCode(), "NORMAL"); + MemberScheduleResponse 장기간_일정_2 = new MemberScheduleResponse("1L", "장기간 일정 2", + LocalDateTime.of(2022, 8, 3, 0, 0), + LocalDateTime.of(2022, 8, 10, 0, 0), "장기간 일정 2의 메모", 3L, Color.COLOR_2.getColorCode(), "NORMAL"); + + MemberScheduleResponse 종일_일정_1 = new MemberScheduleResponse("1L", "종일 일정 1", LocalDateTime.of(2022, 8, 1, 0, 0), + LocalDateTime.of(2022, 8, 1, 23, 59), "종일 일정 1의 메모", 1L, Color.COLOR_3.getColorCode(), "NORMAL"); + MemberScheduleResponse 종일_일정_2 = new MemberScheduleResponse("1L", "종일 일정 2", LocalDateTime.of(2022, 8, 5, 0, 0), + LocalDateTime.of(2022, 8, 5, 23, 59), "종일 일정 2의 메모", 3L, Color.COLOR_4.getColorCode(), "NORMAL"); + + MemberScheduleResponse 짧은_일정_1 = new MemberScheduleResponse("1L", "짧은 일정 1", LocalDateTime.of(2022, 8, 1, 0, 0), + LocalDateTime.of(2022, 8, 1, 1, 0), "짧은 일정 1의 메모", 1L, Color.COLOR_5.getColorCode(), "NORMAL"); + MemberScheduleResponse 짧은_일정_2 = new MemberScheduleResponse("1L", "짧은 일정 2", + LocalDateTime.of(2022, 8, 5, 17, 0), + LocalDateTime.of(2022, 8, 5, 19, 0), "짧은 일정 2의 메모", 3L, Color.COLOR_6.getColorCode(), "NORMAL"); + + MemberScheduleResponses memberScheduleResponses = new MemberScheduleResponses(List.of(장기간_일정_1, 장기간_일정_2), + List.of(종일_일정_1, 종일_일정_2), List.of(짧은_일정_1, 짧은_일정_2)); + + given(calendarService.findSchedulesByMemberId(any(), any())) + .willReturn(memberScheduleResponses); + + // when & then + mockMvc.perform( + get("/api/members/me/schedules?startDateTime={startDate}&endDateTime={endDate}", startDate, endDate) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) + .andDo(print()) + .andDo(document("schedules/findAllByMember", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestParameters( + parameterWithName("startDateTime").description("일정 조회 시작 범위 (yyyy-mm-dd'T'HH:mm)"), + parameterWithName("endDateTime").description("일정 조회 마지막 범위 (yyyy-mm-dd'T'HH:mm)") + ) + )) + .andExpect(status().isOk()); + } +} diff --git a/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java new file mode 100644 index 00000000..85c174a9 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/SchedulerControllerTest.java @@ -0,0 +1,81 @@ +package com.allog.dallog.presentation; + +import static com.allog.dallog.common.fixtures.ScheduleFixtures.날짜_2022년_7월_10일_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일_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월_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월_7일_16시_0분; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +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.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.restdocs.request.RequestDocumentation.requestParameters; +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.composition.application.SchedulerService; +import com.allog.dallog.domain.integrationschedule.domain.Period; +import com.allog.dallog.domain.schedule.dto.response.PeriodResponse; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; + +@WebMvcTest(SchedulerController.class) +class SchedulerControllerTest 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 SchedulerService schedulerService; + + @DisplayName("일정 조율 결과를 반환한다.") + @Test + void 일정_조율_결과를_반환한다() throws Exception { + // given + String startDateTime = "2022-07-01T00:00"; + String endDateTime = "2022-07-31T00:00"; + + given(schedulerService.getAvailablePeriods(any(), any())) + .willReturn(List.of( + new PeriodResponse(new Period(날짜_2022년_7월_7일_16시_0분, 날짜_2022년_7월_10일_0시_0분)), + new PeriodResponse(new Period(날짜_2022년_7월_15일_16시_0분, 날짜_2022년_7월_16일_16시_0분)), + new PeriodResponse(new Period(날짜_2022년_7월_16일_18시_0분, 날짜_2022년_7월_16일_20시_0분)), + new PeriodResponse(new Period(날짜_2022년_7월_20일_11시_59분, 날짜_2022년_7월_27일_0시_0분)) + )); + + // when & then + mockMvc.perform(RestDocumentationRequestBuilders.get( + "/api/scheduler/categories/{categoryId}/available-periods?startDateTime={startDateTime}&endDateTime={endDateTime}", + 1L, startDateTime, endDateTime) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE)) + .andDo(print()) + .andDo(document("scheduler/category/available-periods", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 ID") + ), + requestParameters( + parameterWithName("startDateTime").description("일정 조회 시작 범위 (yyyy-mm-dd'T'HH:mm)"), + parameterWithName("endDateTime").description("일정 조회 마지막 범위 (yyyy-mm-dd'T'HH:mm)") + ) + )) + .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 new file mode 100644 index 00000000..06bb1753 --- /dev/null +++ b/backend/src/test/java/com/allog/dallog/presentation/SubscriptionControllerTest.java @@ -0,0 +1,282 @@ +package com.allog.dallog.presentation; + +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.SubscriptionFixtures.색상1_구독_응답; +import static com.allog.dallog.common.fixtures.SubscriptionFixtures.색상2_구독_응답; +import static com.allog.dallog.common.fixtures.SubscriptionFixtures.색상3_구독_응답; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.BDDMockito.willThrow; +import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; +import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +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.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +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.dto.response.CategoryResponse; +import com.allog.dallog.domain.subscription.application.SubscriptionService; +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; +import com.allog.dallog.domain.subscription.dto.response.SubscriptionsResponse; +import com.allog.dallog.domain.subscription.exception.ExistSubscriptionException; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.restdocs.payload.JsonFieldType; + +@WebMvcTest(SubscriptionController.class) +class SubscriptionControllerTest extends ControllerTest { + + private static final String AUTHORIZATION_HEADER_NAME = "Authorization"; + private static final String AUTHORIZATION_HEADER_VALUE = "Bearer aaaaa.bbbbb.ccccc"; + + @MockBean + private AuthService authService; + + @MockBean + private SubscriptionService subscriptionService; + + @DisplayName("회원과 카테고리 정보를 기반으로 구독한다.") + @Test + void 회원과_카테고리_정보를_기반으로_구독한다() throws Exception { + // given + CategoryResponse 공통_일정_응답 = 공통_일정_응답(관리자_응답); + SubscriptionResponse 색상1_구독_응답 = 색상1_구독_응답(공통_일정_응답); + + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + given(subscriptionService.save(any(), any())).willReturn(색상1_구독_응답); + + // when & then + mockMvc.perform(post("/api/members/me/categories/{categoryId}/subscriptions", 공통_일정_응답.getId()) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/save", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .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 { + // given + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + given(subscriptionService.save(any(), any())).willThrow(new ExistSubscriptionException()); + + // when & then + mockMvc.perform( + post("/api/members/me/categories/{categoryId}/subscriptions", 1L) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/exist", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isBadRequest()); + } + + @DisplayName("타인의 개인 카테고리 구독 요청시 403 Forbidden을 반환한다.") + @Test + void 타인의_개인_카테고리_구독_요청시_403_Forbidden을_반환한다() throws Exception { + // given + given(subscriptionService.save(any(), any())) + .willThrow(new NoPermissionException("구독 권한이 없는 카테고리입니다.")); + + // when & then + mockMvc.perform( + post("/api/members/me/categories/{categoryId}/subscriptions", 1L) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/private-category", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("categoryId").description("카테고리 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isForbidden()); + } + + @DisplayName("자신의 구독 정보를 조회한다.") + @Test + void 자신의_구독_정보를_조회한다() throws Exception { + // given + CategoryResponse 공통_일정_응답 = 공통_일정_응답(관리자_응답); + CategoryResponse BE_일정_응답 = BE_일정_응답(관리자_응답); + CategoryResponse FE_일정_응답 = FE_일정_응답(관리자_응답); + + SubscriptionResponse 색상1_구독_응답 = 색상1_구독_응답(공통_일정_응답); + SubscriptionResponse 색상2_구독_응답 = 색상2_구독_응답(BE_일정_응답); + SubscriptionResponse 색상3_구독_응답 = 색상3_구독_응답(FE_일정_응답); + + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + + List subscriptionResponses = List.of(색상1_구독_응답, 색상2_구독_응답, 색상3_구독_응답); + given(subscriptionService.findByMemberId(any())).willReturn(new SubscriptionsResponse(subscriptionResponses)); + + // when & then + mockMvc.perform(get("/api/members/me/subscriptions") + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/me", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isOk()); + } + + @DisplayName("자신의 구독 정보를 수정한다.") + @Test + void 자신의_구독_정보를_수정한다() throws Exception { + // given + CategoryResponse 공통_일정_응답 = 공통_일정_응답(관리자_응답); + SubscriptionResponse 색상1_구독_응답 = 색상1_구독_응답(공통_일정_응답); + SubscriptionUpdateRequest request = new SubscriptionUpdateRequest(Color.COLOR_2, true); + + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + willDoNothing().given(subscriptionService) + .update(색상1_구독_응답.getId(), 매트_응답.getId(), request); + + // when & then + mockMvc.perform(patch("/api/members/me/subscriptions/{subscriptionId}", 색상1_구독_응답.getId()) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andDo(document("subscription/update", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("subscriptionId").description("구독 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ), + requestFields( + fieldWithPath("colorCode").type(JsonFieldType.STRING).description("구독 색 정보"), + fieldWithPath("checked").type(JsonFieldType.BOOLEAN).description("체크 유무") + ))) + .andExpect(status().isNoContent()); + } + + @DisplayName("구독 id를 기반으로 자신의 구독 정보를 삭제한다.") + @Test + void 구독_id를_기반으로_자신의_구독_정보를_삭제한다() throws Exception { + // given + CategoryResponse 공통_일정_응답 = 공통_일정_응답(관리자_응답); + SubscriptionResponse 색상1_구독_응답 = 색상1_구독_응답(공통_일정_응답); + + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + willDoNothing().given(subscriptionService) + .deleteById(색상1_구독_응답.getId(), 매트_응답.getId()); + + // when & then + mockMvc.perform(delete("/api/members/me/subscriptions/{subscriptionId}", 색상1_구독_응답.getId()) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/delete", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("subscriptionId").description("구독 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isNoContent()); + } + + @DisplayName("자신이 가지고 있지 않은 구독 정보인 경우 예외를 던진다.") + @Test + void 자신이_가지고_있지_않은_구독_정보인_경우_예외를_던진다() throws Exception { + // given + given(authService.extractMemberId(any())).willReturn(매트_응답.getId()); + willThrow(new NoPermissionException()) + .willDoNothing() + .given(subscriptionService) + .deleteById(any(), any()); + + // when & then + mockMvc.perform(delete("/api/members/me/subscriptions/{subscriptionId}", 1L) + .header(AUTHORIZATION_HEADER_NAME, AUTHORIZATION_HEADER_VALUE) + .contentType(MediaType.APPLICATION_JSON)) + .andDo(print()) + .andDo(document("subscription/permission", + preprocessRequest(prettyPrint()), + preprocessResponse(prettyPrint()), + pathParameters( + parameterWithName("subscriptionId").description("구독 id") + ), + requestHeaders( + headerWithName("Authorization").description("JWT 토큰") + ))) + .andExpect(status().isForbidden()); + } +} diff --git a/backend/src/test/resources/application.yml b/backend/src/test/resources/application.yml new file mode 100644 index 00000000..bbb8ec55 --- /dev/null +++ b/backend/src/test/resources/application.yml @@ -0,0 +1,51 @@ +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 + redirect-uri: http://localhost:3000 + 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/~/Desktop/jacoco/index.xml b/backend/~/Desktop/jacoco/index.xml new file mode 100644 index 00000000..bde0fd01 --- /dev/null +++ b/backend/~/Desktop/jacoco/index.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json new file mode 100644 index 00000000..c1e6f3c9 --- /dev/null +++ b/frontend/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "react-hooks", "prettier"], + "extends": [ + "plugin:react/recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/recommended", + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" + ], + "rules": { + "react/react-in-jsx-scope": "off", + "react-hooks/rules-of-hooks": "error", + "react-hooks/exhaustive-deps": "warn", + "import/named": "off", + "import/no-unresolved": "off", + "@typescript-eslint/no-var-requires": "off" + } +} diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..9c97bbd4 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.env diff --git a/frontend/.prettierrc.json b/frontend/.prettierrc.json new file mode 100644 index 00000000..ca2ae640 --- /dev/null +++ b/frontend/.prettierrc.json @@ -0,0 +1,27 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "always", + "endOfLine": "auto", + "importOrder": [ + "@/hooks/(.*)$", + "@/@types", + "@/recoil/(atoms|selectors)", + "@/styles/(.*)$", + "@/(components/@common/|components/|pages/)(.*)$", + "@/constants", + "@/utils", + "@/(api|mocks)/(.*)$", + "react-icons", + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "importOrderCaseInsensitive": true +} diff --git a/frontend/.storybook/main.js b/frontend/.storybook/main.js new file mode 100644 index 00000000..b640a76b --- /dev/null +++ b/frontend/.storybook/main.js @@ -0,0 +1,23 @@ +const path = require('path'); + +module.exports = { + stories: ['../src/**/*.stories.@(tsx)'], + addons: [ + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + ], + framework: '@storybook/react', + core: { + builder: '@storybook/builder-webpack5', + }, + webpackFinal: async (config) => { + config.resolve.alias = { + ...config.resolve.alias, + '@': path.resolve(__dirname, '../src/'), + }; + config.resolve.extensions.push('.ts', '.tsx'); + + return config; + }, +}; diff --git a/frontend/.storybook/preview-body.html b/frontend/.storybook/preview-body.html new file mode 100644 index 00000000..508dda40 --- /dev/null +++ b/frontend/.storybook/preview-body.html @@ -0,0 +1,5 @@ + diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js new file mode 100644 index 00000000..6c3fa7db --- /dev/null +++ b/frontend/.storybook/preview.js @@ -0,0 +1,35 @@ +import { ThemeProvider } from '@emotion/react'; +import { QueryClient, QueryClientProvider } from 'react-query'; +import { BrowserRouter as Router } from 'react-router-dom'; +import { RecoilRoot } from 'recoil'; + +import GlobalStyle from '../src/styles/GlobalStyle'; +import theme from '../src/styles/theme'; + +const queryClient = new QueryClient(); + +export const parameters = { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, +}; + +export const decorators = [ + (Story) => ( + + + + + + + + + + + + ), +]; diff --git a/frontend/babel.config.js b/frontend/babel.config.js new file mode 100644 index 00000000..32315f68 --- /dev/null +++ b/frontend/babel.config.js @@ -0,0 +1,9 @@ +module.exports = { + presets: [ + '@babel/preset-react', + '@babel/preset-env', + '@babel/preset-typescript', + '@emotion/babel-preset-css-prop', + ], + plugins: ['@emotion'], +}; diff --git a/frontend/jest.config.js b/frontend/jest.config.js new file mode 100644 index 00000000..5cea0750 --- /dev/null +++ b/frontend/jest.config.js @@ -0,0 +1,6 @@ +/** @type {import('@jest/types').Config.InitialOptions} */ +const config = { + verbose: true, +}; + +module.exports = config; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..4d793cf9 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,81 @@ +{ + "name": "dallog", + "version": "2.0.0", + "description": "share calendar dallog", + "main": "index.js", + "scripts": { + "dev": "webpack serve --mode development --open --hot", + "prod-build": "webpack --mode production", + "dev-build": "webpack --mode development", + "storybook": "start-storybook -p 6006", + "build-storybook": "build-storybook", + "pretty": "prettier . --write", + "check-lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "check-prettier": "prettier -c ./src", + "test": "jest --setupFiles ./setupFile.js" + }, + "dependencies": { + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", + "axios": "^0.27.2", + "dotenv-webpack": "^8.0.0", + "emotion-reset": "^3.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-query": "^3.39.1", + "react-router-dom": "^6.3.0", + "recoil": "^0.7.4" + }, + "devDependencies": { + "@babel/core": "^7.18.6", + "@babel/preset-env": "^7.18.6", + "@babel/preset-react": "^7.18.6", + "@babel/preset-typescript": "^7.18.6", + "@emotion/babel-plugin": "^11.9.2", + "@emotion/babel-preset-css-prop": "^11.2.0", + "@storybook/addon-actions": "^6.5.9", + "@storybook/addon-essentials": "^6.5.9", + "@storybook/addon-interactions": "^6.5.9", + "@storybook/addon-links": "^6.5.9", + "@storybook/builder-webpack5": "^6.5.9", + "@storybook/manager-webpack5": "^6.5.9", + "@storybook/react": "^6.5.9", + "@storybook/testing-library": "^0.0.13", + "@storybook/testing-react": "^1.3.0", + "@testing-library/react": "^13.3.0", + "@trivago/prettier-plugin-sort-imports": "^3.2.0", + "@types/body-parser": "^1.19.2", + "@types/graceful-fs": "^4.1.5", + "@types/jest": "^28.1.6", + "@types/node": "^18.0.1", + "@types/react": "^18.0.14", + "@types/react-dom": "^18.0.5", + "@typescript-eslint/eslint-plugin": "^5.30.4", + "@typescript-eslint/parser": "^5.30.4", + "babel-loader": "^8.2.5", + "clean-webpack-plugin": "^4.0.0", + "css-loader": "^6.7.1", + "eslint": "^8.19.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-import": "^2.26.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.30.1", + "eslint-plugin-react-hooks": "^4.6.0", + "file-loader": "^6.2.0", + "html-webpack-plugin": "^5.5.0", + "jest": "^28.1.3", + "jest-environment-jsdom": "^28.1.3", + "msw": "^0.43.0", + "prettier": "^2.7.1", + "react-icons": "^4.4.0", + "style-loader": "^3.3.1", + "ts-loader": "^9.3.1", + "typescript": "^4.7.4", + "webpack": "^5.73.0", + "webpack-cli": "^4.10.0", + "webpack-dev-server": "^4.9.3" + }, + "msw": { + "workerDirectory": "public" + } +} diff --git a/frontend/public/mockServiceWorker.js b/frontend/public/mockServiceWorker.js new file mode 100644 index 00000000..a5fbb289 --- /dev/null +++ b/frontend/public/mockServiceWorker.js @@ -0,0 +1,364 @@ +/* eslint-disable */ +/* tslint:disable */ + +/** + * Mock Service Worker (0.43.0). + * @see https://github.com/mswjs/msw + * - Please do NOT modify this file. + * - Please do NOT serve this file on production. + */ + +const INTEGRITY_CHECKSUM = 'c9450df6e4dc5e45740c3b0b640727a2'; +const activeClientIds = new Set(); + +self.addEventListener('install', function () { + self.skipWaiting(); +}); + +self.addEventListener('activate', function (event) { + event.waitUntil(self.clients.claim()); +}); + +self.addEventListener('message', async function (event) { + const clientId = event.source.id; + + if (!clientId || !self.clients) { + return; + } + + const client = await self.clients.get(clientId); + + if (!client) { + return; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + switch (event.data) { + case 'KEEPALIVE_REQUEST': { + sendToClient(client, { + type: 'KEEPALIVE_RESPONSE', + }); + break; + } + + case 'INTEGRITY_CHECK_REQUEST': { + sendToClient(client, { + type: 'INTEGRITY_CHECK_RESPONSE', + payload: INTEGRITY_CHECKSUM, + }); + break; + } + + case 'MOCK_ACTIVATE': { + activeClientIds.add(clientId); + + sendToClient(client, { + type: 'MOCKING_ENABLED', + payload: true, + }); + break; + } + + case 'MOCK_DEACTIVATE': { + activeClientIds.delete(clientId); + break; + } + + case 'CLIENT_CLOSED': { + activeClientIds.delete(clientId); + + const remainingClients = allClients.filter((client) => { + return client.id !== clientId; + }); + + // Unregister itself when there are no more clients + if (remainingClients.length === 0) { + self.registration.unregister(); + } + + break; + } + } +}); + +self.addEventListener('fetch', function (event) { + const { request } = event; + const accept = request.headers.get('accept') || ''; + + // Bypass server-sent events. + if (accept.includes('text/event-stream')) { + return; + } + + // Bypass navigation requests. + if (request.mode === 'navigate') { + return; + } + + // Opening the DevTools triggers the "only-if-cached" request + // that cannot be handled by the worker. Bypass such requests. + if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') { + return; + } + + // Bypass all requests when there are no active clients. + // Prevents the self-unregistered worked from handling requests + // after it's been deleted (still remains active until the next reload). + if (activeClientIds.size === 0) { + return; + } + + // Generate unique request ID. + const requestId = Math.random().toString(16).slice(2); + + event.respondWith( + handleRequest(event, requestId).catch((error) => { + if (error.name === 'NetworkError') { + console.warn( + '[MSW] Successfully emulated a network error for the "%s %s" request.', + request.method, + request.url + ); + return; + } + + // At this point, any exception indicates an issue with the original request/response. + console.error( + `\ +[MSW] Caught an exception from the "%s %s" request (%s). This is probably not a problem with Mock Service Worker. There is likely an additional logging output above.`, + request.method, + request.url, + `${error.name}: ${error.message}` + ); + }) + ); +}); + +async function handleRequest(event, requestId) { + const client = await resolveMainClient(event); + const response = await getResponse(event, client, requestId); + + // Send back the response clone for the "response:*" life-cycle events. + // Ensure MSW is active and ready to handle the message, otherwise + // this message will pend indefinitely. + if (client && activeClientIds.has(client.id)) { + (async function () { + const clonedResponse = response.clone(); + sendToClient(client, { + type: 'RESPONSE', + payload: { + requestId, + type: clonedResponse.type, + ok: clonedResponse.ok, + status: clonedResponse.status, + statusText: clonedResponse.statusText, + body: clonedResponse.body === null ? null : await clonedResponse.text(), + headers: Object.fromEntries(clonedResponse.headers.entries()), + redirected: clonedResponse.redirected, + }, + }); + })(); + } + + return response; +} + +// Resolve the main client for the given event. +// Client that issues a request doesn't necessarily equal the client +// that registered the worker. It's with the latter the worker should +// communicate with during the response resolving phase. +async function resolveMainClient(event) { + const client = await self.clients.get(event.clientId); + + if (client.frameType === 'top-level') { + return client; + } + + const allClients = await self.clients.matchAll({ + type: 'window', + }); + + return allClients + .filter((client) => { + // Get only those clients that are currently visible. + return client.visibilityState === 'visible'; + }) + .find((client) => { + // Find the client ID that's recorded in the + // set of clients that have registered the worker. + return activeClientIds.has(client.id); + }); +} + +async function getResponse(event, client, requestId) { + const { request } = event; + const clonedRequest = request.clone(); + + function passthrough() { + // Clone the request because it might've been already used + // (i.e. its body has been read and sent to the cilent). + const headers = Object.fromEntries(clonedRequest.headers.entries()); + + // Remove MSW-specific request headers so the bypassed requests + // comply with the server's CORS preflight check. + // Operate with the headers as an object because request "Headers" + // are immutable. + delete headers['x-msw-bypass']; + + return fetch(clonedRequest, { headers }); + } + + // Bypass mocking when the client is not active. + if (!client) { + return passthrough(); + } + + // Bypass initial page load requests (i.e. static assets). + // The absence of the immediate/parent client in the map of the active clients + // means that MSW hasn't dispatched the "MOCK_ACTIVATE" event yet + // and is not ready to handle requests. + if (!activeClientIds.has(client.id)) { + return passthrough(); + } + + // Bypass requests with the explicit bypass header. + // Such requests can be issued by "ctx.fetch()". + if (request.headers.get('x-msw-bypass') === 'true') { + return passthrough(); + } + + // Create a communication channel scoped to the current request. + // This way events can be exchanged outside of the worker's global + // "message" event listener (i.e. abstracted into functions). + const operationChannel = new BroadcastChannel(`msw-response-stream-${requestId}`); + + // Notify the client that a request has been intercepted. + const clientMessage = await sendToClient(client, { + type: 'REQUEST', + payload: { + id: requestId, + url: request.url, + method: request.method, + headers: Object.fromEntries(request.headers.entries()), + cache: request.cache, + mode: request.mode, + credentials: request.credentials, + destination: request.destination, + integrity: request.integrity, + redirect: request.redirect, + referrer: request.referrer, + referrerPolicy: request.referrerPolicy, + body: await request.text(), + bodyUsed: request.bodyUsed, + keepalive: request.keepalive, + }, + }); + + switch (clientMessage.type) { + case 'MOCK_RESPONSE': { + return respondWithMock(clientMessage.payload); + } + + case 'MOCK_RESPONSE_START': { + return respondWithMockStream(operationChannel, clientMessage.payload); + } + + case 'MOCK_NOT_FOUND': { + return passthrough(); + } + + case 'NETWORK_ERROR': { + const { name, message } = clientMessage.payload; + const networkError = new Error(message); + networkError.name = name; + + // Rejecting a "respondWith" promise emulates a network error. + throw networkError; + } + + case 'INTERNAL_ERROR': { + const parsedBody = JSON.parse(clientMessage.payload.body); + + console.error( + `\ +[MSW] Uncaught exception in the request handler for "%s %s": + +${parsedBody.location} + +This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error, as it indicates a mistake in your code. If you wish to mock an error response, please see this guide: https://mswjs.io/docs/recipes/mocking-error-responses\ +`, + request.method, + request.url + ); + + return respondWithMock(clientMessage.payload); + } + } + + return passthrough(); +} + +function sendToClient(client, message) { + return new Promise((resolve, reject) => { + const channel = new MessageChannel(); + + channel.port1.onmessage = (event) => { + if (event.data && event.data.error) { + return reject(event.data.error); + } + + resolve(event.data); + }; + + client.postMessage(JSON.stringify(message), [channel.port2]); + }); +} + +function sleep(timeMs) { + return new Promise((resolve) => { + setTimeout(resolve, timeMs); + }); +} + +async function respondWithMock(response) { + await sleep(response.delay); + return new Response(response.body, response); +} + +function respondWithMockStream(operationChannel, mockResponse) { + let streamCtrl; + const stream = new ReadableStream({ + start: (controller) => (streamCtrl = controller), + }); + + return new Promise(async (resolve, reject) => { + operationChannel.onmessageerror = (event) => { + operationChannel.close(); + return reject(event.data.error); + }; + + operationChannel.onmessage = (event) => { + if (!event.data) { + return; + } + + switch (event.data.type) { + case 'MOCK_RESPONSE_CHUNK': { + streamCtrl.enqueue(event.data.payload); + break; + } + + case 'MOCK_RESPONSE_END': { + streamCtrl.close(); + operationChannel.close(); + } + } + }; + + await sleep(mockResponse.delay); + return resolve(new Response(stream, mockResponse)); + }); +} diff --git a/frontend/setupFile.js b/frontend/setupFile.js new file mode 100644 index 00000000..a0e5a9fa --- /dev/null +++ b/frontend/setupFile.js @@ -0,0 +1,5 @@ +import { setGlobalConfig } from '@storybook/testing-react'; + +import * as globalStorybookConfig from './.storybook/preview'; + +setGlobalConfig(globalStorybookConfig); diff --git a/frontend/src/@types/calendar.ts b/frontend/src/@types/calendar.ts new file mode 100644 index 00000000..c0aa76dc --- /dev/null +++ b/frontend/src/@types/calendar.ts @@ -0,0 +1,8 @@ +interface CalendarType { + year: number; + month: number; + date: number; + day: number; +} + +export { CalendarType }; diff --git a/frontend/src/@types/category.ts b/frontend/src/@types/category.ts new file mode 100644 index 00000000..6218dec1 --- /dev/null +++ b/frontend/src/@types/category.ts @@ -0,0 +1,18 @@ +import { CATEGORY_TYPE } from '@/constants/category'; + +import { ProfileType } from './profile'; + +interface CategoryType { + id: number; + name: string; + creator: ProfileType; + createdAt: string; + categoryType: typeof CATEGORY_TYPE[keyof typeof CATEGORY_TYPE]; +} + +interface CategoriesGetResponseType { + page: number; + categories: CategoryType[]; +} + +export { CategoryType, CategoriesGetResponseType }; diff --git a/frontend/src/@types/custom.d.ts b/frontend/src/@types/custom.d.ts new file mode 100644 index 00000000..3e348645 --- /dev/null +++ b/frontend/src/@types/custom.d.ts @@ -0,0 +1,4 @@ +declare module '*.png'; +declare module '*.gif'; +declare module '*.jpg'; +declare module '*.jpeg'; diff --git a/frontend/src/@types/emotion.d.ts b/frontend/src/@types/emotion.d.ts new file mode 100644 index 00000000..98b9b120 --- /dev/null +++ b/frontend/src/@types/emotion.d.ts @@ -0,0 +1,16 @@ +import { SerializedStyles } from '@emotion/react'; + +interface Colors { + [key: string]: string; +} + +interface Flex { + [key: string]: SerializedStyles; +} + +declare module '@emotion/react' { + export interface Theme { + colors: Colors; + flex: Flex; + } +} diff --git a/frontend/src/@types/googleCalendar.ts b/frontend/src/@types/googleCalendar.ts new file mode 100644 index 00000000..c286820a --- /dev/null +++ b/frontend/src/@types/googleCalendar.ts @@ -0,0 +1,13 @@ +interface GoogleCalendarGetResponseType { + externalCalendars: Array<{ + calendarId: string; + summary: string; + }>; +} + +interface GoogleCalendarPostBodyType { + externalId: string; + name: string; +} + +export { GoogleCalendarGetResponseType, GoogleCalendarPostBodyType }; diff --git a/frontend/src/@types/index.ts b/frontend/src/@types/index.ts new file mode 100644 index 00000000..0cb0d39d --- /dev/null +++ b/frontend/src/@types/index.ts @@ -0,0 +1,20 @@ +import { SerializedStyles } from '@emotion/react'; + +interface FieldsetCssPropType { + div?: SerializedStyles; + input?: SerializedStyles; + label?: SerializedStyles; +} + +interface InputRefType { + [index: string]: React.RefObject; +} + +interface ModalPosType { + top?: number; + right?: number; + bottom?: number; + left?: number; +} + +export { FieldsetCssPropType, InputRefType, ModalPosType }; diff --git a/frontend/src/@types/profile.ts b/frontend/src/@types/profile.ts new file mode 100644 index 00000000..f067662b --- /dev/null +++ b/frontend/src/@types/profile.ts @@ -0,0 +1,13 @@ +interface ProfileType { + id: number; + email: string; + displayName: string; + profileImageUrl: string; + socialType: string; +} + +interface ProfileGetResponseType { + data: ProfileType; +} + +export { ProfileType, ProfileGetResponseType }; diff --git a/frontend/src/@types/schedule.ts b/frontend/src/@types/schedule.ts new file mode 100644 index 00000000..1ff6c161 --- /dev/null +++ b/frontend/src/@types/schedule.ts @@ -0,0 +1,20 @@ +import { CATEGORY_TYPE } from '@/constants/category'; + +interface ScheduleType { + id: string; + categoryId: number; + title: string; + startDateTime: string; + endDateTime: string; + memo: string; + colorCode: string; + categoryType: typeof CATEGORY_TYPE[keyof typeof CATEGORY_TYPE]; +} + +interface ScheduleResponseType { + longTerms: Array; + allDays: Array; + fewHours: Array; +} + +export { ScheduleResponseType, ScheduleType }; diff --git a/frontend/src/@types/scheduler.ts b/frontend/src/@types/scheduler.ts new file mode 100644 index 00000000..dee1743a --- /dev/null +++ b/frontend/src/@types/scheduler.ts @@ -0,0 +1,6 @@ +interface SchedulingResponseType { + startDateTime: string; + endDateTime: string; +} + +export { SchedulingResponseType }; diff --git a/frontend/src/@types/subscription.ts b/frontend/src/@types/subscription.ts new file mode 100644 index 00000000..f0094aef --- /dev/null +++ b/frontend/src/@types/subscription.ts @@ -0,0 +1,10 @@ +import { CategoryType } from './category'; + +interface SubscriptionType { + id: number; + category: CategoryType; + colorCode: string; + checked: boolean; +} + +export { SubscriptionType }; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 00000000..65c5257c --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,60 @@ +import { QueryClient, QueryClientProvider } from 'react-query'; +import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; + +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'; +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'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + useErrorBoundary: true, + retry: 1, + retryDelay: 0, + }, + mutations: { + useErrorBoundary: true, + retry: 1, + retryDelay: 0, + }, + }, +}); + +function App() { + return ( + + + + + + + }> + } /> + } /> + } /> + } /> + + }> + } /> + } /> + + + + + + + ); +} + +export default App; diff --git a/frontend/src/api/category.ts b/frontend/src/api/category.ts new file mode 100644 index 00000000..063b3b60 --- /dev/null +++ b/frontend/src/api/category.ts @@ -0,0 +1,74 @@ +import { CategoriesGetResponseType, CategoryType } from '@/@types/category'; + +import dallogApi from './'; + +const categoryApi = { + endpoint: { + entire: '/api/categories', + my: '/api/categories/me', + }, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + getEntire: async (name: string, page: number, size: number) => { + const response = await dallogApi.get( + categoryApi.endpoint.entire, + name === '' + ? { + params: { page, size }, + headers: categoryApi.headers, + } + : { + params: { name, page, size }, + headers: categoryApi.headers, + } + ); + + return response; + }, + + getSingle: async (categoryId: number | undefined) => { + const response = await dallogApi.get(`${categoryApi.endpoint.entire}/${categoryId}`, { + headers: { ...categoryApi.headers }, + }); + + return response; + }, + + getMy: async (accessToken: string) => { + const response = await dallogApi.get(categoryApi.endpoint.my, { + headers: { ...categoryApi.headers, Authorization: `Bearer ${accessToken}` }, + transformResponse: (res) => JSON.parse(res).categories, + }); + + return response; + }, + + post: async (accessToken: string, body: Pick) => { + const response = await dallogApi.post(categoryApi.endpoint.entire, body, { + headers: { ...categoryApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + patch: async (accessToken: string, categoryId: number, body: Pick) => { + const response = await dallogApi.patch(`${categoryApi.endpoint.entire}/${categoryId}`, body, { + headers: { ...categoryApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + delete: async (accessToken: string, categoryId: number) => { + const response = await dallogApi.delete(`${categoryApi.endpoint.entire}/${categoryId}`, { + headers: { ...categoryApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default categoryApi; diff --git a/frontend/src/api/googleCalendar.ts b/frontend/src/api/googleCalendar.ts new file mode 100644 index 00000000..2aa0e32a --- /dev/null +++ b/frontend/src/api/googleCalendar.ts @@ -0,0 +1,33 @@ +import { GoogleCalendarGetResponseType, GoogleCalendarPostBodyType } from '@/@types/googleCalendar'; + +import dallogApi from './'; + +const googleCalendarApi = { + endpoint: '/api/external-calendars/me', + + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + get: async (accessToken: string) => { + const response = await dallogApi.get( + googleCalendarApi.endpoint, + { + headers: { ...googleCalendarApi.headers, Authorization: `Bearer ${accessToken}` }, + } + ); + + return response; + }, + + post: async (accessToken: string, body: GoogleCalendarPostBodyType) => { + const response = await dallogApi.post(googleCalendarApi.endpoint, body, { + headers: { ...googleCalendarApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default googleCalendarApi; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 00000000..267f77f2 --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; + +const dallogApi = axios.create({ + baseURL: process.env.API_URL, +}); + +export default dallogApi; diff --git a/frontend/src/api/login.ts b/frontend/src/api/login.ts new file mode 100644 index 00000000..d36c1082 --- /dev/null +++ b/frontend/src/api/login.ts @@ -0,0 +1,40 @@ +import dallogApi from './'; + +const loginApi = { + endPoint: { + googleEntry: '/api/auth/google/oauth-uri', + googleToken: '/api/auth/google/token', + validate: '/api/auth/validate/token', + }, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + getUrl: async () => { + const { data } = await dallogApi.get( + `${loginApi.endPoint.googleEntry}?redirectUri=${location.href}oauth` + ); + + return data.oAuthUri; + }, + + auth: async (code: string | null) => { + const { data } = await dallogApi.post(loginApi.endPoint.googleToken, { + code, + redirectUri: location.href.split('?')[0], + }); + + return data.accessToken; + }, + + validate: async (accessToken: string) => { + const response = await dallogApi.get(loginApi.endPoint.validate, { + headers: { ...loginApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default loginApi; diff --git a/frontend/src/api/profile.ts b/frontend/src/api/profile.ts new file mode 100644 index 00000000..223f41b9 --- /dev/null +++ b/frontend/src/api/profile.ts @@ -0,0 +1,32 @@ +import { ProfileType } from '@/@types/profile'; + +import dallogApi from './'; + +const profileApi = { + endpoint: { + get: '/api/members/me', + patch: '/api/members/me', + }, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + get: async (accessToken: string) => { + const response = await dallogApi.get(profileApi.endpoint.get, { + headers: { ...profileApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + patch: async (accessToken: string, body: Pick) => { + const response = await dallogApi.patch(profileApi.endpoint.patch, body, { + headers: { ...profileApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default profileApi; diff --git a/frontend/src/api/schedule.ts b/frontend/src/api/schedule.ts new file mode 100644 index 00000000..a124936d --- /dev/null +++ b/frontend/src/api/schedule.ts @@ -0,0 +1,63 @@ +import { ScheduleResponseType, ScheduleType } from '@/@types/schedule'; + +import { DATE_TIME } from '@/constants/date'; + +import dallogApi from './'; + +const scheduleApi = { + endpoint: { + get: '/api/members/me/schedules', + post: (categoryId: number) => `/api/categories/${categoryId}/schedules`, + patch: (scheduleId: string) => `/api/schedules/${scheduleId}`, + delete: (scheduleId: string) => `/api/schedules/${scheduleId}`, + }, + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + get: async (accessToken: string, startDate: string, endDate: string) => { + const response = await dallogApi.get( + `${scheduleApi.endpoint.get}?startDateTime=${startDate}T${DATE_TIME.START}&endDateTime=${endDate}T${DATE_TIME.END}`, + { + headers: { ...scheduleApi.headers, Authorization: `Bearer ${accessToken}` }, + } + ); + + return response; + }, + + post: async ( + accessToken: string, + categoryId: number, + body: Omit + ) => { + const response = await dallogApi.post(scheduleApi.endpoint.post(categoryId), body, { + headers: { ...scheduleApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + patch: async ( + accessToken: string, + scheduleId: string, + body: Omit + ) => { + const response = await dallogApi.patch(scheduleApi.endpoint.patch(scheduleId), body, { + headers: { ...scheduleApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + delete: async (accessToken: string, scheduleId: string) => { + const response = await dallogApi.delete(scheduleApi.endpoint.delete(scheduleId), { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default scheduleApi; diff --git a/frontend/src/api/scheduler.ts b/frontend/src/api/scheduler.ts new file mode 100644 index 00000000..f846ff22 --- /dev/null +++ b/frontend/src/api/scheduler.ts @@ -0,0 +1,33 @@ +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/api/subscription.ts b/frontend/src/api/subscription.ts new file mode 100644 index 00000000..baa59ac7 --- /dev/null +++ b/frontend/src/api/subscription.ts @@ -0,0 +1,64 @@ +import { AxiosResponse } from 'axios'; + +import { SubscriptionType } from '@/@types/subscription'; + +import dallogApi from './'; + +const subscriptionApi = { + endpoint: { + get: '/api/members/me/subscriptions', + post: (categoryId: number) => `/api/members/me/categories/${categoryId}/subscriptions`, + patch: (subscriptionId: number) => `/api/members/me/subscriptions/${subscriptionId}`, + delete: (subscriptionId: number) => `/api/members/me/subscriptions/${subscriptionId}`, + }, + + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + }, + + get: async (accessToken: string) => { + const response = await dallogApi.get(subscriptionApi.endpoint.get, { + headers: { ...subscriptionApi.headers, Authorization: `Bearer ${accessToken}` }, + transformResponse: (res) => { + return JSON.parse(res).subscriptions; + }, + }); + + return response; + }, + + post: async ( + accessToken: string, + categoryId: number, + body: Pick + ) => { + const response = await dallogApi.post(subscriptionApi.endpoint.post(categoryId), body, { + headers: { ...subscriptionApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + patch: async ( + accessToken: string, + subscriptionId: number, + body: Pick | Pick + ) => { + const response = await dallogApi.patch(subscriptionApi.endpoint.patch(subscriptionId), body, { + headers: { ...subscriptionApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, + + delete: async (accessToken: string, subscriptionId: number): Promise> => { + const response = await dallogApi.delete(subscriptionApi.endpoint.delete(subscriptionId), { + headers: { ...subscriptionApi.headers, Authorization: `Bearer ${accessToken}` }, + }); + + return response; + }, +}; + +export default subscriptionApi; diff --git a/frontend/src/assets/dallog_black.png b/frontend/src/assets/dallog_black.png new file mode 100644 index 00000000..a6fff745 Binary files /dev/null and b/frontend/src/assets/dallog_black.png differ diff --git a/frontend/src/assets/dallog_color.png b/frontend/src/assets/dallog_color.png new file mode 100644 index 00000000..b42556d2 Binary files /dev/null and b/frontend/src/assets/dallog_color.png differ diff --git a/frontend/src/assets/dallog_white.png b/frontend/src/assets/dallog_white.png new file mode 100644 index 00000000..07431214 Binary files /dev/null and b/frontend/src/assets/dallog_white.png differ diff --git a/frontend/src/assets/how_to_use_1.png b/frontend/src/assets/how_to_use_1.png new file mode 100644 index 00000000..19330c39 Binary files /dev/null and b/frontend/src/assets/how_to_use_1.png differ diff --git a/frontend/src/assets/how_to_use_2.png b/frontend/src/assets/how_to_use_2.png new file mode 100644 index 00000000..e1fbd040 Binary files /dev/null and b/frontend/src/assets/how_to_use_2.png differ diff --git a/frontend/src/assets/how_to_use_3.png b/frontend/src/assets/how_to_use_3.png new file mode 100644 index 00000000..7dcb9867 Binary files /dev/null and b/frontend/src/assets/how_to_use_3.png differ diff --git a/frontend/src/components/@common/Button/Button.stories.tsx b/frontend/src/components/@common/Button/Button.stories.tsx new file mode 100644 index 00000000..c873e5df --- /dev/null +++ b/frontend/src/components/@common/Button/Button.stories.tsx @@ -0,0 +1,16 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import Button from './Button'; + +export default { + title: 'Components/@Common/Button', + component: Button, +} as ComponentMeta; + +const Template: ComponentStory = (args) => + ); +} + +export default Button; diff --git a/frontend/src/components/@common/ErrorBoundary/ErrorBoundary.tsx b/frontend/src/components/@common/ErrorBoundary/ErrorBoundary.tsx new file mode 100644 index 00000000..4691ca06 --- /dev/null +++ b/frontend/src/components/@common/ErrorBoundary/ErrorBoundary.tsx @@ -0,0 +1,49 @@ +/* eslint-disable react/display-name */ +import { AxiosError } from 'axios'; +import { Component } from 'react'; + +import useSnackBar from '@/hooks/useSnackBar'; + +import { ERROR_MESSAGE } from '@/constants/message'; + +interface Props { + children: JSX.Element; + openSnackBar: (text: string) => void; +} + +interface State { + hasError: boolean; +} + +export const withHooksHOC = (Component: any) => { + return (props: any) => { + const { openSnackBar } = useSnackBar(); + + return ; + }; +}; + +class ErrorBoundary extends Component { + public state: State = { + hasError: false, + }; + + public static getDerivedStateFromError(): State { + return { hasError: true }; + } + + public componentDidCatch(error: unknown) { + if (error instanceof AxiosError) { + this.props.openSnackBar(error.response?.data.message ?? ERROR_MESSAGE.DEFAULT); + return; + } + + this.props.openSnackBar(ERROR_MESSAGE.DEFAULT); + } + + public render() { + return this.props.children; + } +} + +export default withHooksHOC(ErrorBoundary); diff --git a/frontend/src/components/@common/Fieldset/Fieldset.stories.tsx b/frontend/src/components/@common/Fieldset/Fieldset.stories.tsx new file mode 100644 index 00000000..901a0526 --- /dev/null +++ b/frontend/src/components/@common/Fieldset/Fieldset.stories.tsx @@ -0,0 +1,27 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import Fieldset from './Fieldset'; + +export default { + title: 'Components/@Common/Fieldset', + component: Fieldset, +} as ComponentMeta; + +const Template: ComponentStory = (args) =>
; + +const Primary = Template.bind({}); +Primary.args = { + id: 'primary', + labelText: 'primary', + placeholder: '입력해주세요.', +}; + +const DatePicker = Template.bind({}); +DatePicker.args = { + type: 'datetime-local', + id: 'date-time-picker', + labelText: '일정 시작', + placeholder: '입력해주세요.', +}; + +export { Primary, DatePicker }; diff --git a/frontend/src/components/@common/Fieldset/Fieldset.styles.ts b/frontend/src/components/@common/Fieldset/Fieldset.styles.ts new file mode 100644 index 00000000..8c7ea4c3 --- /dev/null +++ b/frontend/src/components/@common/Fieldset/Fieldset.styles.ts @@ -0,0 +1,45 @@ +import { css, Theme } from '@emotion/react'; + +const fieldsetStyle = ({ flex }: Theme) => css` + ${flex.column} + + align-items: flex-start; + gap: 2.5rem; + + width: 100%; + height: auto; + + font-size: 4rem; +`; + +const labelStyle = ({ colors }: Theme) => css` + padding: 0 1rem; + + color: ${colors.GRAY_800}; +`; + +const inputStyle = ({ colors }: Theme, isValid?: boolean) => css` + padding: 3rem; + + width: 100%; + border-radius: 8px; + border: 1px solid ${isValid === false ? colors.RED_400 : colors.GRAY_400}; + + font-family: inherit; + font-size: inherit; + + &:focus { + outline: none; + border-color: ${isValid === false ? colors.RED_400 : colors.YELLOW_500}; + box-shadow: 0 0 2px ${isValid === false ? colors.RED_400 : colors.YELLOW_500}; + } +`; + +const errorMessageStyle = ({ colors }: Theme, isValid?: boolean) => css` + display: ${isValid ? 'none' : 'block'}; + + font-size: 3rem; + color: ${colors.RED_400}; +`; + +export { errorMessageStyle, fieldsetStyle, labelStyle, inputStyle }; diff --git a/frontend/src/components/@common/Fieldset/Fieldset.test.tsx b/frontend/src/components/@common/Fieldset/Fieldset.test.tsx new file mode 100644 index 00000000..1f3dc6ef --- /dev/null +++ b/frontend/src/components/@common/Fieldset/Fieldset.test.tsx @@ -0,0 +1,25 @@ +/** + * @jest-environment jsdom + */ +import { composeStories } from '@storybook/testing-react'; +import { render, screen } from '@testing-library/react'; + +import * as stories from './Fieldset.stories'; + +const { Primary, DatePicker } = composeStories(stories); + +test('기본 입력 필드가 출력된다.', () => { + render(); + + const buttonElement = screen.getByText(/primary/i); + + expect(buttonElement).not.toBeNull(); +}); + +test('날짜 선택을 위한 입력 필드가 출력된다.', () => { + render(); + + const buttonElement = screen.getByText(/일정 시작/i); + + expect(buttonElement).not.toBeNull(); +}); diff --git a/frontend/src/components/@common/Fieldset/Fieldset.tsx b/frontend/src/components/@common/Fieldset/Fieldset.tsx new file mode 100644 index 00000000..db0eb11d --- /dev/null +++ b/frontend/src/components/@common/Fieldset/Fieldset.tsx @@ -0,0 +1,62 @@ +import { useTheme } from '@emotion/react'; + +import { FieldsetCssPropType } from '@/@types'; + +import { errorMessageStyle, fieldsetStyle, inputStyle, labelStyle } from './Fieldset.styles'; + +interface FieldsetProps extends React.HTMLAttributes { + type?: string; + value?: string; + defaultValue?: string; + cssProp?: FieldsetCssPropType; + labelText?: string; + autoFocus?: boolean; + refProp?: React.MutableRefObject; + disabled?: boolean; + onChange?: (e: React.ChangeEvent) => void; + isValid?: boolean; + errorMessage?: string; +} + +function Fieldset({ + type = 'text', + id, + cssProp, + placeholder, + value, + defaultValue, + autoFocus, + refProp, + disabled, + onChange, + labelText, + isValid, + errorMessage, +}: FieldsetProps) { + const theme = useTheme(); + + return ( +
+ {labelText && ( + + )} + + {errorMessage && {errorMessage}} +
+ ); +} + +export default Fieldset; diff --git a/frontend/src/components/@common/ModalPortal/ModalPortal.styles.ts b/frontend/src/components/@common/ModalPortal/ModalPortal.styles.ts new file mode 100644 index 00000000..0c3bb43a --- /dev/null +++ b/frontend/src/components/@common/ModalPortal/ModalPortal.styles.ts @@ -0,0 +1,27 @@ +import { css, Theme } from '@emotion/react'; + +import { TRANSPARENT } from '@/constants/style'; + +const dimmer = ( + { colors, flex }: Theme, + isOpen: boolean, + dimmerBackground?: typeof TRANSPARENT +) => css` + ${flex.row}; + + position: fixed; + top: 0; + left: 0; + z-index: 30; + + width: 100%; + height: 100%; + + background: ${dimmerBackground !== undefined + ? dimmerBackground + : isOpen + ? `${colors.BLACK}bb` + : 'transparent'}; +`; + +export { dimmer }; diff --git a/frontend/src/components/@common/ModalPortal/ModalPortal.tsx b/frontend/src/components/@common/ModalPortal/ModalPortal.tsx new file mode 100644 index 00000000..45f0e7b1 --- /dev/null +++ b/frontend/src/components/@common/ModalPortal/ModalPortal.tsx @@ -0,0 +1,41 @@ +import { useTheme } from '@emotion/react'; +import ReactDOM from 'react-dom'; + +import { TRANSPARENT } from '@/constants/style'; + +import { dimmer } from './ModalPortal.styles'; + +interface ModalPortalProps { + isOpen: boolean; + closeModal: () => void; + children: JSX.Element | JSX.Element[]; + dimmerBackground?: typeof TRANSPARENT; +} + +function ModalPortal({ isOpen, closeModal, children, dimmerBackground }: ModalPortalProps) { + const modalElement = document.getElementById('modal'); + + const theme = useTheme(); + + if (!(modalElement instanceof HTMLElement)) { + return <>; + } + + const handleClickDimmer = (e: React.MouseEvent) => { + if (e.target !== e.currentTarget) { + return; + } + + closeModal(); + }; + + const element = isOpen && ( +
+ {children} +
+ ); + + return ReactDOM.createPortal(element, modalElement); +} + +export default ModalPortal; diff --git a/frontend/src/components/@common/PageLayout/PageLayout.styles.ts b/frontend/src/components/@common/PageLayout/PageLayout.styles.ts new file mode 100644 index 00000000..4e368bad --- /dev/null +++ b/frontend/src/components/@common/PageLayout/PageLayout.styles.ts @@ -0,0 +1,14 @@ +import { css } from '@emotion/react'; + +const pageLayout = (isSideBarOpen: boolean) => css` + overflow-y: auto; + position: relative; + + height: calc(100vh - 16rem); + margin-top: 16rem; + margin-left: ${isSideBarOpen ? '64rem' : '0'}; + + transition: margin-left 0.3s; +`; + +export { pageLayout }; diff --git a/frontend/src/components/@common/PageLayout/PageLayout.tsx b/frontend/src/components/@common/PageLayout/PageLayout.tsx new file mode 100644 index 00000000..15f2f439 --- /dev/null +++ b/frontend/src/components/@common/PageLayout/PageLayout.tsx @@ -0,0 +1,17 @@ +import { useRecoilValue } from 'recoil'; + +import { sideBarState } from '@/recoil/atoms'; + +import { pageLayout } from './PageLayout.styles'; + +interface PageLayoutProps { + children: JSX.Element | JSX.Element[]; +} + +function PageLayout({ children }: PageLayoutProps) { + const isSideBarOpen = useRecoilValue(sideBarState); + + return
{children}
; +} + +export default PageLayout; diff --git a/frontend/src/components/@common/Skeleton/Skeleton.stories.tsx b/frontend/src/components/@common/Skeleton/Skeleton.stories.tsx new file mode 100644 index 00000000..8ca8c19b --- /dev/null +++ b/frontend/src/components/@common/Skeleton/Skeleton.stories.tsx @@ -0,0 +1,20 @@ +import { css } from '@emotion/react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import Skeleton from './Skeleton'; + +export default { + title: 'Components/@Common/Skeleton', + component: Skeleton, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +const Primary = Template.bind({}); +Primary.args = { + cssProp: css` + width: 100rem; + height: 20rem; + `, +}; +export { Primary }; diff --git a/frontend/src/components/@common/Skeleton/Skeleton.styles.ts b/frontend/src/components/@common/Skeleton/Skeleton.styles.ts new file mode 100644 index 00000000..d8e6b39d --- /dev/null +++ b/frontend/src/components/@common/Skeleton/Skeleton.styles.ts @@ -0,0 +1,31 @@ +import { css, Theme } from '@emotion/react'; + +const skeletonStyle = ({ colors }: Theme, width: string, height: string) => css` + @keyframes skeleton { + 0% { + background-color: transparent; + } + 25% { + background-color: ${colors.GRAY_100}; + } + 50% { + background-color: ${colors.GRAY_200}; + } + 75% { + background-color: ${colors.GRAY_300}; + } + 100% { + background-color: transparent; + } + } + + display: inline-block; + + width: ${width}; + height: ${height}; + border-radius: 4px; + + animation: skeleton 2s infinite ease-out; +`; + +export { skeletonStyle }; diff --git a/frontend/src/components/@common/Skeleton/Skeleton.tsx b/frontend/src/components/@common/Skeleton/Skeleton.tsx new file mode 100644 index 00000000..e9c0bd8a --- /dev/null +++ b/frontend/src/components/@common/Skeleton/Skeleton.tsx @@ -0,0 +1,21 @@ +import { SerializedStyles, useTheme } from '@emotion/react'; + +import { skeletonStyle } from './Skeleton.styles'; + +interface SkeletonProps { + cssProp?: SerializedStyles; + width?: string; + height?: string; +} + +function Skeleton({ cssProp, width = '100%', height = '100%' }: SkeletonProps) { + const theme = useTheme(); + + return ( +
+
+
+ ); +} + +export default Skeleton; diff --git a/frontend/src/components/@common/Spinner/Spinner.stories.tsx b/frontend/src/components/@common/Spinner/Spinner.stories.tsx new file mode 100644 index 00000000..990d23b5 --- /dev/null +++ b/frontend/src/components/@common/Spinner/Spinner.stories.tsx @@ -0,0 +1,13 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import Spinner from './Spinner'; + +export default { + title: 'Components/@Common/Spinner', + component: Spinner, +} as ComponentMeta; + +const Template: ComponentStory = () => ; + +const Primary = Template.bind({}); +Primary.args = {}; diff --git a/frontend/src/components/@common/Spinner/Spinner.styles.ts b/frontend/src/components/@common/Spinner/Spinner.styles.ts new file mode 100644 index 00000000..3c9e2ae9 --- /dev/null +++ b/frontend/src/components/@common/Spinner/Spinner.styles.ts @@ -0,0 +1,45 @@ +import { css, Theme } from '@emotion/react'; + +const spinnerStyle = ({ colors }: Theme, size: number) => css` + position: relative; + display: inline-block; + + width: ${size}rem; + height: ${size}rem; + + & div { + position: absolute; + display: block; + + width: ${size * 0.8}rem; + height: ${size * 0.8}rem; + margin: ${size * 0.1}rem; + border: ${size * 0.1}rem solid ${colors.GRAY_700}; + box-sizing: border-box; + border-radius: 50%; + border-color: ${colors.YELLOW_500} transparent transparent transparent; + + animation: loading 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + } + + & div:nth-of-type(1) { + animation-delay: -0.45s; + } + & div:nth-of-type(2) { + animation-delay: -0.3s; + } + & div:nth-of-type(3) { + animation-delay: -0.15s; + } + + @keyframes loading { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } +`; + +export { spinnerStyle }; diff --git a/frontend/src/components/@common/Spinner/Spinner.tsx b/frontend/src/components/@common/Spinner/Spinner.tsx new file mode 100644 index 00000000..769b695c --- /dev/null +++ b/frontend/src/components/@common/Spinner/Spinner.tsx @@ -0,0 +1,22 @@ +import { useTheme } from '@emotion/react'; + +import { spinnerStyle } from './Spinner.styles'; + +interface SpinnerProps { + size?: number; +} + +function Spinner({ size = 5 }: SpinnerProps) { + const theme = useTheme(); + + return ( +
+
+
+
+
+
+ ); +} + +export default Spinner; diff --git a/frontend/src/components/CategoryAddModal/CategoryAddModal.stories.tsx b/frontend/src/components/CategoryAddModal/CategoryAddModal.stories.tsx new file mode 100644 index 00000000..91a4003d --- /dev/null +++ b/frontend/src/components/CategoryAddModal/CategoryAddModal.stories.tsx @@ -0,0 +1,12 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import CategoryAddModal from './CategoryAddModal'; + +export default { + title: 'Components/CategoryAddModal', + component: CategoryAddModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Primary = Template.bind({}); diff --git a/frontend/src/components/CategoryAddModal/CategoryAddModal.styles.ts b/frontend/src/components/CategoryAddModal/CategoryAddModal.styles.ts new file mode 100644 index 00000000..e5546388 --- /dev/null +++ b/frontend/src/components/CategoryAddModal/CategoryAddModal.styles.ts @@ -0,0 +1,78 @@ +import { css, Theme } from '@emotion/react'; + +const categoryAddModal = ({ colors, flex }: Theme) => css` + ${flex.column} + + width: 120rem; + height: 90rem; + padding: 12.5rem; + border-radius: 12px; + justify-content: space-between; + + background: ${colors.WHITE}; +`; + +const title = ({ colors }: Theme) => css` + font-size: 8rem; + font-weight: bold; + color: ${colors.GRAY_700}; +`; + +const form = ({ flex }: Theme) => css` + ${flex.column}; + + width: 100%; + height: 100%; + justify-content: space-between; +`; + +const content = ({ flex }: Theme) => css` + ${flex.column}; + + width: 100%; + height: 100%; + + justify-content: center; +`; + +const controlButtons = ({ flex }: Theme) => css` + ${flex.row} + + align-self: flex-end; + gap: 5rem; +`; + +const cancelButtonStyle = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border: 1px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${colors.WHITE}; + + font-size: 4rem; + color: ${colors.GRAY_600}; +`; + +const saveButtonStyle = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border-radius: 8px; + filter: drop-shadow(0px 2px 2px ${colors.GRAY_400}); + + background: ${colors.YELLOW_500}; + + font-size: 4rem; + color: ${colors.WHITE}; +`; + +export { + cancelButtonStyle, + categoryAddModal, + content, + controlButtons, + form, + saveButtonStyle, + title, +}; diff --git a/frontend/src/components/CategoryAddModal/CategoryAddModal.tsx b/frontend/src/components/CategoryAddModal/CategoryAddModal.tsx new file mode 100644 index 00000000..808f1147 --- /dev/null +++ b/frontend/src/components/CategoryAddModal/CategoryAddModal.tsx @@ -0,0 +1,93 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useValidateCategory from '@/hooks/useValidateCategory'; + +import { CategoryType } from '@/@types/category'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; + +import { CACHE_KEY } from '@/constants/api'; +import { CATEGORY_TYPE } from '@/constants/category'; + +import categoryApi from '@/api/category'; + +import { + cancelButtonStyle, + categoryAddModal, + content, + controlButtons, + form, + saveButtonStyle, + title, +} from './CategoryAddModal.styles'; + +interface CategoryAddModalProps { + closeModal: () => void; +} + +function CategoryAddModal({ closeModal }: CategoryAddModalProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const { categoryValue, getCategoryErrorMessage, isValidCategory } = useValidateCategory(); + + const queryClient = useQueryClient(); + const { mutate } = useMutation< + AxiosResponse, + AxiosError, + Pick, + unknown + >((body) => categoryApi.post(accessToken, body), { + onSuccess: () => onSuccessPostCategory(), + }); + + const handleSubmitCategoryAddForm = (e: React.FormEvent) => { + e.preventDefault(); + + mutate({ name: categoryValue.inputValue, categoryType: CATEGORY_TYPE.NORMAL }); + }; + + const onSuccessPostCategory = () => { + queryClient.invalidateQueries(CACHE_KEY.CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.MY_CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); + + closeModal(); + }; + + return ( +
+

새 카테고리 만들기

+
+
+
+
+
+ + +
+
+
+ ); +} + +export default CategoryAddModal; diff --git a/frontend/src/components/CategoryList/CategoryList.fallback.stories.tsx b/frontend/src/components/CategoryList/CategoryList.fallback.stories.tsx new file mode 100644 index 00000000..e1699cc9 --- /dev/null +++ b/frontend/src/components/CategoryList/CategoryList.fallback.stories.tsx @@ -0,0 +1,14 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import CategoryListFallback from './CategoryList.fallback'; + +export default { + title: 'Components/CategoryListFallback', + component: CategoryListFallback, +} as ComponentMeta; + +const Template: ComponentStory = () => ; + +const Primary = Template.bind({}); + +export { Primary }; diff --git a/frontend/src/components/CategoryList/CategoryList.fallback.tsx b/frontend/src/components/CategoryList/CategoryList.fallback.tsx new file mode 100644 index 00000000..9ad39c95 --- /dev/null +++ b/frontend/src/components/CategoryList/CategoryList.fallback.tsx @@ -0,0 +1,32 @@ +import Skeleton from '@/components/@common/Skeleton/Skeleton'; +import { + categoryItem, + item, +} from '@/components/SubscribedCategoryItem/SubscribedCategoryItem.styles'; + +import { categoryTableHeaderStyle, categoryTableStyle, itemStyle } from './CategoryList.styles'; + +function CategoryListFallback() { + return ( +
+
+ 생성 날짜 + 카테고리 이름 + 생성자 + 구독 상태 +
+
+ {new Array(6).fill(0).map((el, index) => ( +
+ + + + +
+ ))} +
+
+ ); +} + +export default CategoryListFallback; diff --git a/frontend/src/components/CategoryList/CategoryList.stories.tsx b/frontend/src/components/CategoryList/CategoryList.stories.tsx new file mode 100644 index 00000000..c293c3d9 --- /dev/null +++ b/frontend/src/components/CategoryList/CategoryList.stories.tsx @@ -0,0 +1,15 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import CategoryList from './CategoryList'; + +export default { + title: 'Components/CategoryList', + component: CategoryList, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Primary = Template.bind({}); +Primary.args = { + keyword: '', +}; diff --git a/frontend/src/components/CategoryList/CategoryList.styles.ts b/frontend/src/components/CategoryList/CategoryList.styles.ts new file mode 100644 index 00000000..c6e9f117 --- /dev/null +++ b/frontend/src/components/CategoryList/CategoryList.styles.ts @@ -0,0 +1,34 @@ +import { css, Theme } from '@emotion/react'; + +const categoryTableStyle = css` + overflow-y: overlay; + + width: 100%; + height: 100%; +`; + +const categoryTableHeaderStyle = ({ 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 intersectTargetStyle = css` + height: 1rem; +`; + +const itemStyle = css` + flex: 1 1 0; + text-align: center; +`; + +export { categoryTableHeaderStyle, categoryTableStyle, intersectTargetStyle, itemStyle }; diff --git a/frontend/src/components/CategoryList/CategoryList.tsx b/frontend/src/components/CategoryList/CategoryList.tsx new file mode 100644 index 00000000..5ba8d339 --- /dev/null +++ b/frontend/src/components/CategoryList/CategoryList.tsx @@ -0,0 +1,104 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { useInfiniteQuery, useQuery } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useIntersect from '@/hooks/useIntersect'; + +import { CategoriesGetResponseType } from '@/@types/category'; +import { SubscriptionType } from '@/@types/subscription'; + +import { userState } from '@/recoil/atoms'; + +import SubscribedCategoryItem from '@/components/SubscribedCategoryItem/SubscribedCategoryItem'; +import UnsubscribedCategoryItem from '@/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem'; + +import { API, CACHE_KEY } from '@/constants/api'; + +import categoryApi from '@/api/category'; +import subscriptionApi from '@/api/subscription'; + +import { + categoryTableHeaderStyle, + categoryTableStyle, + intersectTargetStyle, + itemStyle, +} from './CategoryList.styles'; + +interface CategoryListProps { + keyword: string; +} + +function CategoryList({ keyword }: CategoryListProps) { + const { accessToken } = useRecoilValue(userState); + + const { + error: categoriesGetError, + data: categoriesGetResponse, + fetchNextPage, + hasNextPage, + } = useInfiniteQuery, AxiosError>( + [CACHE_KEY.CATEGORIES, keyword], + ({ pageParam = 0 }) => categoryApi.getEntire(keyword, pageParam, API.CATEGORY_GET_SIZE), + { + getNextPageParam: ({ data }) => { + if (data.categories.length > 0) { + return data.page + 1; + } + }, + } + ); + + const { error: subscriptionsGetError, data: subscriptionsGetResponse } = useQuery< + AxiosResponse, + AxiosError + >(CACHE_KEY.SUBSCRIPTIONS, () => subscriptionApi.get(accessToken)); + + const ref = useIntersect(() => { + hasNextPage && fetchNextPage(); + }); + + if (categoriesGetError || subscriptionsGetError) { + return <>Error; + } + + const categoryList = categoriesGetResponse?.pages.flatMap(({ data }) => data.categories); + const subscriptionList = subscriptionsGetResponse?.data.map((el) => { + return { + subscriptionId: el.id, + categoryId: el.category.id, + }; + }); + + return ( + <> +
+ 생성 날짜 + 카테고리 이름 + 생성자 + 구독 상태 +
+
+ {categoryList?.map((category) => { + const subscribedCategoryInfo = subscriptionList?.find( + (el) => el.categoryId === category.id + ); + + if (subscribedCategoryInfo === undefined) { + return ; + } + + return ( + + ); + })} +
+
+ + ); +} + +export default CategoryList; diff --git a/frontend/src/components/CategoryModifyModal/CategoryModifyModal.styles.ts b/frontend/src/components/CategoryModifyModal/CategoryModifyModal.styles.ts new file mode 100644 index 00000000..71c2daf6 --- /dev/null +++ b/frontend/src/components/CategoryModifyModal/CategoryModifyModal.styles.ts @@ -0,0 +1,70 @@ +import { css, Theme } from '@emotion/react'; + +const modal = ({ colors, flex }: Theme) => css` + ${flex.column} + + width: 120rem; + height: 90rem; + padding: 12.5rem; + border-radius: 12px; + justify-content: space-between; + + background: ${colors.WHITE}; +`; + +const title = ({ colors }: Theme) => css` + font-size: 8rem; + font-weight: bold; + color: ${colors.GRAY_700}; +`; + +const form = ({ flex }: Theme) => css` + ${flex.column}; + + width: 100%; + height: 100%; + justify-content: space-between; +`; + +const content = ({ flex }: Theme) => css` + ${flex.column}; + + width: 100%; + height: 100%; + + justify-content: center; +`; + +const controlButtons = ({ flex }: Theme) => css` + ${flex.row} + + align-self: flex-end; + gap: 5rem; +`; + +const cancelButton = ({ colors }: Theme) => css` + width: 22.5rem; + height: 10rem; + border: 2px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${colors.WHITE}; + + font-size: 5rem; + color: ${colors.GRAY_600}; +`; + +const saveButton = ({ colors }: Theme) => css` + width: 22.5rem; + height: 10rem; + border-radius: 8px; + filter: drop-shadow(0px 2px 2px ${colors.GRAY_400}); + + background: ${colors.YELLOW_500}; + + font-size: 5rem; + color: ${colors.WHITE}; +`; + +export { cancelButton, content, controlButtons, form, modal, saveButton, title }; diff --git a/frontend/src/components/CategoryModifyModal/CategoryModifyModal.tsx b/frontend/src/components/CategoryModifyModal/CategoryModifyModal.tsx new file mode 100644 index 00000000..1317711e --- /dev/null +++ b/frontend/src/components/CategoryModifyModal/CategoryModifyModal.tsx @@ -0,0 +1,94 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useValidateCategory from '@/hooks/useValidateCategory'; + +import { CategoryType } from '@/@types/category'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; + +import { CACHE_KEY } from '@/constants/api'; + +import categoryApi from '@/api/category'; + +import { + cancelButton, + content, + controlButtons, + form, + modal, + saveButton, + title, +} from './CategoryModifyModal.styles'; + +interface CategoryModifyModalProps { + category: CategoryType; + closeModal: () => void; +} + +function CategoryModifyModal({ category, closeModal }: CategoryModifyModalProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const { categoryValue, getCategoryErrorMessage, isValidCategory } = useValidateCategory( + category.name + ); + + const queryClient = useQueryClient(); + const { mutate } = useMutation< + AxiosResponse>, + AxiosError, + Pick, + unknown + >((body) => categoryApi.patch(accessToken, category.id, body), { + onSuccess: () => onSuccessPatchCategory(), + }); + + const handleSubmitCategoryModifyForm = (e: React.FormEvent) => { + e.preventDefault(); + + mutate({ name: categoryValue.inputValue }); + }; + + const onSuccessPatchCategory = () => { + queryClient.invalidateQueries(CACHE_KEY.CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.MY_CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); + closeModal(); + }; + + return ( +
+

카테고리 이름 수정

+
+
+
+
+
+ + +
+
+
+ ); +} + +export default CategoryModifyModal; diff --git a/frontend/src/components/DateModal/DateModal.styles.ts b/frontend/src/components/DateModal/DateModal.styles.ts new file mode 100644 index 00000000..3d524b99 --- /dev/null +++ b/frontend/src/components/DateModal/DateModal.styles.ts @@ -0,0 +1,86 @@ +import { css, Theme } from '@emotion/react'; + +import { ModalPosType } from '@/@types'; + +const dateModalStyle = ({ colors, flex }: Theme, dateModalPos: ModalPosType) => css` + ${flex.column} + + position: absolute; + top: ${dateModalPos.top ? `${dateModalPos.top + 20}px` : 'none'}; + right: ${dateModalPos.right ? `${dateModalPos.right + 20}px` : 'none'}; + bottom: ${dateModalPos.bottom ? `${dateModalPos.bottom + 20}px` : 'none'}; + left: ${dateModalPos.left ? `${dateModalPos.left + 20}px` : 'none'}; + gap: 1rem; + + width: 50rem; + padding: 4rem; + border-radius: 4px; + box-shadow: 0 0 5px ${colors.GRAY_500}; + + background: ${colors.WHITE}; +`; + +const headerStyle = ({ flex }: Theme) => css` + ${flex.column} + + justify-content: flex-end; + gap: 1rem; + + width: 100%; + margin-bottom: 2rem; +`; + +const dayTextStyle = ({ colors }: Theme, day: number) => css` + font-size: 3rem; + color: ${day === 0 && colors.RED_400}; + text-align: right; +`; + +const dateTextStyle = ({ colors }: Theme, day: number, isToday: boolean) => css` + width: 5rem; + height: 5rem; + padding: 1rem; + border-radius: 50%; + + background: ${isToday && colors.YELLOW_500}; + + font-size: 2.5rem; + font-weight: 500; + color: ${isToday ? colors.WHITE : day === 0 ? colors.RED_400 : colors.GRAY_700}; + text-align: center; + line-height: 3rem; +`; + +const itemWithBackgroundStyle = (colorCode: string) => css` + width: 100%; + height: 5rem; + padding: 1rem; + + background: ${colorCode}; + + font-size: 2.75rem; + color: white; + white-space: nowrap; + line-height: 2.75rem; +`; + +const itemWithoutBackgroundStyle = ({ colors }: Theme, colorCode: string) => css` + ${itemWithBackgroundStyle(colorCode)}; + + overflow: hidden; + + border-left: 3px solid ${colorCode}; + + background: ${colors.WHITE}; + + color: black; +`; + +export { + dateModalStyle, + dateTextStyle, + dayTextStyle, + headerStyle, + itemWithBackgroundStyle, + itemWithoutBackgroundStyle, +}; diff --git a/frontend/src/components/DateModal/DateModal.tsx b/frontend/src/components/DateModal/DateModal.tsx new file mode 100644 index 00000000..59d17c90 --- /dev/null +++ b/frontend/src/components/DateModal/DateModal.tsx @@ -0,0 +1,106 @@ +import { useTheme } from '@emotion/react'; + +import { ModalPosType } from '@/@types'; +import { CalendarType } from '@/@types/calendar'; +import { ScheduleType } from '@/@types/schedule'; + +import { CALENDAR } from '@/constants'; +import { DAYS } from '@/constants/date'; + +import { getFormattedDate, getISODateString, getThisDate, getThisMonth } from '@/utils/date'; + +import { + dateModalStyle, + dateTextStyle, + dayTextStyle, + headerStyle, + itemWithBackgroundStyle, + itemWithoutBackgroundStyle, +} from './DateModal.styles'; + +interface DateModalProps { + dateModalPos: ModalPosType; + moreDateInfo: CalendarType; + longTermsWithPriority: { schedule: ScheduleType; priority: number }[]; + allDaysWithPriority: { schedule: ScheduleType; priority: number }[]; + fewHoursWithPriority: { schedule: ScheduleType; priority: number }[]; +} + +function DateModal({ + dateModalPos, + moreDateInfo, + longTermsWithPriority, + allDaysWithPriority, + fewHoursWithPriority, +}: DateModalProps) { + const theme = useTheme(); + + return ( +
+
+ {DAYS[moreDateInfo.day]} + + {moreDateInfo.date} + +
+ + {longTermsWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const endDate = getISODateString(el.schedule.endDateTime); + const nowDate = getFormattedDate(moreDateInfo.year, moreDateInfo.month, moreDateInfo.date); + + return ( + startDate <= nowDate && + nowDate <= endDate && ( +
+ {el.schedule.title || CALENDAR.EMPTY_TITLE} +
+ ) + ); + })} + + {allDaysWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const nowDate = getFormattedDate(moreDateInfo.year, moreDateInfo.month, moreDateInfo.date); + + return ( + startDate === nowDate && ( +
+ {el.schedule.title || CALENDAR.EMPTY_TITLE} +
+ ) + ); + })} + + {fewHoursWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const nowDate = getFormattedDate(moreDateInfo.year, moreDateInfo.month, moreDateInfo.date); + + return ( + startDate === nowDate && ( +
+ {el.schedule.title || CALENDAR.EMPTY_TITLE} +
+ ) + ); + })} +
+ ); +} + +export default DateModal; diff --git a/frontend/src/components/FilterCategoryItem/FilterCategoryItem.styles.ts b/frontend/src/components/FilterCategoryItem/FilterCategoryItem.styles.ts new file mode 100644 index 00000000..dcdb8d3c --- /dev/null +++ b/frontend/src/components/FilterCategoryItem/FilterCategoryItem.styles.ts @@ -0,0 +1,112 @@ +import { css, Theme } from '@emotion/react'; + +const itemStyle = ({ colors, flex }: Theme) => css` + ${flex.row} + + justify-content: space-between; + + width: 100%; + height: 8rem; + + &:hover { + background-color: ${colors.GRAY_100}; + + button { + visibility: visible; + } + } +`; + +const checkBoxNameStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap: 1rem; + + &:hover { + cursor: pointer; + } +`; + +const nameStyle = css` + overflow: hidden; + position: relative; + + width: 32rem; + + white-space: nowrap; + text-overflow: ellipsis; +`; + +const colorStyle = (color: string) => css` + width: 5rem; + height: 5rem; + border-radius: 50%; + + background: ${color}; + + &:hover { + filter: none; + transform: scale(1.2); + } +`; + +const headerStyle = css` + padding: 2rem; + + font-size: 5rem; +`; + +const iconStyle = css` + visibility: hidden; +`; + +const paletteStyle = ({ colors }: Theme) => css` + position: absolute; + right: 0; + z-index: 30; + + display: grid; + grid-template-columns: repeat(4, 1fr); + place-items: center; + gap: 2rem; + + width: 35rem; + padding: 2rem; + border: 1px solid ${colors.GRAY_300}; + border-radius: 4px; + + background: ${colors.WHITE}; +`; + +const paletteLayoutStyle = css` + position: relative; +`; + +const outerStyle = css` + position: fixed; + left: 0; + top: 16rem; + z-index: 20; + + width: 100%; + height: 100%; + + background-color: transparent; +`; + +const grayTextStyle = ({ colors }: Theme) => css` + color: ${colors.GRAY_600}; +`; + +export { + itemStyle, + colorStyle, + checkBoxNameStyle, + grayTextStyle, + headerStyle, + iconStyle, + nameStyle, + outerStyle, + paletteStyle, + paletteLayoutStyle, +}; diff --git a/frontend/src/components/FilterCategoryItem/FilterCategoryItem.tsx b/frontend/src/components/FilterCategoryItem/FilterCategoryItem.tsx new file mode 100644 index 00000000..d438af96 --- /dev/null +++ b/frontend/src/components/FilterCategoryItem/FilterCategoryItem.tsx @@ -0,0 +1,128 @@ +import { useMutation, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useToggle from '@/hooks/useToggle'; + +import { SubscriptionType } from '@/@types/subscription'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Spinner from '@/components/@common/Spinner/Spinner'; + +import { CATEGORY_TYPE } from '@/constants/category'; +import { PALETTE } from '@/constants/style'; + +import subscriptionApi from '@/api/subscription'; + +import { BiPalette } from 'react-icons/bi'; +import { RiCheckboxBlankLine, RiCheckboxFill } from 'react-icons/ri'; + +import { + checkBoxNameStyle, + colorStyle, + grayTextStyle, + iconStyle, + itemStyle, + nameStyle, + outerStyle, + paletteLayoutStyle, + paletteStyle, +} from './FilterCategoryItem.styles'; + +interface FilterItemProps { + subscription: SubscriptionType; +} + +function FilterCategoryItem({ subscription }: FilterItemProps) { + const { accessToken } = useRecoilValue(userState); + + const { state: isPaletteOpen, toggleState: togglePaletteOpen } = useToggle(); + + const queryClient = useQueryClient(); + + const { isLoading, mutate } = useMutation( + (body: Pick | Pick) => + subscriptionApi.patch(accessToken, subscription.id, body), + { + onSuccess: () => queryClient.invalidateQueries(), + } + ); + + const handleClickFilledCheckBox = (colorCode: string) => { + mutate({ + checked: false, + colorCode, + }); + }; + + const handleClickBlankCheckBox = (colorCode: string) => { + mutate({ + checked: true, + colorCode, + }); + }; + + const handleClickPalette = (checked: boolean, colorCode: string) => { + mutate({ checked, colorCode }); + togglePaletteOpen(); + }; + + return ( +
+
+ {subscription.checked ? ( + + ) : ( + + )} + + {subscription.category.name} + + {subscription.category.categoryType === CATEGORY_TYPE.GOOGLE && ' (구글)'} + {subscription.category.categoryType === CATEGORY_TYPE.PERSONAL && ' (기본)'} + + +
+
+ {isLoading && } + + + {isPaletteOpen && ( + <> +
+
+ {PALETTE.map((color) => { + return ( + + ); + })} +
+ + )} +
+
+ ); +} + +export default FilterCategoryItem; diff --git a/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.stories.tsx b/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.stories.tsx new file mode 100644 index 00000000..7d761a95 --- /dev/null +++ b/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.stories.tsx @@ -0,0 +1,16 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import FilterCategoryListFallback from './FilterCategoryList.fallback'; + +export default { + title: 'Components/FilterCategoryListFallback', + component: FilterCategoryListFallback, +} as ComponentMeta; + +const Template: ComponentStory = () => ( + +); + +const Primary = Template.bind({}); + +export { Primary }; diff --git a/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.tsx b/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.tsx new file mode 100644 index 00000000..ff5a7e8d --- /dev/null +++ b/frontend/src/components/FilterCategoryList/FilterCategoryList.fallback.tsx @@ -0,0 +1,22 @@ +import { useTheme } from '@emotion/react'; + +import Skeleton from '@/components/@common/Skeleton/Skeleton'; + +import { headerStyle, skeletonListStyle, skeletonStyle } from './FilterCategoryList.styles'; + +function FilterCategoryFallback() { + const theme = useTheme(); + + return ( +
+ 구독 카테고리 +
+ {new Array(10).fill(0).map((el, index) => ( + + ))} +
+
+ ); +} + +export default FilterCategoryFallback; diff --git a/frontend/src/components/FilterCategoryList/FilterCategoryList.styles.ts b/frontend/src/components/FilterCategoryList/FilterCategoryList.styles.ts new file mode 100644 index 00000000..d297352e --- /dev/null +++ b/frontend/src/components/FilterCategoryList/FilterCategoryList.styles.ts @@ -0,0 +1,85 @@ +import { css, Theme } from '@emotion/react'; + +const listStyle = ({ flex }: Theme, isSideBarOpen: boolean) => css` + ${flex.column} + + display: ${isSideBarOpen ? 'flex' : 'none'}; + justify-content: flex-start; + + width: 54rem; + + font-size: 4rem; +`; + +const headerStyle = ({ flex }: Theme) => css` + ${flex.row} + + justify-content: space-between; + + width: 100%; + height: 8rem; + + font-weight: bold; +`; + +const googleImportButtonStyle = ({ colors, flex }: Theme) => css` + ${flex.row} + + position: relative; + + width: 100%; + height: 11rem; + padding: 4rem; + margin: 2rem 0 3rem; + border-radius: 4px; + border: 1px solid ${colors.GRAY_600}; + + background: ${colors.WHITE}; + + font-size: 4rem; + color: ${colors.GRAY_600}; + + &:hover { + filter: none; + } +`; + +const googleImportTextStyle = css` + width: 100%; +`; + +const contentStyle = css` + display: flex; + flex-direction: column; + gap: 2rem; + + width: 100%; +`; + +const skeletonStyle = ({ flex }: Theme) => css` + ${flex.column}; + + gap: 5rem; +`; + +const skeletonListStyle = ({ flex }: Theme, isSideBarOpen: boolean) => css` + ${flex.column}; + + display: ${isSideBarOpen ? 'flex' : 'none'}; + justify-content: flex-start; + + width: 54rem; + margin-top: 16rem; + + font-size: 4rem; +`; + +export { + contentStyle, + googleImportButtonStyle, + googleImportTextStyle, + headerStyle, + listStyle, + skeletonStyle, + skeletonListStyle, +}; diff --git a/frontend/src/components/FilterCategoryList/FilterCategoryList.tsx b/frontend/src/components/FilterCategoryList/FilterCategoryList.tsx new file mode 100644 index 00000000..81b0ffd7 --- /dev/null +++ b/frontend/src/components/FilterCategoryList/FilterCategoryList.tsx @@ -0,0 +1,72 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useToggle from '@/hooks/useToggle'; + +import { SubscriptionType } from '@/@types/subscription'; + +import { sideBarState, userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; +import FilterCategoryItem from '@/components/FilterCategoryItem/FilterCategoryItem'; +import GoogleImportModal from '@/components/GoogleImportModal/GoogleImportModal'; + +import { CACHE_KEY } from '@/constants/api'; + +import subscriptionApi from '@/api/subscription'; + +import { FcGoogle } from 'react-icons/fc'; + +import FilterCategoryFallback from './FilterCategoryList.fallback'; +import { + contentStyle, + googleImportButtonStyle, + googleImportTextStyle, + headerStyle, + listStyle, +} from './FilterCategoryList.styles'; + +function FilterCategoryList() { + const { accessToken } = useRecoilValue(userState); + const isSideBarOpen = useRecoilValue(sideBarState); + + const theme = useTheme(); + + const { state: isGoogleImportModalOpen, toggleState: toggleGoogleImportModalOpen } = useToggle(); + + const { isLoading, data } = useQuery, AxiosError>( + CACHE_KEY.SUBSCRIPTIONS, + () => subscriptionApi.get(accessToken) + ); + + if (isLoading || data === undefined) { + return ; + } + + const handleClickGoogleImportButton = () => { + toggleGoogleImportModalOpen(); + }; + + return ( +
+ 구독 카테고리 + +
+ {data?.data.map((el) => { + return ; + })} +
+ + + +
+ ); +} + +export default FilterCategoryList; diff --git a/frontend/src/components/Footer/Footer.styles.ts b/frontend/src/components/Footer/Footer.styles.ts new file mode 100644 index 00000000..a8c407e0 --- /dev/null +++ b/frontend/src/components/Footer/Footer.styles.ts @@ -0,0 +1,20 @@ +import { css, Theme } from '@emotion/react'; + +const footerStyle = ({ colors, flex }: Theme) => css` + ${flex.column} + + width: 100%; + height: 40rem; + + color: ${colors.GRAY_600}; + line-height: 150%; +`; + +const privacyPolicyButtonStyle = css` + margin: 1rem; + + font-size: inherit; + color: inherit; +`; + +export { footerStyle, privacyPolicyButtonStyle }; diff --git a/frontend/src/components/Footer/Footer.tsx b/frontend/src/components/Footer/Footer.tsx new file mode 100644 index 00000000..38cbd633 --- /dev/null +++ b/frontend/src/components/Footer/Footer.tsx @@ -0,0 +1,28 @@ +import { useNavigate } from 'react-router-dom'; + +import Button from '@/components/@common/Button/Button'; + +import { PATH } from '@/constants'; + +import { footerStyle, privacyPolicyButtonStyle } from './Footer.styles'; + +function Footer() { + const navigate = useNavigate(); + + const handleClickPrivacyPolicyButton = () => { + navigate(PATH.POLICY); + }; + + return ( +
+

우아한테크코스 4기 달록

+

서울특별시 송파구 올림픽로35다길 42, 14층 (한국루터회관)

+

Copyright © 2022 달록 - All rights reserved.

+ +
+ ); +} + +export default Footer; diff --git a/frontend/src/components/GoogleImportModal/GoogleImportModal.styles.ts b/frontend/src/components/GoogleImportModal/GoogleImportModal.styles.ts new file mode 100644 index 00000000..d594c053 --- /dev/null +++ b/frontend/src/components/GoogleImportModal/GoogleImportModal.styles.ts @@ -0,0 +1,58 @@ +import { css, Theme } from '@emotion/react'; + +const layoutStyle = ({ colors, flex }: Theme) => css` + ${flex.column}; + + align-items: flex-start; + justify-content: center; + gap: 10rem; + + width: 120rem; + height: 120rem; + padding: 12.5rem; + border-radius: 12px; + + background: ${colors.WHITE}; + + color: ${colors.GRAY_700}; +`; + +const headerStyle = css` + font-size: 8rem; + font-weight: bold; + text-align: center; +`; + +const titleStyle = css` + padding: 0 1rem; + font-size: 4rem; +`; + +const googleSelectStyle = ({ colors }: Theme) => css` + padding: 3rem; + border: 1px solid ${colors.GRAY_400}; + border-radius: 8px; + + font-size: 4rem; +`; + +const googleSelectBoxStyle = ({ flex }: Theme) => css` + ${flex.column}; + + align-items: flex-start; + + gap: 2rem; + + font-size: 4rem; +`; + +const formStyle = ({ flex }: Theme) => css` + ${flex.column}; + + align-items: flex-start; + + width: 100%; + height: 100%; +`; + +export { formStyle, googleSelectBoxStyle, googleSelectStyle, headerStyle, layoutStyle, titleStyle }; diff --git a/frontend/src/components/GoogleImportModal/GoogleImportModal.tsx b/frontend/src/components/GoogleImportModal/GoogleImportModal.tsx new file mode 100644 index 00000000..c5440136 --- /dev/null +++ b/frontend/src/components/GoogleImportModal/GoogleImportModal.tsx @@ -0,0 +1,132 @@ +import { validateNotEmpty } from '@/validation'; +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useControlledInput from '@/hooks/useControlledInput'; +import useValidateCategory from '@/hooks/useValidateCategory'; + +import { GoogleCalendarGetResponseType, GoogleCalendarPostBodyType } from '@/@types/googleCalendar'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; +import Spinner from '@/components/@common/Spinner/Spinner'; +import { + cancelButtonStyle, + content, + controlButtons, + saveButtonStyle, +} from '@/components/CategoryAddModal/CategoryAddModal.styles'; + +import { CACHE_KEY } from '@/constants/api'; + +import googleCalendarApi from '@/api/googleCalendar'; + +import { + formStyle, + googleSelectBoxStyle, + googleSelectStyle, + headerStyle, + layoutStyle, + titleStyle, +} from './GoogleImportModal.styles'; + +interface GoogleImportModal { + closeModal: () => void; +} + +function GoogleImportModal({ closeModal }: GoogleImportModal) { + const theme = useTheme(); + + const { accessToken } = useRecoilValue(userState); + + const { categoryValue, getCategoryErrorMessage, isValidCategory } = useValidateCategory(); + + const { inputValue: googleCalendarInputValue, onChangeValue: onChangeGoogleCalendarInputValue } = + useControlledInput(); + + const queryClient = useQueryClient(); + + const { isLoading, data } = useQuery, AxiosError>( + CACHE_KEY.GOOGLE_CALENDAR, + () => googleCalendarApi.get(accessToken) + ); + + const { mutate } = useMutation( + (body: GoogleCalendarPostBodyType) => googleCalendarApi.post(accessToken, body), + { + onSuccess: () => onSuccessPostCategory(), + } + ); + + const handleSubmitCategoryAddForm = (e: React.FormEvent) => { + e.preventDefault(); + + mutate({ externalId: googleCalendarInputValue, name: categoryValue.inputValue }); + }; + + const onSuccessPostCategory = () => { + queryClient.invalidateQueries(CACHE_KEY.CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.MY_CATEGORIES); + queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); + + closeModal(); + }; + + if (isLoading || data === undefined) { + return ; + } + + return ( +
+
구글 캘린더 가져오기
+
+
+
구글 캘린더 목록
+ +
+ +
+
+
+
+ + +
+
+
+ ); +} + +export default GoogleImportModal; diff --git a/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts b/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts new file mode 100644 index 00000000..587c27cd --- /dev/null +++ b/frontend/src/components/MyCategoryItem/MyCategoryItem.style.ts @@ -0,0 +1,63 @@ +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 new file mode 100644 index 00000000..e041a8dd --- /dev/null +++ b/frontend/src/components/MyCategoryItem/MyCategoryItem.tsx @@ -0,0 +1,101 @@ +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 new file mode 100644 index 00000000..8f7c2f26 --- /dev/null +++ b/frontend/src/components/MyCategoryList/MyCategoryList.fallback.tsx @@ -0,0 +1,33 @@ +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 new file mode 100644 index 00000000..17a620cf --- /dev/null +++ b/frontend/src/components/MyCategoryList/MyCategoryList.styles.ts @@ -0,0 +1,47 @@ +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 new file mode 100644 index 00000000..d724cff5 --- /dev/null +++ b/frontend/src/components/MyCategoryList/MyCategoryList.tsx @@ -0,0 +1,41 @@ +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; diff --git a/frontend/src/components/NavBar/NavBar.stories.tsx b/frontend/src/components/NavBar/NavBar.stories.tsx new file mode 100644 index 00000000..9c4dd234 --- /dev/null +++ b/frontend/src/components/NavBar/NavBar.stories.tsx @@ -0,0 +1,12 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import NavBar from './NavBar'; + +export default { + title: 'Components/NavBar', + component: NavBar, +} as ComponentMeta; + +const Template: ComponentStory = () => ; + +export const Primary = Template.bind({}); diff --git a/frontend/src/components/NavBar/NavBar.styles.ts b/frontend/src/components/NavBar/NavBar.styles.ts new file mode 100644 index 00000000..b6f24759 --- /dev/null +++ b/frontend/src/components/NavBar/NavBar.styles.ts @@ -0,0 +1,86 @@ +import { css, Theme } from '@emotion/react'; + +const navBar = ({ colors, flex }: Theme) => css` + ${flex.row} + + justify-content: space-between; + position: fixed; + top: 0; + left: 0; + z-index: 20; + + width: 100%; + height: 16rem; + padding: 2rem 5rem 2rem 2rem; + + background: ${colors.WHITE}; + + box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); +`; + +const menus = ({ flex }: Theme) => css` + ${flex.row} + + gap: 3rem; +`; + +const logo = ({ colors, flex }: Theme) => css` + ${flex.row} + + position: relative; + + background: transparent; + + font-size: 5rem; + font-weight: bold; + color: ${colors.GRAY_700}; +`; + +const logoImg = css` + width: 6rem; + height: 6rem; +`; + +const logoText = css` + margin-left: 2rem; +`; + +const menu = ({ colors, flex }: Theme) => css` + ${logo({ colors, flex })} + + width: 11rem; + height: 11rem; + + font-size: 7rem; + + &: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; +`; + +export { logo, logoImg, logoText, menu, menus, menuTitle, navBar }; diff --git a/frontend/src/components/NavBar/NavBar.tsx b/frontend/src/components/NavBar/NavBar.tsx new file mode 100644 index 00000000..c996ef20 --- /dev/null +++ b/frontend/src/components/NavBar/NavBar.tsx @@ -0,0 +1,107 @@ +import { useTheme } from '@emotion/react'; +import { lazy, Suspense } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilState, useRecoilValue } from 'recoil'; + +import useToggle from '@/hooks/useToggle'; + +import { userState } from '@/recoil/atoms'; +import { sideBarSelector } from '@/recoil/selectors'; + +import Button from '@/components/@common/Button/Button'; +import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; +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 BlackLogo from '../../assets/dallog_black.png'; +import { logo, logoImg, logoText, menu, menus, menuTitle, navBar } from './NavBar.styles'; + +const Profile = lazy(() => import('@/components/Profile/Profile')); + +function NavBar() { + const { accessToken } = useRecoilValue(userState); + const [isSideBarOpen, toggleSideBarOpen] = useRecoilState(sideBarSelector); + + const theme = useTheme(); + const navigate = useNavigate(); + + const { state: isProfileModalOpen, toggleState: toggleProfileModalOpen } = useToggle(); + + const handleClickSideBarButton = () => { + toggleSideBarOpen(isSideBarOpen); + }; + + const handleClickMainButton = () => { + navigate(PATH.MAIN); + }; + + const handleClickCategoryMenuButton = () => { + navigate(PATH.CATEGORY); + }; + + const handleClickSchedulingMenuButton = () => { + navigate(PATH.SCHEDULING); + }; + + const handleClickProfileMenuButton = () => { + toggleProfileModalOpen(); + }; + + return ( +
+
+ {accessToken && ( + + )} + +
+
+ {accessToken && ( + <> + + + + + + }> + + + + + )} +
+
+ ); +} + +export default NavBar; diff --git a/frontend/src/components/Profile/Profile.fallback.stories.tsx b/frontend/src/components/Profile/Profile.fallback.stories.tsx new file mode 100644 index 00000000..3648f2f7 --- /dev/null +++ b/frontend/src/components/Profile/Profile.fallback.stories.tsx @@ -0,0 +1,15 @@ +import { css } from '@emotion/react'; +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import ProfileFallback from './Profile.fallback'; + +export default { + title: 'Components/ProfileFallback', + component: ProfileFallback, +} as ComponentMeta; + +const Template: ComponentStory = () => ; + +const Primary = Template.bind({}); + +export { Primary }; diff --git a/frontend/src/components/Profile/Profile.fallback.tsx b/frontend/src/components/Profile/Profile.fallback.tsx new file mode 100644 index 00000000..da8f50d2 --- /dev/null +++ b/frontend/src/components/Profile/Profile.fallback.tsx @@ -0,0 +1,18 @@ +import Skeleton from '@/components/@common/Skeleton/Skeleton'; + +import { imageStyle, layoutStyle, skeletonStyle } from './Profile.styles'; + +function ProfileFallback() { + return ( +
+ +
+ + + +
+
+ ); +} + +export default ProfileFallback; diff --git a/frontend/src/components/Profile/Profile.styles.ts b/frontend/src/components/Profile/Profile.styles.ts new file mode 100644 index 00000000..f6913f5c --- /dev/null +++ b/frontend/src/components/Profile/Profile.styles.ts @@ -0,0 +1,129 @@ +import { css, Theme } from '@emotion/react'; + +const contentStyle = css` + width: 100%; + + text-align: center; +`; + +const emailStyle = ({ colors }: Theme) => css` + font-size: 3rem; + color: ${colors.GRAY_500}; +`; + +const imageStyle = css` + width: 35rem; + height: 35rem; + border-radius: 50%; +`; + +const inputStyle = { + div: css` + height: 3rem; + + font-size: 3rem; + `, + input: css` + height: 3rem; + + font-size: 3rem; + `, +}; + +const layoutStyle = ({ flex, colors }: Theme) => css` + ${flex.column}; + + justify-content: space-around; + gap: 5rem; + position: absolute; + top: 15rem; + right: 2rem; + + width: 60rem; + padding: 5rem; + box-shadow: 0 0 1px 1px rgba(0, 0, 0, 0.25); + border-radius: 10px; + + background: ${colors.WHITE}; + + font-size: 4rem; +`; + +const logoutButtonStyle = ({ colors }: Theme) => css` + padding: 2rem 3rem; + border: 1px solid ${colors.GRAY_400}; + border-radius: 3px; + + font-size: 3rem; +`; + +const menu = ({ colors }: Theme) => css` + position: relative; + + width: 9rem; + height: 9rem; + + &: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 nameButtonStyle = ({ flex }: Theme) => css` + ${flex.row}; + + justify-content: flex-end; + gap: 2rem; + + font-size: 3rem; +`; + +const nameStyle = css` + margin-left: 7rem; + + font-size: 3.5rem; +`; + +const skeletonStyle = ({ flex }: Theme) => css` + ${flex.column}; + + gap: 3rem; +`; + +export { + contentStyle, + emailStyle, + imageStyle, + inputStyle, + layoutStyle, + logoutButtonStyle, + menu, + menuTitle, + nameStyle, + nameButtonStyle, + skeletonStyle, +}; diff --git a/frontend/src/components/Profile/Profile.tsx b/frontend/src/components/Profile/Profile.tsx new file mode 100644 index 00000000..da150d39 --- /dev/null +++ b/frontend/src/components/Profile/Profile.tsx @@ -0,0 +1,133 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { useRef, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilValue } from 'recoil'; + +import { ProfileType } from '@/@types/profile'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; + +import { PATH } from '@/constants'; +import { CACHE_KEY } from '@/constants/api'; +import { CONFIRM_MESSAGE } from '@/constants/message'; + +import { createPostBody } from '@/utils'; +import { removeAccessToken } from '@/utils/storage'; + +import profileApi from '@/api/profile'; + +import { AiOutlineCheck } from 'react-icons/ai'; +import { FiEdit3 } from 'react-icons/fi'; + +import { + contentStyle, + emailStyle, + imageStyle, + inputStyle, + layoutStyle, + logoutButtonStyle, + menu, + menuTitle, + nameButtonStyle, + nameStyle, +} from './Profile.styles'; + +function Profile() { + const [isEditingName, setEditingName] = useState(false); + + const inputRef = { + displayName: useRef(null), + }; + + const navigate = useNavigate(); + + const { accessToken } = useRecoilValue(userState); + + const queryClient = useQueryClient(); + const { data } = useQuery, AxiosError>(CACHE_KEY.PROFILE, () => + profileApi.get(accessToken) + ); + + const { mutate } = useMutation( + (body: { displayName: string }) => profileApi.patch(accessToken, body), + { + onSuccess: () => { + queryClient.invalidateQueries(CACHE_KEY.PROFILE); + queryClient.invalidateQueries(CACHE_KEY.CATEGORIES); + }, + } + ); + + const handleClickModifyButton = () => { + setEditingName(true); + }; + + const handleClickCompleteButton = (defaultName: string | undefined) => { + if (defaultName === undefined) { + return; + } + + const body = createPostBody(inputRef); + + if (body.displayName === '') { + body.displayName = defaultName; + } + + mutate(body); + setEditingName(false); + }; + + const handleClickLogoutButton = () => { + if (window.confirm(CONFIRM_MESSAGE.LOGOUT)) { + removeAccessToken(); + navigate(PATH.MAIN); + location.reload(); + } + }; + + return ( +
+ 프로필 이미지 +
+ {isEditingName ? ( +
+
+ + + ) : ( +
+ {data?.data.displayName} + +
+ )} + {data?.data.email} +
+ +
+ ); +} + +export default Profile; diff --git a/frontend/src/components/ProtectRoute/ProtectRoute.tsx b/frontend/src/components/ProtectRoute/ProtectRoute.tsx new file mode 100644 index 00000000..23176867 --- /dev/null +++ b/frontend/src/components/ProtectRoute/ProtectRoute.tsx @@ -0,0 +1,21 @@ +import { Navigate, Outlet } from 'react-router-dom'; + +import useUserValue from '@/hooks/useUserValue'; + +import { PATH } from '@/constants'; + +function ProtectRoute() { + const { isAuthenticating, user } = useUserValue(); + + if (isAuthenticating) { + return <>; + } + + if (!user.accessToken) { + return ; + } + + return ; +} + +export default ProtectRoute; diff --git a/frontend/src/components/PublicRoute/PublicRoute.tsx b/frontend/src/components/PublicRoute/PublicRoute.tsx new file mode 100644 index 00000000..b6b15b61 --- /dev/null +++ b/frontend/src/components/PublicRoute/PublicRoute.tsx @@ -0,0 +1,15 @@ +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/components/ScheduleAddButton/ScheduleAddButton.stories.tsx b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.stories.tsx new file mode 100644 index 00000000..39160033 --- /dev/null +++ b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.stories.tsx @@ -0,0 +1,17 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import ScheduleAddButton from './ScheduleAddButton'; + +export default { + title: 'Components/ScheduleAddButton', + component: ScheduleAddButton, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ( + +); + +export const Primary = Template.bind({}); +Primary.args = { + onClick: () => void 0, +}; diff --git a/frontend/src/components/ScheduleAddButton/ScheduleAddButton.styles.ts b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.styles.ts new file mode 100644 index 00000000..c9f21a55 --- /dev/null +++ b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.styles.ts @@ -0,0 +1,20 @@ +import { css, Theme } from '@emotion/react'; + +const scheduleAddButton = ({ colors }: Theme) => css` + position: fixed; + right: 7rem; + bottom: 7rem; + + width: 13rem; + height: 13rem; + border-radius: 50%; + box-shadow: 0px 0px 4px rgba(0, 0, 0, 0.25); + + background: ${colors.WHITE}; + + font-size: 7rem; + line-height: 7rem; + color: ${colors.YELLOW_500}; +`; + +export { scheduleAddButton }; diff --git a/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx new file mode 100644 index 00000000..e835f153 --- /dev/null +++ b/frontend/src/components/ScheduleAddButton/ScheduleAddButton.tsx @@ -0,0 +1,23 @@ +import { useTheme } from '@emotion/react'; + +import Button from '@/components/@common/Button/Button'; + +import { BsCalendarPlusFill } from 'react-icons/bs'; + +import { scheduleAddButton } from './ScheduleAddButton.styles'; + +interface ScheduleAddButtonProps { + onClick: () => void; +} + +function ScheduleAddButton({ onClick }: ScheduleAddButtonProps) { + const theme = useTheme(); + + return ( + + ); +} + +export default ScheduleAddButton; diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.stories.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.stories.tsx new file mode 100644 index 00000000..dc2129d8 --- /dev/null +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.stories.tsx @@ -0,0 +1,20 @@ +import { ComponentMeta, ComponentStory } from '@storybook/react'; + +import ScheduleAddModal from './ScheduleAddModal'; + +export default { + title: 'Components/ScheduleAddModal', + component: ScheduleAddModal, +} as ComponentMeta; + +const Template: ComponentStory = (args) => ; + +export const Primary = Template.bind({}); +Primary.args = { + dateInfo: { + year: 2022, + month: 8, + date: 5, + day: 5, + }, +}; diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts new file mode 100644 index 00000000..f56f1364 --- /dev/null +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.styles.ts @@ -0,0 +1,116 @@ +import { css, Theme } from '@emotion/react'; + +const scheduleAddModal = ({ colors }: Theme) => css` + width: 120rem; + padding: 12.5rem; + border-radius: 12px; + + background: ${colors.WHITE}; +`; + +const form = ({ flex }: Theme) => css` + ${flex.column}; + + gap: 6rem; + + height: 100%; +`; + +const categorySelect = ({ colors }: Theme) => css` + width: 100%; + padding: 3rem; + border: 1px solid ${colors.GRAY_400}; + border-radius: 8px; + + font-size: 4rem; +`; + +const allDayButton = ({ colors }: Theme, isAllDay: boolean) => css` + width: 100%; + height: 9rem; + border: 1px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${isAllDay ? colors.YELLOW_500 : colors.WHITE}; + + font-size: 5rem; + color: ${isAllDay ? colors.WHITE : colors.GRAY_600}; +`; + +const dateTime = ({ flex }: Theme) => css` + ${flex.column} + + gap: 2.5rem; + + width: 100%; +`; + +const arrow = ({ colors }: Theme) => css` + font-size: 6rem; + font-weight: bold; + color: ${colors.GRAY_500}; +`; + +const controlButtons = ({ flex }: Theme) => css` + ${flex.row} + + align-self: flex-end; + gap: 5rem; +`; + +const cancelButton = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border: 1px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${colors.WHITE}; + + font-size: 4rem; + color: ${colors.GRAY_600}; +`; + +const saveButton = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border-radius: 8px; + + filter: drop-shadow(0px 2px 2px ${colors.GRAY_400}); + + background: ${colors.YELLOW_500}; + + font-size: 4rem; + color: ${colors.WHITE}; +`; + +const labelStyle = ({ colors }: Theme) => css` + padding: 0 1rem; + + font-size: 4rem; + color: ${colors.GRAY_800}; +`; + +const selectBoxStyle = ({ flex }: Theme) => css` + ${flex.column}; + + align-items: flex-start; + gap: 2.5rem; + + width: 100%; +`; + +export { + allDayButton, + arrow, + categorySelect, + cancelButton, + controlButtons, + dateTime, + form, + labelStyle, + saveButton, + scheduleAddModal, + selectBoxStyle, +}; diff --git a/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx new file mode 100644 index 00000000..d1113c9a --- /dev/null +++ b/frontend/src/components/ScheduleAddModal/ScheduleAddModal.tsx @@ -0,0 +1,222 @@ +import { validateLength } from '@/validation'; +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +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 { CalendarType } from '@/@types/calendar'; +import { CategoryType } from '@/@types/category'; +import { ScheduleType } from '@/@types/schedule'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; + +import { CACHE_KEY } from '@/constants/api'; +import { DATE_TIME } from '@/constants/date'; +import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; + +import { getDate, getDateTime } from '@/utils/date'; + +import categoryApi from '@/api/category'; +import scheduleApi from '@/api/schedule'; + +import { + allDayButton, + arrow, + cancelButton, + categorySelect, + controlButtons, + dateTime, + form, + labelStyle, + saveButton, + scheduleAddModal, + selectBoxStyle, +} from './ScheduleAddModal.styles'; + +interface ScheduleAddModalProps { + dateInfo: CalendarType | null; + closeModal: () => void; +} + +function ScheduleAddModal({ dateInfo, closeModal }: ScheduleAddModalProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const [isAllDay, setAllDay] = useState(true); + + const queryClient = useQueryClient(); + + const { data } = useQuery, AxiosError>( + CACHE_KEY.MY_CATEGORIES, + () => categoryApi.getMy(accessToken), + { + onSuccess: (data) => onSuccessGetCategories(data), + } + ); + + const categoryId = useControlledInput(); + + const { + isLoading, + error, + mutate: postSchedule, + } = useMutation< + AxiosResponse<{ schedules: ScheduleType[] }>, + AxiosError, + Omit, + unknown + >((body) => scheduleApi.post(accessToken, Number(categoryId.inputValue), body), { + onSuccess: () => { + onSuccessPostSchedule(); + }, + }); + + const dateFieldset = isAllDay + ? { + type: 'date', + initialValue: getDate(dateInfo), + } + : { + type: 'datetime-local', + initialValue: getDateTime(dateInfo), + }; + + const validationSchedule = useValidateSchedule({ + initialStartDateTime: dateFieldset.initialValue, + initialEndDateTime: dateFieldset.initialValue, + }); + + const handleClickAllDayButton = () => { + setAllDay((prev) => !prev); + }; + + const handleSubmitScheduleAddForm = (e: React.FormEvent) => { + e.preventDefault(); + + const body = { + title: validationSchedule.title.inputValue, + startDateTime: validationSchedule.startDateTime.inputValue, + endDateTime: validationSchedule.endDateTime.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); + }; + + const onSuccessGetCategories = (data: AxiosResponse) => { + categoryId.setInputValue(`${data.data[0].id}`); + }; + + const onSuccessPostSchedule = () => { + queryClient.invalidateQueries(CACHE_KEY.SCHEDULES); + + closeModal(); + }; + + if (isLoading) return <>Loading; + + if (error) return <>Error; + + return ( +
+
+
+ 카테고리 + +
+
+ +
+
+

+
+
+
+
+ + +
+ +
+ ); +} + +export default ScheduleAddModal; diff --git a/frontend/src/components/ScheduleModal/ScheduleModal.styles.ts b/frontend/src/components/ScheduleModal/ScheduleModal.styles.ts new file mode 100644 index 00000000..06913855 --- /dev/null +++ b/frontend/src/components/ScheduleModal/ScheduleModal.styles.ts @@ -0,0 +1,119 @@ +import { css, Theme } from '@emotion/react'; + +import { ModalPosType } from '@/@types'; + +const scheduleModalStyle = ({ colors }: Theme, scheduleModalPos: ModalPosType) => css` + position: absolute; + top: ${scheduleModalPos.top ? `${scheduleModalPos.top + 20}px` : 'none'}; + right: ${scheduleModalPos.right ? `${scheduleModalPos.right + 20}px` : 'none'}; + bottom: ${scheduleModalPos.bottom ? `${scheduleModalPos.bottom + 20}px` : 'none'}; + left: ${scheduleModalPos.left ? `${scheduleModalPos.left + 20}px` : 'none'}; + + padding: 5rem 5rem 10rem 10rem; + border-radius: 8px; + box-shadow: 0 0 30px ${colors.GRAY_500}; + + background: ${colors.WHITE}; +`; + +const headerStyle = ({ flex }: Theme) => css` + ${flex.row}; + + justify-content: flex-end; +`; + +const buttonStyle = ({ colors }: Theme) => css` + position: relative; + + width: 11rem; + height: 11rem; + + font-size: 5rem; + color: ${colors.GRAY_700}; + + &:hover { + border-radius: 50%; + + background: ${colors.GRAY_100}; + + filter: none; + } + + &:hover span { + visibility: visible; + } +`; + +const buttonTitleStyle = ({ 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 contentStyle = ({ flex }: Theme) => css` + ${flex.column} + + gap: 10rem; +`; + +const contentBlockStyle = ({ colors }: Theme) => css` + display: flex; + gap: 3rem; + + width: 90rem; + + font-size: 4rem; + color: ${colors.GRAY_700}; +`; + +const scheduleIconStyle = css` + margin-top: 1rem; +`; + +const scheduleInfoStyle = ({ flex }: Theme) => css` + ${flex.column} + + align-items: flex-start; + gap: 3rem; +`; + +const scheduleTitleStyle = css` + font-size: 6rem; +`; + +const colorStyle = (colorCode: string | undefined) => css` + width: 4rem; + height: 4rem; + border-radius: 25%; + + background: ${colorCode}; +`; + +const grayTextStyle = ({ colors }: Theme) => css` + color: ${colors.GRAY_600}; +`; + +export { + buttonStyle, + buttonTitleStyle, + colorStyle, + contentBlockStyle, + contentStyle, + grayTextStyle, + headerStyle, + scheduleIconStyle, + scheduleInfoStyle, + scheduleModalStyle, + scheduleTitleStyle, +}; diff --git a/frontend/src/components/ScheduleModal/ScheduleModal.tsx b/frontend/src/components/ScheduleModal/ScheduleModal.tsx new file mode 100644 index 00000000..04516b60 --- /dev/null +++ b/frontend/src/components/ScheduleModal/ScheduleModal.tsx @@ -0,0 +1,155 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import { ModalPosType } from '@/@types'; +import { CategoryType } from '@/@types/category'; +import { ProfileType } from '@/@types/profile'; +import { ScheduleType } from '@/@types/schedule'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; + +import { CACHE_KEY } from '@/constants/api'; +import { CATEGORY_TYPE } from '@/constants/category'; +import { CONFIRM_MESSAGE } from '@/constants/message'; + +import categoryApi from '@/api/category'; +import profileApi from '@/api/profile'; +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 { + buttonStyle, + buttonTitleStyle, + colorStyle, + contentBlockStyle, + contentStyle, + grayTextStyle, + headerStyle, + scheduleIconStyle, + scheduleInfoStyle, + scheduleModalStyle, + scheduleTitleStyle, +} from './ScheduleModal.styles'; + +interface ScheduleModalProps { + scheduleModalPos: ModalPosType; + scheduleInfo: ScheduleType; + toggleScheduleModifyModalOpen: () => void; + closeModal: () => void; +} + +function ScheduleModal({ + scheduleModalPos, + scheduleInfo, + toggleScheduleModifyModalOpen, + closeModal, +}: ScheduleModalProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const queryClient = useQueryClient(); + + const { data: profileGetResponse } = useQuery, AxiosError>( + CACHE_KEY.PROFILE, + () => profileApi.get(accessToken) + ); + + const { data: categoryGetResponse } = useQuery, AxiosError>( + CACHE_KEY.CATEGORY, + () => categoryApi.getSingle(scheduleInfo.categoryId) + ); + + const { mutate } = useMutation( + () => scheduleApi.delete(accessToken, scheduleInfo.id), + { + onSuccess: () => onSuccessDeleteSchedule(), + } + ); + + const onSuccessDeleteSchedule = () => { + queryClient.invalidateQueries(CACHE_KEY.SCHEDULES); + + closeModal(); + }; + + const handleClickModifyButton = () => { + closeModal(); + toggleScheduleModifyModalOpen(); + }; + + const handleClickDeleteButton = () => { + if (confirm(CONFIRM_MESSAGE.DELETE)) { + mutate(); + } + }; + + const formatDateTime = (dateTime: string | undefined) => { + if (dateTime === undefined) { + return; + } + + return dateTime.replace('T', ' '); + }; + + const canEditSchedule = + (scheduleInfo.categoryType === CATEGORY_TYPE.NORMAL || + scheduleInfo.categoryType === CATEGORY_TYPE.PERSONAL) && + profileGetResponse?.data.id === categoryGetResponse?.data.creator.id; + + return ( +
+
+ {canEditSchedule && ( + <> + + + + )} + +
+
+
+ +
+

{scheduleInfo.title}

+

+ {formatDateTime(scheduleInfo.startDateTime)} +   →   + {formatDateTime(scheduleInfo.endDateTime)} +

+ {scheduleInfo.memo &&

{scheduleInfo.memo}

} +
+
+
+
+ + {categoryGetResponse?.data.name} + + {scheduleInfo.categoryType === CATEGORY_TYPE.GOOGLE && ' (구글)'} + {scheduleInfo.categoryType === CATEGORY_TYPE.PERSONAL && ' (기본)'} + + +
+
+
+ ); +} + +export default ScheduleModal; diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts new file mode 100644 index 00000000..0b2753de --- /dev/null +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.styles.ts @@ -0,0 +1,123 @@ +import { css, Theme } from '@emotion/react'; + +const modalStyle = ({ colors }: Theme) => css` + width: 120rem; + padding: 12.5rem; + border-radius: 12px; + + background: ${colors.WHITE}; +`; + +const formStyle = ({ flex }: Theme) => css` + ${flex.column}; + + 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 allDayButtonStyle = ({ colors }: Theme, isAllDay: boolean) => css` + width: 100%; + height: 9rem; + border: 1px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${isAllDay ? colors.YELLOW_500 : colors.WHITE}; + + font-size: 5rem; + color: ${isAllDay ? colors.WHITE : colors.GRAY_600}; +`; + +const dateTimeStyle = ({ flex }: Theme) => css` + ${flex.column} + + gap: 2.5rem; + + width: 100%; +`; + +const arrowStyle = ({ colors }: Theme) => css` + font-size: 6rem; + font-weight: bold; + color: ${colors.GRAY_500}; +`; + +const controlButtonsStyle = ({ flex }: Theme) => css` + ${flex.row} + + align-self: flex-end; + gap: 5rem; +`; + +const cancelButtonStyle = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border: 1px solid ${colors.GRAY_500}; + border-radius: 8px; + filter: drop-shadow(0 2px 2px ${colors.GRAY_400}); + + background: ${colors.WHITE}; + + font-size: 4rem; + color: ${colors.GRAY_600}; +`; + +const saveButtonStyle = ({ colors }: Theme) => css` + padding: 2rem 3rem; + box-sizing: border-box; + border-radius: 8px; + filter: drop-shadow(0px 2px 2px ${colors.GRAY_400}); + + background: ${colors.YELLOW_500}; + + font-size: 4rem; + color: ${colors.WHITE}; +`; + +const labelStyle = ({ colors }: Theme) => css` + padding: 0 1rem; + + font-size: 4rem; + color: ${colors.GRAY_800}; +`; + +const categoryBoxStyle = ({ flex }: Theme) => css` + ${flex.column}; + + align-items: flex-start; + gap: 2.5rem; + + width: 100%; +`; + +export { + allDayButtonStyle, + arrowStyle, + cancelButtonStyle, + categoryStyle, + controlButtonsStyle, + dateTimeStyle, + formStyle, + labelStyle, + modalStyle, + saveButtonStyle, + categoryBoxStyle, +}; diff --git a/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx new file mode 100644 index 00000000..88448b37 --- /dev/null +++ b/frontend/src/components/ScheduleModifyModal/ScheduleModifyModal.tsx @@ -0,0 +1,196 @@ +import { validateLength } from '@/validation'; +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useValidateSchedule from '@/hooks/useValidateSchedule'; + +import { CategoryType } from '@/@types/category'; +import { ScheduleType } from '@/@types/schedule'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; + +import { CACHE_KEY } from '@/constants/api'; +import { DATE_TIME } from '@/constants/date'; +import { VALIDATION_MESSAGE, VALIDATION_SIZE } from '@/constants/validate'; + +import { checkAllDay, getISODateString } from '@/utils/date'; + +import categoryApi from '@/api/category'; +import scheduleApi from '@/api/schedule'; + +import { + allDayButtonStyle, + arrowStyle, + cancelButtonStyle, + categoryBoxStyle, + categoryStyle, + controlButtonsStyle, + dateTimeStyle, + formStyle, + labelStyle, + modalStyle, + saveButtonStyle, +} from './ScheduleModifyModal.styles'; + +interface ScheduleModifyModalProps { + scheduleInfo: ScheduleType; + closeModal: () => void; +} + +function ScheduleModifyModal({ scheduleInfo, closeModal }: ScheduleModifyModalProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const [isAllDay, setAllDay] = useState( + !!checkAllDay(scheduleInfo.startDateTime, scheduleInfo.endDateTime) + ); + + const queryClient = useQueryClient(); + + const { data } = useQuery, AxiosError>(CACHE_KEY.CATEGORY, () => + categoryApi.getSingle(scheduleInfo.categoryId) + ); + + const { mutate } = useMutation< + AxiosResponse, + AxiosError, + Omit, + unknown + >(CACHE_KEY.SCHEDULE, (body) => scheduleApi.patch(accessToken, scheduleInfo.id, body), { + onSuccess: () => onSuccessPatchSchedule(), + }); + + const onSuccessPatchSchedule = () => { + queryClient.invalidateQueries(CACHE_KEY.SCHEDULES); + 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 validationSchedule = useValidateSchedule({ + initialTitle: scheduleInfo.title, + initialStartDateTime: startDateFieldsetProps.defaultValue, + initialEndDateTime: endDateFieldsetProps.defaultValue, + initialMemo: scheduleInfo.memo, + }); + + const handleSubmitScheduleModifyForm = (e: React.FormEvent) => { + e.preventDefault(); + + const body = { + title: validationSchedule.title.inputValue, + startDateTime: validationSchedule.startDateTime.inputValue, + endDateTime: validationSchedule.endDateTime.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); + }; + + const handleClickAllDayButton = () => { + setAllDay((prev) => !prev); + }; + + return ( +
+
+
+
카테고리
+
{data?.data.name}
+
+
+ +
+
+

+
+
+
+
+ + +
+ +
+ ); +} + +export default ScheduleModifyModal; diff --git a/frontend/src/components/SideBar/SideBar.styles.ts b/frontend/src/components/SideBar/SideBar.styles.ts new file mode 100644 index 00000000..18c8371c --- /dev/null +++ b/frontend/src/components/SideBar/SideBar.styles.ts @@ -0,0 +1,19 @@ +import { css, Theme } from '@emotion/react'; + +const sideBar = ({ colors }: Theme, isSideBarOpen: boolean) => css` + overflow-y: overlay; + overflow-x: hidden; + position: fixed; + z-index: 10; + + width: ${isSideBarOpen ? '64rem' : '0'}; + height: calc(100vh - 16rem); + padding: ${isSideBarOpen ? '4rem' : '0'}; + border: 1px solid ${colors.GRAY_400}; + + background: ${colors.WHITE}; + + transition: width 0.3s; +`; + +export { sideBar }; diff --git a/frontend/src/components/SideBar/SideBar.tsx b/frontend/src/components/SideBar/SideBar.tsx new file mode 100644 index 00000000..5f90af8d --- /dev/null +++ b/frontend/src/components/SideBar/SideBar.tsx @@ -0,0 +1,26 @@ +import { useTheme } from '@emotion/react'; +import { useRecoilValue } from 'recoil'; + +import { sideBarState, userState } from '@/recoil/atoms'; + +import FilterCategoryList from '../FilterCategoryList/FilterCategoryList'; +import { sideBar } from './SideBar.styles'; + +function SideBar() { + const { accessToken } = useRecoilValue(userState); + const isSideBarOpen = useRecoilValue(sideBarState); + + const theme = useTheme(); + + if (!accessToken) { + return <>; + } + + return ( +
+ +
+ ); +} + +export default SideBar; diff --git a/frontend/src/components/SnackBar/SnackBar.styles.ts b/frontend/src/components/SnackBar/SnackBar.styles.ts new file mode 100644 index 00000000..7b38678c --- /dev/null +++ b/frontend/src/components/SnackBar/SnackBar.styles.ts @@ -0,0 +1,45 @@ +import { css, Theme } from '@emotion/react'; + +const snackBarStyle = ({ colors }: Theme, isOpen: boolean) => css` + ${isOpen && + css` + z-index: 30; + position: fixed; + bottom: 5rem; + left: 50%; + transform: translateX(-50%); + + padding: 4rem; + border-radius: 3px; + + background: ${colors.YELLOW_500}; + opacity: 0; + + color: ${colors.WHITE}; + font-size: 3.5rem; + line-height: 3.5rem; + text-align: center; + + @keyframes show { + 0% { + opacity: 0; + } + + 50% { + opacity: 1; + } + + 75% { + opacity: 1; + } + + 100% { + opacity: 0; + } + } + + animation: show 2.5s; + `} +`; + +export { snackBarStyle }; diff --git a/frontend/src/components/SnackBar/SnackBar.tsx b/frontend/src/components/SnackBar/SnackBar.tsx new file mode 100644 index 00000000..14096a8d --- /dev/null +++ b/frontend/src/components/SnackBar/SnackBar.tsx @@ -0,0 +1,33 @@ +import { useTheme } from '@emotion/react'; +import { useEffect, useState } from 'react'; +import { useRecoilValue } from 'recoil'; + +import { snackBarState } from '@/recoil/atoms'; + +import { snackBarStyle } from './SnackBar.styles'; + +function SnackBar() { + const theme = useTheme(); + + const snackBarInfo = useRecoilValue(snackBarState); + + const [timer, setTimer] = useState(null); + + useEffect(() => { + if (snackBarInfo.text === '' || timer) { + return; + } + + const newTimer = setTimeout(() => { + setTimer(null); + }, 4000); + + setTimer(newTimer); + }, [snackBarInfo]); + + const isOpen = timer !== null; + + return
{snackBarInfo.text}
; +} + +export default SnackBar; diff --git a/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.styles.ts b/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.styles.ts new file mode 100644 index 00000000..780a9f59 --- /dev/null +++ b/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.styles.ts @@ -0,0 +1,59 @@ +import { css, Theme } from '@emotion/react'; + +const categoryItem = ({ colors, flex }: Theme) => css` + ${flex.row} + + justify-content:space-around; + + height: 20rem; + border-bottom: 1px solid ${colors.GRAY_400}; + + font-size: 4rem; +`; + +const item = css` + flex: 1 1 0; + text-align: center; +`; + +const unsubscribeButton = ({ colors }: Theme) => css` + position: relative; + + width: 15rem; + height: 8rem; + border-radius: 3px; + + background-color: ${colors.GRAY_500}; + + font-size: 3.5rem; + font-weight: 700; + line-height: 3.5rem; + color: ${colors.WHITE}; + + &:hover { + 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; +`; + +export { categoryItem, item, menuTitle, unsubscribeButton }; diff --git a/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.tsx b/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.tsx new file mode 100644 index 00000000..1b678bab --- /dev/null +++ b/frontend/src/components/SubscribedCategoryItem/SubscribedCategoryItem.tsx @@ -0,0 +1,75 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQuery, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import { CategoryType } from '@/@types/category'; +import { ProfileType } from '@/@types/profile'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; + +import { CACHE_KEY } from '@/constants/api'; +import { CONFIRM_MESSAGE, TOOLTIP_MESSAGE } from '@/constants/message'; + +import { getISODateString } from '@/utils/date'; + +import profileApi from '@/api/profile'; +import subscriptionApi from '@/api/subscription'; + +import { categoryItem, item, menuTitle, unsubscribeButton } from './SubscribedCategoryItem.styles'; + +interface SubscribedCategoryItemProps { + category: CategoryType; + subscriptionId: number; +} + +function SubscribedCategoryItem({ category, subscriptionId }: SubscribedCategoryItemProps) { + const { accessToken } = useRecoilValue(userState); + const theme = useTheme(); + + const queryClient = useQueryClient(); + + const { data } = useQuery, AxiosError>(CACHE_KEY.PROFILE, () => + profileApi.get(accessToken) + ); + + const { mutate } = useMutation(() => subscriptionApi.delete(accessToken, subscriptionId), { + onSuccess: () => { + queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); + }, + }); + + const handleClickUnsubscribeButton = () => { + if (window.confirm(CONFIRM_MESSAGE.UNSUBSCRIBE)) { + mutate(); + } + }; + + const canUnsubscribeCategory = category.creator.id !== data?.data.id; + + return ( +
+ {getISODateString(category.createdAt)} + {category.name} + {category.creator.displayName} +
+ +
+
+ ); +} + +export default SubscribedCategoryItem; diff --git a/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.styles.ts b/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.styles.ts new file mode 100644 index 00000000..3a4f8d13 --- /dev/null +++ b/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.styles.ts @@ -0,0 +1,32 @@ +import { css, Theme } from '@emotion/react'; + +const categoryItem = ({ colors, flex }: Theme) => css` + ${flex.row} + + justify-content:space-around; + + height: 20rem; + border-bottom: 1px solid ${colors.GRAY_400}; + + font-size: 4rem; +`; + +const item = css` + flex: 1 1 0; + text-align: center; +`; + +const subscribeButton = ({ colors }: Theme) => css` + width: 15rem; + height: 8rem; + border-radius: 3px; + + background-color: ${colors.YELLOW_500}; + + font-size: 3.5rem; + font-weight: 700; + line-height: 3.5rem; + color: ${colors.WHITE}; +`; + +export { categoryItem, item, subscribeButton }; diff --git a/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.tsx b/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.tsx new file mode 100644 index 00000000..4ea5bff1 --- /dev/null +++ b/frontend/src/components/UnsubscribedCategoryItem/UnsubscribedCategoryItem.tsx @@ -0,0 +1,66 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useMutation, useQueryClient } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import { CategoryType } from '@/@types/category'; +import { SubscriptionType } from '@/@types/subscription'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; + +import { CACHE_KEY } from '@/constants/api'; +import { PALETTE } from '@/constants/style'; + +import { getRandomNumber } from '@/utils'; +import { getISODateString } from '@/utils/date'; + +import subscriptionApi from '@/api/subscription'; + +import { categoryItem, item, subscribeButton } from './UnsubscribedCategoryItem.styles'; + +interface UnsubscribedCategoryItemProps { + category: CategoryType; +} + +function UnsubscribedCategoryItem({ category }: UnsubscribedCategoryItemProps) { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const body = { + colorCode: PALETTE[getRandomNumber(0, PALETTE.length)], + }; + + const queryClient = useQueryClient(); + const { mutate } = useMutation< + AxiosResponse>, + AxiosError, + Pick, + unknown + >(() => subscriptionApi.post(accessToken, category.id, body), { + onSuccess: () => { + queryClient.invalidateQueries(CACHE_KEY.SUBSCRIPTIONS); + }, + }); + + const handleClickSubscribeButton = () => { + mutate(body); + }; + + return ( +
+ {getISODateString(category.createdAt)} + {category.name} + {category.creator.displayName} +
+ +
+
+ ); +} + +export default UnsubscribedCategoryItem; diff --git a/frontend/src/constants/api.ts b/frontend/src/constants/api.ts new file mode 100644 index 00000000..08de8588 --- /dev/null +++ b/frontend/src/constants/api.ts @@ -0,0 +1,29 @@ +const API = { + AUTH_CODE_KEY: 'code', + CATEGORY_GET_SIZE: 4, +}; + +const API_URL = process.env.API_URL; + +const CACHE_KEY = { + AUTH: 'auth', + CATEGORIES: 'categories', + CATEGORY: 'category', + ENTER: 'enter', + GOOGLE_CALENDAR: 'googleCalendar', + MY_CATEGORIES: 'myCategories', + PROFILE: 'profile', + SCHEDULE: 'schedule', + SCHEDULER: 'scheduler', + SCHEDULES: 'schedules', + SUBSCRIPTIONS: 'subscriptions', + VALIDATE: 'validate', +}; + +const RESPONSE = { + STATUS: { + UNAUTHORIZED: 401, + }, +}; + +export { API, API_URL, CACHE_KEY, RESPONSE }; diff --git a/frontend/src/constants/category.ts b/frontend/src/constants/category.ts new file mode 100644 index 00000000..b82002b4 --- /dev/null +++ b/frontend/src/constants/category.ts @@ -0,0 +1,7 @@ +const CATEGORY_TYPE = { + GOOGLE: 'GOOGLE', + NORMAL: 'NORMAL', + PERSONAL: 'PERSONAL', +}; + +export { CATEGORY_TYPE }; diff --git a/frontend/src/constants/date.ts b/frontend/src/constants/date.ts new file mode 100644 index 00000000..877d4d00 --- /dev/null +++ b/frontend/src/constants/date.ts @@ -0,0 +1,8 @@ +const DATE_TIME = { + START: '00:00', + END: '23:59', +}; + +const DAYS = ['일', '월', '화', '수', '목', '금', '토']; + +export { DATE_TIME, DAYS }; diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts new file mode 100644 index 00000000..f95f97c4 --- /dev/null +++ b/frontend/src/constants/index.ts @@ -0,0 +1,28 @@ +const ATOM_KEY = { + SNACK_BAR: 'snackBarState', + SIDE_BAR: 'sideBarState', + USER: 'userState', +}; + +const CALENDAR = { + MAX_VIEW: 10, + EMPTY_TITLE: '제목 없음', +}; + +const SELECTOR_KEY = { + SIDE_BAR: 'sideBarSelector', +}; + +const STORAGE_KEY = { + ACCESS_TOKEN: 'accessToken', +}; + +const PATH = { + MAIN: '/', + AUTH: '/oauth', + CATEGORY: '/category', + POLICY: '/policy', + SCHEDULING: '/scheduling', +}; + +export { ATOM_KEY, CALENDAR, SELECTOR_KEY, STORAGE_KEY, PATH }; diff --git a/frontend/src/constants/message.ts b/frontend/src/constants/message.ts new file mode 100644 index 00000000..e4c4bbfb --- /dev/null +++ b/frontend/src/constants/message.ts @@ -0,0 +1,16 @@ +const CONFIRM_MESSAGE = { + DELETE: '정말 삭제하시겠습니까?', + UNSUBSCRIBE: '구독을 해제하시겠습니까?', + LOGOUT: '로그아웃하시겠습니까?', +}; + +const ERROR_MESSAGE = { + DEFAULT: '에러가 발생했습니다. 잠시 후에 다시 시도해주세요.', +}; + +const TOOLTIP_MESSAGE = { + CANNOT_UNSUBSCRIBE_MINE: '나의 카테고리는 구독 취소할 수 없습니다.', + CANNOT_EDIT_DELETE_DEFAULT_CATEGORY: '기본 카테고리는 수정/삭제가 불가능합니다.', +}; + +export { CONFIRM_MESSAGE, ERROR_MESSAGE, TOOLTIP_MESSAGE }; diff --git a/frontend/src/constants/style.ts b/frontend/src/constants/style.ts new file mode 100644 index 00000000..4cce3499 --- /dev/null +++ b/frontend/src/constants/style.ts @@ -0,0 +1,30 @@ +const PALETTE = [ + '#AD1457', + '#D81B60', + '#D50000', + '#E67C73', + '#F4511E', + '#EF6C00', + '#F09300', + '#F6BF26', + '#E4C441', + '#C0CA33', + '#7CB342', + '#33B679', + '#0B8043', + '#009688', + '#039BE5', + '#4285F4', + '#3F51B5', + '#7986CB', + '#B39DDB', + '#9E69AF', + '#8E24AA', + '#795548', + '#616161', + '#A79B8E', +]; + +const TRANSPARENT = 'transparent'; + +export { PALETTE, TRANSPARENT }; diff --git a/frontend/src/constants/validate.ts b/frontend/src/constants/validate.ts new file mode 100644 index 00000000..3a2c98f4 --- /dev/null +++ b/frontend/src/constants/validate.ts @@ -0,0 +1,17 @@ +const VALIDATION_SIZE = { + MIN_LENGTH: 1, + SCHEDULE_MEMO_MAX_LENGTH: 255, + SCHEDULE_TITLE_MAX_LENGTH: 50, + CATEGORY_NAME_MAX_LENGTH: 20, +}; + +const VALIDATION_STRING = { + CATEGORY: '내 일정', +}; + +const VALIDATION_MESSAGE = { + STRING_LENGTH: (min: number, max: number) => `${min}자 ~ ${max}자로 입력해주세요.`, + INVALID_CATEGORY_NAME: `"${VALIDATION_STRING.CATEGORY}"을 카테고리 이름으로 지정할 수 없습니다.`, +}; + +export { VALIDATION_MESSAGE, VALIDATION_STRING, VALIDATION_SIZE }; diff --git a/frontend/src/hooks/useCalendar.ts b/frontend/src/hooks/useCalendar.ts new file mode 100644 index 00000000..b8273f79 --- /dev/null +++ b/frontend/src/hooks/useCalendar.ts @@ -0,0 +1,67 @@ +import { useState } from 'react'; + +import { + getBeforeYearMonth, + getCalendarMonth, + getFormattedDate, + getNextYearMonth, + getThisMonth, + getThisYear, +} from '@/utils/date'; + +function useCalendar() { + const [current, setCurrent] = useState({ + year: getThisYear(), + month: getThisMonth(), + }); + + const [calendarMonth, setCalendarMonth] = useState( + getCalendarMonth(getThisYear(), getThisMonth()) + ); + + const moveToBeforeMonth = () => { + const { year, month } = getBeforeYearMonth(current.year, current.month); + + setCurrent({ year, month }); + setCalendarMonth(getCalendarMonth(year, month)); + }; + + const moveToToday = () => { + const year = getThisYear(); + const month = getThisMonth(); + + setCurrent({ year, month }); + setCalendarMonth(getCalendarMonth(year, month)); + }; + + const moveToNextMonth = () => { + const { year, month } = getNextYearMonth(current.year, current.month); + + setCurrent({ year, month }); + setCalendarMonth(getCalendarMonth(year, month)); + }; + + const startDate = getFormattedDate( + calendarMonth[0].year, + calendarMonth[0].month, + calendarMonth[0].date + ); + + const endDate = getFormattedDate( + calendarMonth[calendarMonth.length - 1].year, + calendarMonth[calendarMonth.length - 1].month, + calendarMonth[calendarMonth.length - 1].date + ); + + return { + calendarMonth, + current, + endDate, + moveToBeforeMonth, + moveToNextMonth, + moveToToday, + startDate, + }; +} + +export default useCalendar; diff --git a/frontend/src/hooks/useControlledInput.ts b/frontend/src/hooks/useControlledInput.ts new file mode 100644 index 00000000..96f763aa --- /dev/null +++ b/frontend/src/hooks/useControlledInput.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +function useControlledInput(initialInputValue?: string) { + const [inputValue, setInputValue] = useState(initialInputValue ?? ''); + + const onChangeValue = ({ + target, + }: React.ChangeEvent | React.ChangeEvent) => { + if (target instanceof HTMLInputElement || target instanceof HTMLSelectElement) { + setInputValue(target.value); + } + }; + + useEffect(() => { + setInputValue(initialInputValue ?? ''); + }, [initialInputValue]); + + return { inputValue, setInputValue, onChangeValue }; +} + +export default useControlledInput; diff --git a/frontend/src/hooks/useIntersect.ts b/frontend/src/hooks/useIntersect.ts new file mode 100644 index 00000000..9458cf94 --- /dev/null +++ b/frontend/src/hooks/useIntersect.ts @@ -0,0 +1,35 @@ +import { useCallback, useEffect, useRef } from 'react'; + +type IntersectHandler = (entry: IntersectionObserverEntry, observer: IntersectionObserver) => void; + +function useIntersect(onIntersect: IntersectHandler, options?: IntersectionObserverInit) { + const ref = useRef(null); + const callback = useCallback( + (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + onIntersect(entry, observer); + } + }); + }, + [onIntersect] + ); + + useEffect(() => { + if (!ref.current) { + return; + } + + const observer = new IntersectionObserver(callback, options); + + observer.observe(ref.current); + + return () => { + observer.disconnect(); + }; + }, [ref, options, callback]); + + return ref; +} + +export default useIntersect; diff --git a/frontend/src/hooks/useSchedulePriority.ts b/frontend/src/hooks/useSchedulePriority.ts new file mode 100644 index 00000000..3e73d101 --- /dev/null +++ b/frontend/src/hooks/useSchedulePriority.ts @@ -0,0 +1,121 @@ +import { ScheduleType } from '@/@types/schedule'; + +import { CALENDAR } from '@/constants'; + +import { getFormattedDate, getISODateString } from '@/utils/date'; + +interface DateType { + year: number; + month: number; + date: number; + day: number; +} + +function useSchedulePriority(calendarMonth: DateType[]) { + const calendarInfoWithPriority = calendarMonth.reduce( + ( + acc: { + [key: string]: Array; + }, + cur: DateType + ) => { + const { year, month, date } = cur; + + acc[getFormattedDate(year, month, date)] = new Array(CALENDAR.MAX_VIEW).fill(false); + + return acc; + }, + {} + ); + + const getLongTermsPriority = (longTerms: Array) => + longTerms.map((el) => { + const startDate = getISODateString(el.startDateTime); + const endDate = getISODateString(el.endDateTime); + + const scheduleRange = calendarMonth + .filter((el) => { + const date = getFormattedDate(el.year, el.month, el.date); + + return startDate <= date && date <= endDate; + }) + .map((el) => getFormattedDate(el.year, el.month, el.date)); + + if (!calendarInfoWithPriority.hasOwnProperty(scheduleRange[0])) { + return { + schedule: el, + priority: CALENDAR.MAX_VIEW + 1, + }; + } + + const priorityPosition = calendarInfoWithPriority[scheduleRange[0]].findIndex( + (el) => el === false + ); + + if (priorityPosition === -1) { + return { + schedule: el, + priority: CALENDAR.MAX_VIEW + 1, + }; + } + + scheduleRange.forEach((el) => { + if (calendarInfoWithPriority.hasOwnProperty(el)) { + calendarInfoWithPriority[el][priorityPosition] = true; + } + }); + + return { + schedule: el, + priority: priorityPosition + 1, + }; + }); + + const getAllDaysPriority = (allDays: Array) => + allDays.map((el) => { + const startDate = getISODateString(el.startDateTime); + const priorityPosition = calendarInfoWithPriority[startDate].findIndex((el) => el === false); + + if (priorityPosition === -1) { + return { + schedule: el, + priority: CALENDAR.MAX_VIEW + 1, + }; + } + + calendarInfoWithPriority[startDate][priorityPosition] = true; + + return { + schedule: el, + priority: priorityPosition + 1, + }; + }); + + const getFewHoursPriority = (fewHours: Array) => + fewHours.map((el) => { + const startDate = getISODateString(el.startDateTime); + const priorityPosition = calendarInfoWithPriority[startDate].findIndex((el) => el === false); + + if (priorityPosition === -1) { + return { + schedule: el, + priority: CALENDAR.MAX_VIEW + 1, + }; + } + + calendarInfoWithPriority[startDate][priorityPosition] = true; + + return { + schedule: el, + priority: priorityPosition + 1, + }; + }); + + return { + getLongTermsPriority, + getAllDaysPriority, + getFewHoursPriority, + }; +} + +export default useSchedulePriority; diff --git a/frontend/src/hooks/useSnackBar.ts b/frontend/src/hooks/useSnackBar.ts new file mode 100644 index 00000000..57de3351 --- /dev/null +++ b/frontend/src/hooks/useSnackBar.ts @@ -0,0 +1,15 @@ +import { useSetRecoilState } from 'recoil'; + +import { snackBarState } from '@/recoil/atoms'; + +function useSnackBar() { + const setText = useSetRecoilState(snackBarState); + + const openSnackBar = (text: string) => { + setText({ text }); + }; + + return { openSnackBar }; +} + +export default useSnackBar; diff --git a/frontend/src/hooks/useToggle.ts b/frontend/src/hooks/useToggle.ts new file mode 100644 index 00000000..535ed11f --- /dev/null +++ b/frontend/src/hooks/useToggle.ts @@ -0,0 +1,13 @@ +import { useState } from 'react'; + +function useToggle(initialState = false) { + const [state, setState] = useState(initialState); + + const toggleState = () => { + setState((prev) => !prev); + }; + + return { state, toggleState }; +} + +export default useToggle; diff --git a/frontend/src/hooks/useUserValue.ts b/frontend/src/hooks/useUserValue.ts new file mode 100644 index 00000000..d6345438 --- /dev/null +++ b/frontend/src/hooks/useUserValue.ts @@ -0,0 +1,38 @@ +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery } from 'react-query'; +import { useRecoilValue, useResetRecoilState, useSetRecoilState } from 'recoil'; + +import { sideBarState, userState } from '@/recoil/atoms'; + +import { CACHE_KEY } from '@/constants/api'; + +import { removeAccessToken } from '@/utils/storage'; + +import loginApi from '@/api/login'; + +function useUserValue() { + const user = useRecoilValue(userState); + const resetUser = useResetRecoilState(userState); + + const setSideBarOpen = useSetRecoilState(sideBarState); + + const { isLoading } = useQuery( + CACHE_KEY.VALIDATE, + () => loginApi.validate(user.accessToken), + { + onError: () => onErrorValidate(), + retry: false, + useErrorBoundary: false, + } + ); + + const onErrorValidate = () => { + setSideBarOpen(false); + removeAccessToken(); + resetUser(); + }; + + return { isAuthenticating: isLoading, user }; +} + +export default useUserValue; diff --git a/frontend/src/hooks/useValidateCategory.ts b/frontend/src/hooks/useValidateCategory.ts new file mode 100644 index 00000000..37cd1d4d --- /dev/null +++ b/frontend/src/hooks/useValidateCategory.ts @@ -0,0 +1,41 @@ +import { validateLength, validateNotEqualString } from '@/validation'; + +import { VALIDATION_MESSAGE, VALIDATION_SIZE, VALIDATION_STRING } from '@/constants/validate'; + +import useControlledInput from './useControlledInput'; + +function useValidateCategory(initialCategory?: string) { + const categoryValue = useControlledInput(initialCategory); + + const getCategoryErrorMessage = () => { + if ( + !validateLength( + categoryValue.inputValue, + VALIDATION_SIZE.MIN_LENGTH, + VALIDATION_SIZE.CATEGORY_NAME_MAX_LENGTH + ) + ) { + return VALIDATION_MESSAGE.STRING_LENGTH( + VALIDATION_SIZE.MIN_LENGTH, + VALIDATION_SIZE.CATEGORY_NAME_MAX_LENGTH + ); + } + + if (!validateNotEqualString(categoryValue.inputValue, VALIDATION_STRING.CATEGORY)) { + return VALIDATION_MESSAGE.INVALID_CATEGORY_NAME; + } + + return undefined; + }; + + const isValidCategory = + validateLength( + categoryValue.inputValue, + VALIDATION_SIZE.MIN_LENGTH, + VALIDATION_SIZE.CATEGORY_NAME_MAX_LENGTH + ) && validateNotEqualString(categoryValue.inputValue, VALIDATION_STRING.CATEGORY); + + return { categoryValue, getCategoryErrorMessage, isValidCategory }; +} + +export default useValidateCategory; diff --git a/frontend/src/hooks/useValidateSchedule.ts b/frontend/src/hooks/useValidateSchedule.ts new file mode 100644 index 00000000..998c78f3 --- /dev/null +++ b/frontend/src/hooks/useValidateSchedule.ts @@ -0,0 +1,84 @@ +import { validateLength, validateNotEmpty, validateStartEndDateTime } from '@/validation'; +import { useEffect } from 'react'; + +import { VALIDATION_SIZE } from '@/constants/validate'; + +import { getOneHourEarlierISOString, getOneHourLaterISOString } from '@/utils/date'; + +import useControlledInput from './useControlledInput'; + +interface useValidateScheduleParametersType { + initialTitle?: string; + initialStartDateTime?: string; + initialEndDateTime?: string; + initialMemo?: string; +} + +function useValidateSchedule({ + initialTitle, + initialStartDateTime, + initialEndDateTime, + initialMemo, +}: useValidateScheduleParametersType) { + const title = useControlledInput(initialTitle); + const startDateTime = useControlledInput(initialStartDateTime); + const endDateTime = useControlledInput(initialEndDateTime); + const memo = useControlledInput(initialMemo); + + useEffect(() => { + const resetEndDateTimeValue = () => { + if (startDateTime.inputValue <= endDateTime.inputValue) { + return; + } + + if (!startDateTime.inputValue.includes('T')) { + endDateTime.setInputValue(startDateTime.inputValue); + + return; + } + + endDateTime.setInputValue(getOneHourLaterISOString(startDateTime.inputValue)); + }; + + resetEndDateTimeValue(); + }, [startDateTime.inputValue]); + + useEffect(() => { + const resetStartDateTimeValue = () => { + if (endDateTime.inputValue >= startDateTime.inputValue) { + return; + } + + if (!endDateTime.inputValue.includes('T')) { + startDateTime.setInputValue(endDateTime.inputValue); + + return; + } + + startDateTime.setInputValue(getOneHourEarlierISOString(endDateTime.inputValue)); + }; + + resetStartDateTimeValue(); + }, [endDateTime.inputValue]); + + const isValidSchedule = + validateLength( + title.inputValue, + VALIDATION_SIZE.MIN_LENGTH, + VALIDATION_SIZE.SCHEDULE_TITLE_MAX_LENGTH + ) && + validateStartEndDateTime(startDateTime.inputValue, endDateTime.inputValue) && + validateLength(memo.inputValue, 0, VALIDATION_SIZE.SCHEDULE_MEMO_MAX_LENGTH) && + validateNotEmpty(startDateTime.inputValue) && + validateNotEmpty(endDateTime.inputValue); + + return { + title, + startDateTime, + endDateTime, + memo, + isValidSchedule, + }; +} + +export default useValidateSchedule; diff --git a/frontend/src/index.html b/frontend/src/index.html new file mode 100644 index 00000000..43007932 --- /dev/null +++ b/frontend/src/index.html @@ -0,0 +1,12 @@ + + + + + + 달록 + + +
+ + + diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx new file mode 100644 index 00000000..e5c38264 --- /dev/null +++ b/frontend/src/index.tsx @@ -0,0 +1,27 @@ +import { ThemeProvider } from '@emotion/react'; +import { createRoot } from 'react-dom/client'; +import { RecoilRoot } from 'recoil'; + +import GlobalStyle from '@/styles/GlobalStyle'; +import theme from '@/styles/theme'; + +import { worker } from '@/mocks/browser'; + +import App from './App'; + +if (process.env.NODE_ENV === 'development') { + worker.start(); +} + +const root = document.getElementById('root') as HTMLElement; + +const rootElement = createRoot(root); + +rootElement.render( + + + + + + +); diff --git a/frontend/src/mocks/browser.ts b/frontend/src/mocks/browser.ts new file mode 100644 index 00000000..9c10cad9 --- /dev/null +++ b/frontend/src/mocks/browser.ts @@ -0,0 +1,5 @@ +import { setupWorker } from 'msw'; + +import { handlers } from './handlers'; + +export const worker = setupWorker(...handlers); diff --git a/frontend/src/mocks/data.ts b/frontend/src/mocks/data.ts new file mode 100644 index 00000000..b6d36a0e --- /dev/null +++ b/frontend/src/mocks/data.ts @@ -0,0 +1,445 @@ +import { ScheduleType } from '@/@types/schedule'; + +const matProfileDB = { + id: 1, + email: 'example@email.com', + displayName: '매트', + profileImageUrl: 'https://img.insight.co.kr/static/2020/05/04/700/51wlu5y2281iku1o0hms.jpg', + socialType: 'GOOGLE', +}; + +const tigerProfileDB = { + id: 2, + email: 'tiger@dallog.com', + displayName: '티거', + profileImageUrl: + 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRBo4PPBaz3xqgKPEs8W2djKBXYb2D8PFDkZg&usqp=CAU', + socialType: 'GOOGLE', +}; + +const categoryDB = { + categories: [ + { + id: 1, + name: 'BE 공식 일정', + createdAt: '2022-07-04T13:00:00', + creator: matProfileDB, + }, + { + id: 2, + name: '알록달록', + createdAt: '2022-07-08T15:00:00', + creator: matProfileDB, + }, + { + id: 3, + name: '레벨 1 공원조', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + { + id: 4, + name: '자바 스터디', + createdAt: '2022-07-03T15:00:00', + creator: matProfileDB, + }, + { + id: 5, + name: '코틀린 스터디', + createdAt: '2022-07-02T13:00:00', + creator: matProfileDB, + }, + { + id: 6, + name: '지원 플랫폼 근로', + createdAt: '2022-07-01T15:00:00', + creator: matProfileDB, + }, + { + id: 7, + name: '레벨 2 준조', + createdAt: '2022-07-08T15:00:00', + creator: tigerProfileDB, + }, + { + id: 8, + name: '레벨 1 네오조', + createdAt: '2022-07-12T15:00:00', + creator: matProfileDB, + }, + { + id: 9, + name: '매트의 아고라', + createdAt: '2022-07-13T13:00:00', + creator: matProfileDB, + }, + { + id: 10, + name: '자바스크립트 스터디', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + { + id: 11, + name: '리액트 스터디', + createdAt: '2022-07-03T15:00:00', + creator: tigerProfileDB, + }, + { + id: 12, + name: 'FE 공식 일정', + createdAt: '2022-07-16T15:00:00', + creator: tigerProfileDB, + }, + { + id: 13, + name: '레벨 2 브리조', + createdAt: '2022-07-18T13:00:00', + creator: matProfileDB, + }, + { + id: 14, + name: '세미나', + createdAt: '2022-07-20T15:00:00', + creator: matProfileDB, + }, + { + id: 15, + name: '네오 면담', + createdAt: '2022-07-01T15:00:00', + creator: tigerProfileDB, + }, + { + id: 16, + name: '공원 면담', + createdAt: '2022-07-11T13:00:00', + creator: tigerProfileDB, + }, + { + id: 17, + name: '포코 면담', + createdAt: '2022-07-12T15:00:00', + creator: tigerProfileDB, + }, + { + id: 18, + name: '포비의 수다 타임', + createdAt: '2022-07-13T13:00:00', + creator: matProfileDB, + }, + { + id: 19, + name: '튼튼이 클럽', + createdAt: '2022-07-13T13:00:00', + creator: tigerProfileDB, + }, + { + id: 20, + name: '한강팟', + createdAt: '2022-07-14T15:00:00', + creator: tigerProfileDB, + }, + ], +}; + +const myCategoryDB = { + categories: [ + { + id: 3, + name: '레벨 1 공원조', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + { + id: 7, + name: '레벨 2 준조', + createdAt: '2022-07-08T15:00:00', + creator: tigerProfileDB, + }, + { + id: 10, + name: '자바스크립트 스터디', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + { + id: 11, + name: '리액트 스터디', + createdAt: '2022-07-03T15:00:00', + creator: tigerProfileDB, + }, + { + id: 12, + name: 'FE 공식 일정', + createdAt: '2022-07-16T15:00:00', + creator: tigerProfileDB, + }, + { + id: 15, + name: '네오 면담', + createdAt: '2022-07-01T15:00:00', + creator: tigerProfileDB, + }, + { + id: 16, + name: '공원 면담', + createdAt: '2022-07-11T13:00:00', + creator: tigerProfileDB, + }, + { + id: 17, + name: '포코 면담', + createdAt: '2022-07-12T15:00:00', + creator: tigerProfileDB, + }, + { + id: 19, + name: '튼튼이 클럽', + createdAt: '2022-07-13T13:00:00', + creator: tigerProfileDB, + }, + { + id: 20, + name: '한강팟', + createdAt: '2022-07-14T15:00:00', + creator: tigerProfileDB, + }, + ], +}; + +const scheduleDB = [ + { + id: '1', + title: '공원조 회식', + startDateTime: '2022-07-07T18:00', + endDateTime: '2022-07-07T22:00', + memo: '잠실역', + categoryId: 3, + }, + { + id: '2', + title: 'Typescript 강의', + startDateTime: '2022-07-09T10:00', + endDateTime: '2022-07-10T12:30', + memo: '포코', + categoryId: 12, + }, + { + id: '3', + title: '어깨', + startDateTime: '2022-07-17T00:00', + endDateTime: '2022-07-17T23:59', + memo: '', + categoryId: 19, + }, + { + id: '4', + title: '준조 MT', + startDateTime: '2022-07-17T17:00', + endDateTime: '2022-07-21T07:00', + memo: '', + categoryId: 7, + }, + { + id: '5', + title: '제주 여행', + startDateTime: '2022-07-04T13:00', + endDateTime: '2022-07-06T07:00', + memo: '✈️', + categoryId: 20, + }, + { + id: '6', + title: '공원조 MT', + startDateTime: '2022-07-23T13:00', + endDateTime: '2022-07-23T15:00', + memo: '리액트 MT', + categoryId: 13, + }, +]; + +const getScheduleDB: { [key: string]: ScheduleType[] } = { + longTerms: [ + { + id: '1', + title: '공원조 회식', + startDateTime: '2022-08-04T13:00', + endDateTime: '2022-08-06T07:00', + memo: '잠실역', + categoryId: 3, + colorCode: '#F37970', + categoryType: 'NORMAL', + }, + { + id: '2', + title: '준조 MT', + startDateTime: '2022-08-17T13:00', + endDateTime: '2022-08-21T07:00', + memo: '', + categoryId: 7, + colorCode: '#FEDA15', + categoryType: 'NORMAL', + }, + { + id: '3', + title: '제주 여행', + startDateTime: '2022-08-05T13:00', + endDateTime: '2022-08-22T07:00', + memo: '✈️', + categoryId: 20, + colorCode: '#0C2D48', + categoryType: 'NORMAL', + }, + ], + + allDays: [ + { + id: '4', + title: '어깨', + startDateTime: '2022-08-17T00:00', + endDateTime: '2022-08-17T23:59', + memo: '', + categoryId: 19, + colorCode: '#695E93', + categoryType: 'NORMAL', + }, + ], + + fewHours: [ + { + id: '5', + title: '모의 면접', + startDateTime: '2022-08-23T13:00', + endDateTime: '2022-08-23T15:00', + memo: '실행 컨텍스트', + categoryId: 10, + colorCode: '#81B622', + categoryType: 'NORMAL', + }, + ], +}; + +const subscriptionDB = { + subscriptions: [ + { + id: 1, + category: { + id: 3, + name: '레벨 1 공원조', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + colorCode: '#F37970', + checked: true, + }, + { + id: 2, + category: { + id: 7, + name: '레벨 2 준조', + createdAt: '2022-07-08T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#FEDA15', + checked: true, + }, + { + id: 3, + category: { + id: 10, + name: '자바스크립트 스터디', + createdAt: '2022-07-05T13:00:00', + creator: tigerProfileDB, + }, + colorCode: '#81B622', + checked: true, + }, + { + id: 4, + category: { + id: 11, + name: '리액트 스터디', + createdAt: '2022-07-03T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#145DA0', + checked: true, + }, + { + id: 5, + category: { + id: 12, + name: 'FE 공식 일정', + createdAt: '2022-07-16T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#464033', + checked: true, + }, + { + id: 6, + category: { + id: 15, + name: '네오 면담', + createdAt: '2022-07-01T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#7E7C73', + checked: false, + }, + { + id: 7, + category: { + id: 16, + name: '공원 면담', + createdAt: '2022-07-11T13:00:00', + creator: tigerProfileDB, + }, + colorCode: '#7E7C73', + checked: false, + }, + { + id: 8, + category: { + id: 17, + name: '포코 면담', + createdAt: '2022-07-12T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#7E7C73', + checked: false, + }, + { + id: 9, + category: { + id: 19, + name: '튼튼이 클럽', + createdAt: '2022-07-13T13:00:00', + creator: tigerProfileDB, + }, + colorCode: '#695E93', + checked: true, + }, + { + id: 10, + category: { + id: 20, + name: '한강팟', + createdAt: '2022-07-14T15:00:00', + creator: tigerProfileDB, + }, + colorCode: '#0C2D48', + checked: true, + }, + ], +}; + +export { + categoryDB, + getScheduleDB, + matProfileDB, + myCategoryDB, + scheduleDB, + subscriptionDB, + tigerProfileDB, +}; diff --git a/frontend/src/mocks/handlers.ts b/frontend/src/mocks/handlers.ts new file mode 100644 index 00000000..957668c4 --- /dev/null +++ b/frontend/src/mocks/handlers.ts @@ -0,0 +1,263 @@ +import { rest } from 'msw'; + +import { CategoryType } from '@/@types/category'; +import { ProfileType } from '@/@types/profile'; +import { ScheduleType } from '@/@types/schedule'; +import { SubscriptionType } from '@/@types/subscription'; + +import { API, API_URL } from '@/constants/api'; +import { CATEGORY_TYPE } from '@/constants/category'; + +import categoryApi from '@/api/category'; +import profileApi from '@/api/profile'; +import scheduleApi from '@/api/schedule'; +import subscriptionApi from '@/api/subscription'; + +import { + categoryDB, + getScheduleDB, + myCategoryDB, + scheduleDB, + subscriptionDB, + tigerProfileDB, +} from './data'; + +const handlers = [ + rest.get(API_URL + categoryApi.endpoint.entire, (req, res, ctx) => { + const page = parseInt(req.url.searchParams.get('page') as string); + const size = API.CATEGORY_GET_SIZE; + const name = req.url.searchParams.get('name'); + + if (name === null || name === '') { + const entireCategories = categoryDB.categories.slice(page * size, page * size + size); + + return res(ctx.status(200), ctx.json({ page, categories: entireCategories })); + } + + const filteredCategories = categoryDB.categories + .filter((category) => category.name.includes(name)) + .slice(page * size, page * size + size); + + return res(ctx.status(200), ctx.json({ page, categories: filteredCategories })); + }), + + rest.post>(API_URL + categoryApi.endpoint.entire, (req, res, ctx) => { + const newCategory: CategoryType = { + ...req.body, + id: categoryDB.categories.length + 1, + createdAt: new Date().toISOString().slice(0, -5), + creator: tigerProfileDB, + categoryType: CATEGORY_TYPE.NORMAL, + }; + + categoryDB.categories.push(newCategory); + myCategoryDB.categories.push(newCategory); + + return res(ctx.status(201), ctx.json(newCategory)); + }), + + rest.patch>( + `${API_URL}${categoryApi.endpoint.entire}/:id`, + (req, res, ctx) => { + const { id } = req.params; + const categoryId = parseInt(id as string); + const categoryIndex = categoryDB.categories.findIndex((el) => el.id === categoryId); + const myCategoryIndex = myCategoryDB.categories.findIndex((el) => el.id === categoryId); + const subscriptionIndex = subscriptionDB.subscriptions.findIndex( + (el) => el.category.id === categoryId + ); + + if (categoryIndex < 0 || myCategoryIndex < 0 || subscriptionIndex < 0) { + return res(ctx.status(404)); + } + + categoryDB.categories[categoryIndex] = { + ...categoryDB.categories[categoryIndex], + ...req.body, + }; + myCategoryDB.categories[myCategoryIndex] = { + ...myCategoryDB.categories[myCategoryIndex], + ...req.body, + }; + subscriptionDB.subscriptions[subscriptionIndex].category = { + ...subscriptionDB.subscriptions[subscriptionIndex].category, + ...req.body, + }; + + return res(ctx.status(201)); + } + ), + + rest.delete>( + `${API_URL}${categoryApi.endpoint.entire}/:id`, + (req, res, ctx) => { + const { id } = req.params; + const categoryId = parseInt(id as string); + const categoryIndex = categoryDB.categories.findIndex((el) => el.id === categoryId); + const myCategoryIndex = myCategoryDB.categories.findIndex((el) => el.id === categoryId); + const subscriptionIndex = subscriptionDB.subscriptions.findIndex( + (el) => el.category.id === categoryId + ); + + if (categoryIndex < 0 || myCategoryIndex < 0 || subscriptionIndex < 0) { + return res(ctx.status(400)); + } + + categoryDB.categories.splice(categoryIndex, 1); + myCategoryDB.categories.splice(myCategoryIndex, 1); + subscriptionDB.subscriptions.splice(subscriptionIndex, 1); + + return res(ctx.status(204)); + } + ), + + rest.get(`${API_URL}${categoryApi.endpoint.entire}/:id`, (req, res, ctx) => { + const { id } = req.params; + const categoryId = parseInt(id as string); + const category = categoryDB.categories.find((el) => el.id === categoryId); + + if (!category) { + return res(ctx.status(404)); + } + + return res(ctx.status(201), ctx.json(category)); + }), + + rest.get(API_URL + categoryApi.endpoint.my, (req, res, ctx) => { + return res(ctx.status(200), ctx.json(myCategoryDB)); + }), + + rest.get(API_URL + profileApi.endpoint.get, (req, res, ctx) => { + return res(ctx.status(200), ctx.json(tigerProfileDB)); + }), + + rest.patch(API_URL + profileApi.endpoint.patch, (req, res, ctx) => { + if (!req.body) { + return res(ctx.status(404)); + } + + tigerProfileDB.displayName = (req.body as Pick).displayName; + + return res(ctx.status(204)); + }), + + rest.get(API_URL + scheduleApi.endpoint.get, (req, res, ctx) => { + return res(ctx.status(200), ctx.json(getScheduleDB)); + }), + + rest.post>( + `${API_URL}/api/categories/:id/schedules`, + (req, res, ctx) => { + const { id } = req.params; + const categoryId = parseInt(id as string); + const newSchedule = { id: `${scheduleDB.length + 1}`, categoryId, ...req.body }; + + scheduleDB.push(newSchedule); + + return res(ctx.status(201)); + } + ), + + rest.patch>( + `${API_URL}/api/schedules/:id`, + (req, res, ctx) => { + const { id: scheduleId } = req.params; + + const scheduleIndex = scheduleDB.findIndex((el) => el.id === scheduleId); + + if (scheduleIndex < 0) { + return res(ctx.status(404)); + } + + const scheduleGetIndex: { [key: string]: number } = { + longTerms: getScheduleDB.longTerms.findIndex((el) => el.id === scheduleId), + allDays: getScheduleDB.allDays.findIndex((el) => el.id === scheduleId), + fewHours: getScheduleDB.fewHours.findIndex((el) => el.id === scheduleId), + }; + const scheduleType = Object.keys(scheduleGetIndex).find( + (key) => scheduleGetIndex[key] !== -1 + ); + + if (scheduleType === undefined) { + return res(ctx.status(404)); + } + + scheduleDB[scheduleIndex] = { ...scheduleDB[scheduleIndex], ...req.body }; + getScheduleDB[scheduleType][scheduleGetIndex[scheduleType]] = { + ...getScheduleDB[scheduleType][scheduleGetIndex[scheduleType]], + ...req.body, + }; + + return res(ctx.status(204)); + } + ), + + rest.delete(`${API_URL}/api/schedules/:id`, (req, res, ctx) => { + const { id: scheduleId } = req.params; + + const scheduleIndex = scheduleDB.findIndex((el) => el.id === scheduleId); + + if (scheduleIndex < 0) { + return res(ctx.status(404)); + } + + const scheduleGetIndex: { [key: string]: number } = { + longTerms: getScheduleDB.longTerms.findIndex((el) => el.id === scheduleId), + allDays: getScheduleDB.allDays.findIndex((el) => el.id === scheduleId), + fewHours: getScheduleDB.fewHours.findIndex((el) => el.id === scheduleId), + }; + const scheduleType = Object.keys(scheduleGetIndex).find((key) => scheduleGetIndex[key] !== -1); + + if (scheduleType === undefined) { + return res(ctx.status(404)); + } + + scheduleDB.splice(scheduleIndex, 1); + getScheduleDB[scheduleType].splice(scheduleGetIndex[scheduleType], 1); + + return res(ctx.status(204)); + }), + + rest.get(API_URL + subscriptionApi.endpoint.get, (req, res, ctx) => { + return res(ctx.status(200), ctx.json(subscriptionDB)); + }), + + rest.post>( + `${API_URL}/api/members/me/categories/:id/subscriptions`, + (req, res, ctx) => { + const { id } = req.params; + const categoryId = parseInt(id as string); + const newSubscription = { + id: subscriptionDB.subscriptions.length + 1, + category: { + id: categoryDB.categories[categoryId - 1].id, + name: categoryDB.categories[categoryId - 1].name, + creator: tigerProfileDB, + createdAt: categoryDB.categories[categoryId - 1].createdAt, + }, + colorCode: req.body.colorCode, + checked: true, + }; + + subscriptionDB.subscriptions.push(newSubscription); + + return res(ctx.status(201), ctx.json(newSubscription)); + } + ), + + rest.delete(`${API_URL}/api/members/me/subscriptions/:id`, (req, res, ctx) => { + const { id } = req.params; + const subscriptionId = parseInt(id as string); + const subscriptionIndex = subscriptionDB.subscriptions.findIndex( + (el) => el.id === subscriptionId + ); + + if (subscriptionIndex > -1) { + subscriptionDB.subscriptions.splice(subscriptionIndex, 1); + } + + return res(ctx.status(204)); + }), +]; + +export { handlers }; diff --git a/frontend/src/pages/AuthPage/AuthPage.tsx b/frontend/src/pages/AuthPage/AuthPage.tsx new file mode 100644 index 00000000..2e7fdfc2 --- /dev/null +++ b/frontend/src/pages/AuthPage/AuthPage.tsx @@ -0,0 +1,47 @@ +import { AxiosError } from 'axios'; +import { useEffect } from 'react'; +import { useMutation } from 'react-query'; +import { useNavigate } from 'react-router-dom'; +import { useRecoilState } from 'recoil'; + +import { userState } from '@/recoil/atoms'; + +import { PATH } from '@/constants'; +import { API, CACHE_KEY } from '@/constants/api'; + +import { getSearchParam } from '@/utils'; +import { setAccessToken } from '@/utils/storage'; + +import loginApi from '@/api/login'; + +function AuthPage() { + const [user, setUser] = useRecoilState(userState); + const navigate = useNavigate(); + + const code = getSearchParam(API.AUTH_CODE_KEY); + + const { mutate } = useMutation(CACHE_KEY.AUTH, () => loginApi.auth(code), { + onError: () => onErrorAuth(), + onSuccess: (data) => onSuccessAuth(data), + }); + + const onErrorAuth = () => { + navigate(PATH.MAIN); + }; + + const onSuccessAuth = (accessToken: string) => { + setUser({ ...user, accessToken }); + setAccessToken(accessToken); + + navigate(PATH.MAIN); + }; + + useEffect(() => { + code && mutate(); + !code && navigate(PATH.MAIN); + }, []); + + return
; +} + +export default AuthPage; diff --git a/frontend/src/pages/CalendarPage/CalendarPage.styles.ts b/frontend/src/pages/CalendarPage/CalendarPage.styles.ts new file mode 100644 index 00000000..cf63dc19 --- /dev/null +++ b/frontend/src/pages/CalendarPage/CalendarPage.styles.ts @@ -0,0 +1,245 @@ +import { css, Theme } from '@emotion/react'; + +import { DAYS } from '@/constants/date'; + +const calendarPage = css` + padding: 0 5rem; +`; + +const calendarHeader = ({ colors, flex }: Theme) => css` + ${flex.row} + + justify-content: space-between; + + width: 100%; + padding: 3rem 2rem; + + font-size: 5rem; + font-weight: 500; + color: ${colors.GRAY_700}; +`; + +const monthPicker = ({ flex }: Theme) => css` + ${flex.row} + + justify-content: space-around; +`; + +const todayButton = ({ colors }: Theme) => css` + width: 12rem; + height: 8rem; + + padding: auto 0; + + font-size: 4rem; + line-height: 4rem; + font-weight: 500; + color: ${colors.GRAY_700}; +`; + +const navButton = ({ colors }: Theme) => css` + position: relative; + + width: 8rem; + height: 8rem; + padding: 0; + + font-size: 4rem; + line-height: 4rem; + color: ${colors.GRAY_600}; + + &:hover { + border-radius: 50%; + + background: ${colors.GRAY_100}; + + filter: none; + } + + &:hover span { + visibility: visible; + } +`; + +const navButtonTitle = ({ 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 navBarGrid = css` + display: grid; + grid-template-columns: repeat(7, calc(100% / 7)); +`; + +const calendarGrid = (rowNum: number) => css` + display: grid; + grid-template-columns: repeat(7, calc(100% / 7)); + grid-auto-rows: calc(75vh / ${rowNum}); +`; + +const dayBar = ({ colors }: Theme, day: string) => css` + padding: 2rem 3rem; + border-top: 1px solid ${colors.GRAY_300}; + border-right: 1px solid ${colors.GRAY_300}; + border-left: ${day === DAYS[0] && `1px solid ${colors.GRAY_300}`}; + + font-size: 3rem; + text-align: right; + color: ${day === DAYS[0] && colors.RED_400}; +`; + +const dateBorder = ({ colors }: Theme, day: number) => css` + position: relative; + + height: 100%; + border-bottom: 1px solid ${colors.GRAY_300}; + border-right: 1px solid ${colors.GRAY_300}; + border-left: ${day === 0 && `1px solid ${colors.GRAY_300}`}; + + &:hover { + background: ${colors.GRAY_000}; + } +`; + +const dateText = ({ colors }: Theme, day: number, isThisMonth: boolean, isToday: boolean) => css` + position: absolute; + top: 1rem; + right: 1rem; + + width: 5rem; + height: 5rem; + padding: 1rem; + border-radius: 50%; + + background: ${isToday && colors.YELLOW_500}; + + font-size: 2.5rem; + text-align: center; + line-height: 3rem; + font-weight: 500; + color: ${isToday + ? colors.WHITE + : day === 0 + ? isThisMonth + ? colors.RED_400 + : `${colors.RED_400}80` + : isThisMonth + ? colors.GRAY_700 + : `${colors.GRAY_700}80`}; +`; + +const itemWithBackgroundStyle = ( + priority: number, + color: string, + isHovering: boolean, + maxView: number +) => css` + position: absolute; + top: ${priority * 5.5 + 1}rem; + + display: ${priority >= maxView ? 'none' : 'block'}; + width: 100%; + height: 5rem; + padding: 1rem; + + background: ${color}; + + font-size: 2.75rem; + line-height: 2.75rem; + white-space: nowrap; + color: white; + + cursor: pointer; + filter: ${isHovering && 'brightness(0.95)'}; +`; + +const itemWithoutBackgroundStyle = ( + { colors }: Theme, + priority: number, + color: string, + isHovering: boolean, + maxView: number +) => css` + ${itemWithBackgroundStyle(priority, color, isHovering, maxView)}; + + overflow: hidden; + + border-left: 3px solid ${color}; + + background: ${isHovering ? colors.GRAY_000 : colors.WHITE}; + + color: black; + + cursor: pointer; + filter: none; +`; + +const moreStyle = ({ colors }: Theme) => css` + position: absolute; + bottom: 0; + + width: 100%; + height: 5rem; + padding: 1rem; + + font-size: 2.75rem; + line-height: 2.75rem; + white-space: nowrap; + font-weight: 200; + color: ${colors.GRAY_500}; + + cursor: pointer; + + &:hover { + color: ${colors.BLACK}; + } +`; + +const spinnerStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap: 2rem; + + width: 100%; + height: 100%; + + font-size: 2rem; +`; + +const waitingNavStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap:4rem; +`; + +export { + calendarGrid, + calendarHeader, + calendarPage, + dayBar, + dateBorder, + dateText, + itemWithoutBackgroundStyle, + itemWithBackgroundStyle, + monthPicker, + moreStyle, + navBarGrid, + navButton, + navButtonTitle, + spinnerStyle, + todayButton, + waitingNavStyle, +}; diff --git a/frontend/src/pages/CalendarPage/CalendarPage.tsx b/frontend/src/pages/CalendarPage/CalendarPage.tsx new file mode 100644 index 00000000..02cbf894 --- /dev/null +++ b/frontend/src/pages/CalendarPage/CalendarPage.tsx @@ -0,0 +1,443 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useRef, useState } from 'react'; +import { useQuery } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useCalendar from '@/hooks/useCalendar'; +import useSchedulePriority from '@/hooks/useSchedulePriority'; +import useToggle from '@/hooks/useToggle'; + +import { ModalPosType } from '@/@types'; +import { CalendarType } from '@/@types/calendar'; +import { ScheduleResponseType, ScheduleType } from '@/@types/schedule'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; +import PageLayout from '@/components/@common/PageLayout/PageLayout'; +import Spinner from '@/components/@common/Spinner/Spinner'; +import DateModal from '@/components/DateModal/DateModal'; +import ScheduleAddButton from '@/components/ScheduleAddButton/ScheduleAddButton'; +import ScheduleAddModal from '@/components/ScheduleAddModal/ScheduleAddModal'; +import ScheduleModal from '@/components/ScheduleModal/ScheduleModal'; +import ScheduleModifyModal from '@/components/ScheduleModifyModal/ScheduleModifyModal'; + +import { CALENDAR } from '@/constants'; +import { CACHE_KEY } from '@/constants/api'; +import { DAYS } from '@/constants/date'; +import { TRANSPARENT } from '@/constants/style'; + +import { + getDayFromFormattedDate, + getFormattedDate, + getISODateString, + getThisDate, + getThisMonth, +} from '@/utils/date'; + +import scheduleApi from '@/api/schedule'; + +import { AiOutlineLeft, AiOutlineRight } from 'react-icons/ai'; + +import { + calendarGrid, + calendarHeader, + calendarPage, + dateBorder, + dateText, + dayBar, + itemWithBackgroundStyle, + itemWithoutBackgroundStyle, + monthPicker, + moreStyle, + navBarGrid, + navButton, + navButtonTitle, + spinnerStyle, + todayButton, + waitingNavStyle, +} from './CalendarPage.styles'; + +function CalendarPage() { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const dateRef = useRef(null); + + const [hoveringId, setHoveringId] = useState('0'); + const [dateInfo, setDateInfo] = useState(null); + const [modalPos, setModalPos] = useState({}); + const [scheduleInfo, setScheduleInfo] = useState(null); + const [moreDateInfo, setMoreDateInfo] = useState(null); + + const { + calendarMonth, + current, + moveToBeforeMonth, + moveToToday, + moveToNextMonth, + startDate, + endDate, + } = useCalendar(); + + const { getLongTermsPriority, getAllDaysPriority, getFewHoursPriority } = + useSchedulePriority(calendarMonth); + + const { state: isScheduleAddModalOpen, toggleState: toggleScheduleAddModalOpen } = useToggle(); + const { state: isScheduleModalOpen, toggleState: toggleScheduleModalOpen } = useToggle(); + const { state: isScheduleModifyModalOpen, toggleState: toggleScheduleModifyModalOpen } = + useToggle(); + const { state: isDateModalOpen, toggleState: toggleDateModalOpen } = useToggle(); + + const { isLoading, data } = useQuery, AxiosError>( + [CACHE_KEY.SCHEDULES, current], + () => scheduleApi.get(accessToken, startDate, endDate) + ); + + const rowNum = Math.ceil(calendarMonth.length / 7); + + const handleClickDate = (e: React.MouseEvent, info: CalendarType) => { + if (e.target !== e.currentTarget) { + return; + } + + setDateInfo(info); + toggleScheduleAddModalOpen(); + }; + + const handleClickSchedule = (e: React.MouseEvent, info: ScheduleType) => { + if (e.target !== e.currentTarget) { + return; + } + + setModalPos(calculateModalPos(e.clientX, e.clientY)); + setScheduleInfo(info); + toggleScheduleModalOpen(); + }; + + const calculateModalPos = (clickX: number, clickY: number) => { + const position = { top: clickY, right: 0, bottom: 0, left: clickX }; + + if (clickX > innerWidth / 2) { + position.right = innerWidth - clickX; + position.left = 0; + } + + if (clickY > innerHeight / 2) { + position.bottom = innerHeight - clickY; + position.top = 0; + } + + return position; + }; + + if (isLoading || data === undefined) { + return ( + +
+
+ + {current.year}년 {current.month}월 + +
+
+ + 일정을 가져오고 있습니다. +
+
+ + + +
+
+
+
+ {DAYS.map((day) => ( + + {day} + + ))} +
+
+ {calendarMonth.map((info) => { + const key = getFormattedDate(info.year, info.month, info.date); + + return ( +
handleClickDate(e, info)} + ref={dateRef} + > + + {info.date} + +
+ ); + })} +
+ + + + +
+
+ ); + } + + const longTermsWithPriority = getLongTermsPriority(data.data.longTerms); + const allDaysWithPriority = getAllDaysPriority(data.data.allDays); + const fewHoursWithPriority = getFewHoursPriority(data.data.fewHours); + + const handleClickMoreButton = (e: React.MouseEvent, info: CalendarType) => { + if (e.target !== e.currentTarget) { + return; + } + + setModalPos(calculateModalPos(e.clientX, e.clientY)); + setMoreDateInfo(info); + toggleDateModalOpen(); + }; + + const onMouseEnter = (scheduleId: string) => { + setHoveringId(scheduleId); + }; + + const onMouseLeave = () => { + setHoveringId('0'); + }; + + const maxView = + dateRef.current !== null + ? Math.floor((dateRef.current.clientHeight - 20) / 22) + : CALENDAR.MAX_VIEW; + + return ( + +
+
+ + {current.year}년 {current.month}월 + +
+ + + +
+
+
+ {DAYS.map((day) => ( + + {day} + + ))} +
+
+ {calendarMonth.map((info) => { + const key = getFormattedDate(info.year, info.month, info.date); + + return ( + <> +
handleClickDate(e, info)} + ref={dateRef} + > + + {info.date} + + + {longTermsWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const endDate = getISODateString(el.schedule.endDateTime); + const nowDate = getFormattedDate(info.year, info.month, info.date); + const nowDay = getDayFromFormattedDate(nowDate); + + if (startDate <= nowDate && nowDate <= endDate && el.priority >= maxView) { + return ( + handleClickMoreButton(e, info)}> + 일정 더보기 + + ); + } + + return ( + startDate <= nowDate && + nowDate <= endDate && ( +
onMouseEnter(el.schedule.id)} + onClick={(e) => handleClickSchedule(e, el.schedule)} + onMouseLeave={onMouseLeave} + > + {(startDate === nowDate || nowDay === 0) && + (el.schedule.title || CALENDAR.EMPTY_TITLE)} +
+ ) + ); + })} + + {allDaysWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const nowDate = getFormattedDate(info.year, info.month, info.date); + + if (startDate === nowDate && el.priority >= maxView) { + return ( + handleClickMoreButton(e, info)}> + 일정 더보기 + + ); + } + + return ( + startDate === nowDate && ( +
onMouseEnter(el.schedule.id)} + onClick={(e) => handleClickSchedule(e, el.schedule)} + onMouseLeave={onMouseLeave} + > + {el.schedule.title || CALENDAR.EMPTY_TITLE} +
+ ) + ); + })} + + {fewHoursWithPriority.map((el) => { + const startDate = getISODateString(el.schedule.startDateTime); + const nowDate = getFormattedDate(info.year, info.month, info.date); + + if (startDate === nowDate && el.priority >= maxView) { + return ( + handleClickMoreButton(e, info)}> + 일정 더보기 + + ); + } + + return ( + startDate === nowDate && ( +
onMouseEnter(el.schedule.id)} + onClick={(e) => handleClickSchedule(e, el.schedule)} + onMouseLeave={onMouseLeave} + > + {el.schedule.title || CALENDAR.EMPTY_TITLE} +
+ ) + ); + })} +
+ + {info === moreDateInfo && ( + + + + )} + + ); + })} +
+
+ + + + + + + {scheduleInfo ? ( + <> + + + + + + + + ) : ( + <> + )} +
+ ); +} + +export default CalendarPage; diff --git a/frontend/src/pages/CategoryPage/CategoryPage.styles.ts b/frontend/src/pages/CategoryPage/CategoryPage.styles.ts new file mode 100644 index 00000000..2376d6e6 --- /dev/null +++ b/frontend/src/pages/CategoryPage/CategoryPage.styles.ts @@ -0,0 +1,108 @@ +import { css, Theme } from '@emotion/react'; + +const categoryPageStyle = css` + height: 80%; + padding: 9rem; +`; + +const searchFormStyle = css` + position: relative; + + width: 100%; + height: 12rem; + margin-bottom: 8rem; +`; + +const searchButtonStyle = css` + position: absolute; + + top: 50%; + transform: translateY(-50%); + + width: 12rem; +`; + +const searchFieldsetStyle = css` + height: 100%; +`; + +const searchInputStyle = css` + height: 100%; + padding-left: 12rem; + + font-size: 4rem; +`; + +const buttonStyle = ({ colors }: Theme) => css` + width: 40rem; + height: 12rem; + border-radius: 8px; + border: 1px solid ${colors.GRAY_500}; + + background: ${colors.YELLOW_500}; + + font-size: 3.5rem; + font-weight: 700; + line-height: 3.5rem; + color: ${colors.WHITE}; +`; + +const controlStyle = ({ flex }: Theme) => css` + ${flex.row}; + + align-items: flex-start; + justify-content: center; + gap: 4rem; + + width: 100%; +`; + +const outLineButtonStyle = ({ colors }: Theme) => css` + width: 40rem; + height: 12rem; + border-radius: 8px; + border: 1px solid ${colors.GRAY_500}; + + font-size: 3.5rem; + font-weight: 700; + line-height: 3.5rem; + color: ${colors.YELLOW_500}; +`; + +const toggleModeStyle = ({ colors, flex }: Theme, mode: 'ALL' | 'MY') => css` + ${flex.row}; + + justify-content: space-around; + + width: 35rem; + height: 12rem; + padding: 0 1rem; + border-radius: 8px; + border: 1px solid ${colors.GRAY_500}; + + background: linear-gradient( + 90deg, + ${mode === 'ALL' ? colors.YELLOW_500 : colors.WHITE} 50%, + ${mode === 'MY' ? colors.YELLOW_500 : colors.WHITE} 50% + ); +`; + +const modeTextStyle = ({ colors }: Theme, isSelected: boolean) => css` + font-size: 3.5rem; + font-weight: 700; + line-height: 3.5rem; + color: ${isSelected ? colors.WHITE : colors.YELLOW_500}; +`; + +export { + buttonStyle, + categoryPageStyle, + controlStyle, + modeTextStyle, + outLineButtonStyle, + searchButtonStyle, + searchFieldsetStyle, + searchFormStyle, + searchInputStyle, + toggleModeStyle, +}; diff --git a/frontend/src/pages/CategoryPage/CategoryPage.tsx b/frontend/src/pages/CategoryPage/CategoryPage.tsx new file mode 100644 index 00000000..ec8cdd3d --- /dev/null +++ b/frontend/src/pages/CategoryPage/CategoryPage.tsx @@ -0,0 +1,100 @@ +import { useTheme } from '@emotion/react'; +import { lazy, Suspense, useRef, useState } from 'react'; + +import useToggle from '@/hooks/useToggle'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; +import ModalPortal from '@/components/@common/ModalPortal/ModalPortal'; +import PageLayout from '@/components/@common/PageLayout/PageLayout'; +import CategoryAddModal from '@/components/CategoryAddModal/CategoryAddModal'; +import CategoryListFallback from '@/components/CategoryList/CategoryList.fallback'; +import MyCategoryListFallback from '@/components/MyCategoryList/MyCategoryList.fallback'; + +import { GoSearch } from 'react-icons/go'; + +import { + buttonStyle, + categoryPageStyle, + controlStyle, + modeTextStyle, + searchButtonStyle, + searchFieldsetStyle, + searchFormStyle, + searchInputStyle, + toggleModeStyle, +} from './CategoryPage.styles'; + +const CategoryList = lazy(() => import('@/components/CategoryList/CategoryList')); +const MyCategoryList = lazy(() => import('@/components/MyCategoryList/MyCategoryList')); + +function CategoryPage() { + const theme = useTheme(); + const [mode, setMode] = useState<'ALL' | 'MY'>('ALL'); + const { state: isCategoryAddModalOpen, toggleState: toggleCategoryAddModalOpen } = useToggle(); + + const keywordRef = useRef(null); + + const [keyword, setKeyword] = useState(''); + + const handleSubmitCategorySearchForm = (e: React.FormEvent) => { + e.preventDefault(); + + if (!(keywordRef.current instanceof HTMLInputElement)) { + return; + } + + setKeyword((keywordRef.current as HTMLInputElement).value); + }; + + const handleClickFilteringButton = () => { + mode === 'ALL' && setMode('MY'); + mode === 'MY' && setMode('ALL'); + }; + + const handleClickCategoryAddButton = () => { + toggleCategoryAddModalOpen(); + }; + + return ( + +
+ + + +
+
+ +
+ + + +
+ {mode === 'ALL' && ( + }> + + + )} + {mode === 'MY' && ( + }> + + + )} +
+
+ ); +} + +export default CategoryPage; diff --git a/frontend/src/pages/MainPage/MainPage.tsx b/frontend/src/pages/MainPage/MainPage.tsx new file mode 100644 index 00000000..adaac4fd --- /dev/null +++ b/frontend/src/pages/MainPage/MainPage.tsx @@ -0,0 +1,18 @@ +import { useRecoilValue } from 'recoil'; + +import { userState } from '@/recoil/atoms'; + +import CalendarPage from '@/pages/CalendarPage/CalendarPage'; +import StartPage from '@/pages/StartPage/StartPage'; + +function MainPage() { + const { accessToken } = useRecoilValue(userState); + + if (!accessToken) { + return ; + } + + return ; +} + +export default MainPage; diff --git a/frontend/src/pages/NotFoundPage/NotFoundPage.styles.ts b/frontend/src/pages/NotFoundPage/NotFoundPage.styles.ts new file mode 100644 index 00000000..b9439857 --- /dev/null +++ b/frontend/src/pages/NotFoundPage/NotFoundPage.styles.ts @@ -0,0 +1,33 @@ +import { css, Theme } from '@emotion/react'; + +const layoutStyle = ({ flex }: Theme) => css` + ${flex.column}; + + gap: 15rem; + + height: calc(100% - 16rem); +`; + +const buttonStyle = ({ colors }: Theme) => css` + padding: 3rem 6rem; + border: 1px solid ${colors.GRAY_800}; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.25); + + font-size: 5rem; + color: ${colors.GRAY_800}; + + &:hover { + box-shadow: none; + } +`; + +const textStyle = ({ colors }: Theme, fontSize: string) => css` + font-size: ${fontSize}; + font-weight: 400; + text-align: center; + line-height: 150%; + color: ${colors.GRAY_800}; +`; + +export { buttonStyle, layoutStyle, textStyle }; diff --git a/frontend/src/pages/NotFoundPage/NotFoundPage.tsx b/frontend/src/pages/NotFoundPage/NotFoundPage.tsx new file mode 100644 index 00000000..2137b1c7 --- /dev/null +++ b/frontend/src/pages/NotFoundPage/NotFoundPage.tsx @@ -0,0 +1,36 @@ +import { useTheme } from '@emotion/react'; +import { useNavigate } from 'react-router-dom'; + +import Button from '@/components/@common/Button/Button'; +import PageLayout from '@/components/@common/PageLayout/PageLayout'; + +import { PATH } from '@/constants'; + +import { buttonStyle, layoutStyle, textStyle } from './NotFoundPage.styles'; + +function NotFoundPage() { + const theme = useTheme(); + + const navigation = useNavigate(); + + const handleClickReturnButton = () => { + navigation(PATH.MAIN); + }; + + return ( + +
+ (⊙_⊙;) + + 죄송합니다.
요청하신 페이지를 찾을 수가 없어요. +
+ + +
+
+ ); +} + +export default NotFoundPage; diff --git a/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.styles.ts b/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.styles.ts new file mode 100644 index 00000000..ebc9740d --- /dev/null +++ b/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.styles.ts @@ -0,0 +1,22 @@ +import { css, Theme } from '@emotion/react'; + +const privacyPolicyStyle = ({ flex }: Theme) => css` + ${flex.column} + + align-items: flex-start; + + margin: 5% 10%; +`; + +const headerStyle = css` + margin-bottom: 10rem; + + font-size: 5rem; + font-weight: bold; +`; + +const contentStyle = css` + white-space: pre-wrap; +`; + +export { contentStyle, headerStyle, privacyPolicyStyle }; diff --git a/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.tsx b/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.tsx new file mode 100644 index 00000000..7f4b813a --- /dev/null +++ b/frontend/src/pages/PrivacyPolicyPage/PrivacyPolicyPage.tsx @@ -0,0 +1,158 @@ +import PageLayout from '@/components/@common/PageLayout/PageLayout'; + +import { contentStyle, headerStyle, privacyPolicyStyle } from './PrivacyPolicyPage.styles'; + +function PrivacyPolicyPage() { + return ( + +
+

개인정보처리방침

+

+ {` +< dallog >('https://dallog.me'이하 'dallog')은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다. + +○ 이 개인정보처리방침은 2022년 8월 17부터 적용됩니다. + + +제1조(개인정보의 처리 목적) + +< dallog >('https://dallog.me'이하 'dallog')은(는) 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며 이용 목적이 변경되는 경우에는 「개인정보 보호법」 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다. + +1. 홈페이지 회원가입 및 관리 + +회원자격 유지·관리 목적으로 개인정보를 처리합니다. + + +2. 재화 또는 서비스 제공 + +서비스 제공을 목적으로 개인정보를 처리합니다. + + + + +제2조(개인정보의 처리 및 보유 기간) + +① < dallog >은(는) 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유·이용기간 내에서 개인정보를 처리·보유합니다. + +② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다. + +1.<홈페이지 회원가입 및 관리> +<홈페이지 회원가입 및 관리>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<회원탈퇴 시 까지>까지 위 이용목적을 위하여 보유.이용됩니다. +보유근거 : 회원 가입의사 확인, 회원자격 유지․관리, 서비스 부정이용 방지 목적으로 개인정보를 처리합니다. +관련법령 : +예외사유 : + + +제3조(처리하는 개인정보의 항목) + +① < dallog >은(는) 다음의 개인정보 항목을 처리하고 있습니다. + +1< 홈페이지 회원가입 및 관리 > +필수항목 : 이메일, 유저네임, 프로필이미지URL +선택항목 : + + +제4조(개인정보의 파기절차 및 파기방법) + + +① < dallog > 은(는) 개인정보 보유기간의 경과, 처리목적 달성 등 개인정보가 불필요하게 되었을 때에는 지체없이 해당 개인정보를 파기합니다. + +② 정보주체로부터 동의받은 개인정보 보유기간이 경과하거나 처리목적이 달성되었음에도 불구하고 다른 법령에 따라 개인정보를 계속 보존하여야 하는 경우에는, 해당 개인정보를 별도의 데이터베이스(DB)로 옮기거나 보관장소를 달리하여 보존합니다. + +③ 개인정보 파기의 절차 및 방법은 다음과 같습니다. +1. 파기절차 +< dallog > 은(는) 파기 사유가 발생한 개인정보를 선정하고, < dallog > 의 개인정보 보호책임자의 승인을 받아 개인정보를 파기합니다. + + + +제5조(정보주체와 법정대리인의 권리·의무 및 그 행사방법에 관한 사항) + + + +① 정보주체는 dallog에 대해 언제든지 개인정보 열람·정정·삭제·처리정지 요구 등의 권리를 행사할 수 있습니다. + +② 제1항에 따른 권리 행사는 dallog에 대해 「개인정보 보호법」 시행령 제41조제1항에 따라 서면, 전자우편, 모사전송(FAX) 등을 통하여 하실 수 있으며 dallog은(는) 이에 대해 지체 없이 조치하겠습니다. + +③ 제1항에 따른 권리 행사는 정보주체의 법정대리인이나 위임을 받은 자 등 대리인을 통하여 하실 수 있습니다.이 경우 “개인정보 처리 방법에 관한 고시(제2020-7호)” 별지 제11호 서식에 따른 위임장을 제출하셔야 합니다. + +④ 개인정보 열람 및 처리정지 요구는 「개인정보 보호법」 제35조 제4항, 제37조 제2항에 의하여 정보주체의 권리가 제한 될 수 있습니다. + +⑤ 개인정보의 정정 및 삭제 요구는 다른 법령에서 그 개인정보가 수집 대상으로 명시되어 있는 경우에는 그 삭제를 요구할 수 없습니다. + +⑥ dallog은(는) 정보주체 권리에 따른 열람의 요구, 정정·삭제의 요구, 처리정지의 요구 시 열람 등 요구를 한 자가 본인이거나 정당한 대리인인지를 확인합니다. + + + +제6조(개인정보의 안전성 확보조치에 관한 사항) + +< dallog >은(는) 개인정보의 안전성 확보를 위해 다음과 같은 조치를 취하고 있습니다. + +1. 해킹 등에 대비한 기술적 대책 +('dallog')은 해킹이나 컴퓨터 바이러스 등에 의한 개인정보 유출 및 훼손을 막기 위하여 보안프로그램을 설치하고 주기적인 갱신·점검을 하며 외부로부터 접근이 통제된 구역에 시스템을 설치하고 기술적/물리적으로 감시 및 차단하고 있습니다. + +2. 개인정보에 대한 접근 제한 +개인정보를 처리하는 데이터베이스시스템에 대한 접근권한의 부여,변경,말소를 통하여 개인정보에 대한 접근통제를 위하여 필요한 조치를 하고 있으며 침입차단시스템을 이용하여 외부로부터의 무단 접근을 통제하고 있습니다. + + + + +제7조(개인정보를 자동으로 수집하는 장치의 설치·운영 및 그 거부에 관한 사항) + + + +dallog 은(는) 정보주체의 이용정보를 저장하고 수시로 불러오는 ‘쿠키(cookie)’를 사용하지 않습니다. + +제8조 (개인정보 보호책임자에 관한 사항) + +① dallog 은(는) 개인정보 처리에 관한 업무를 총괄해서 책임지고, 개인정보 처리와 관련한 정보주체의 불만처리 및 피해구제 등을 위하여 아래와 같이 개인정보 보호책임자를 지정하고 있습니다. + +▶ 개인정보 보호책임자 +성명 :구동희 +직책 :팀원 +직급 :팀원 +연락처 :010-3160-2953, gudonghee2000@gmail.com, +※ 개인정보 보호 담당부서로 연결됩니다. + +▶ 개인정보 보호 담당부서 +부서명 : +담당자 : +연락처 :, , +② 정보주체께서는 dallog 의 서비스(또는 사업)을 이용하시면서 발생한 모든 개인정보 보호 관련 문의, 불만처리, 피해구제 등에 관한 사항을 개인정보 보호책임자 및 담당부서로 문의하실 수 있습니다. dallog 은(는) 정보주체의 문의에 대해 지체 없이 답변 및 처리해드릴 것입니다. + +제9조(개인정보의 열람청구를 접수·처리하는 부서) +정보주체는 「개인정보 보호법」 제35조에 따른 개인정보의 열람 청구를 아래의 부서에 할 수 있습니다. +< dallog >은(는) 정보주체의 개인정보 열람청구가 신속하게 처리되도록 노력하겠습니다. + +▶ 개인정보 열람청구 접수·처리 부서 +부서명 : +담당자 : +연락처 : , , + + +제10조(정보주체의 권익침해에 대한 구제방법) + + + +정보주체는 개인정보침해로 인한 구제를 받기 위하여 개인정보분쟁조정위원회, 한국인터넷진흥원 개인정보침해신고센터 등에 분쟁해결이나 상담 등을 신청할 수 있습니다. 이 밖에 기타 개인정보침해의 신고, 상담에 대하여는 아래의 기관에 문의하시기 바랍니다. + +1. 개인정보분쟁조정위원회 : (국번없이) 1833-6972 (www.kopico.go.kr) +2. 개인정보침해신고센터 : (국번없이) 118 (privacy.kisa.or.kr) +3. 대검찰청 : (국번없이) 1301 (www.spo.go.kr) +4. 경찰청 : (국번없이) 182 (ecrm.cyber.go.kr) + +「개인정보보호법」제35조(개인정보의 열람), 제36조(개인정보의 정정·삭제), 제37조(개인정보의 처리정지 등)의 규정에 의한 요구에 대 하여 공공기관의 장이 행한 처분 또는 부작위로 인하여 권리 또는 이익의 침해를 받은 자는 행정심판법이 정하는 바에 따라 행정심판을 청구할 수 있습니다. + +※ 행정심판에 대해 자세한 사항은 중앙행정심판위원회(www.simpan.go.kr) 홈페이지를 참고하시기 바랍니다. + +제11조(개인정보 처리방침 변경) + + +① 이 개인정보처리방침은 2022년 8월 17부터 적용됩니다. + `} +

+
+
+ ); +} + +export default PrivacyPolicyPage; diff --git a/frontend/src/pages/SchedulingPage/SchedulingPage.styles.ts b/frontend/src/pages/SchedulingPage/SchedulingPage.styles.ts new file mode 100644 index 00000000..d6348639 --- /dev/null +++ b/frontend/src/pages/SchedulingPage/SchedulingPage.styles.ts @@ -0,0 +1,124 @@ +import { css, Theme } from '@emotion/react'; + +const pageStyle = ({ flex }: Theme) => css` + ${flex.column} + + padding: 9rem 10%; +`; + +const formStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap: 6rem; + + max-width: 100%; + + font-size: 4rem; +`; + +const subscriptionStyle = ({ flex }: Theme) => css` + ${flex.column} + + align-items: flex-start; + gap: 2.5rem; +`; + +const labelStyle = ({ colors }: Theme) => css` + padding: 0 1rem; + + font-size: 4rem; + color: ${colors.GRAY_800}; +`; + +const subscriptionSelectStyle = ({ colors }: Theme) => css` + width: 80rem; + height: 12rem; + padding: 3rem; + border: 1px solid ${colors.GRAY_400}; + border-radius: 8px; + + font-size: inherit; +`; + +const dateTimeStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap: 3rem; + + width: 120rem; +`; + +const dateTimeFieldsetStyle = css` + height: auto; + + font-size: 4rem; +`; + +const resultStyle = ({ flex }: Theme) => css` + ${flex.column} + + overflow-y: overlay; + justify-content: flex-start; + gap: 5rem; + + width: 214rem; + height: 60vh; + padding: 0 4rem; +`; + +const searchButtonStyle = ({ colors, flex }: Theme) => css` + ${flex.row} + + gap: 5rem; + + width: 206rem; + padding: 5rem; + margin: 8rem 0 5rem; + border-radius: 8px; + box-shadow: 2px 2px 4px ${colors.GRAY_500}; + + background: ${colors.ORANGE_500}; + + font-size: 4rem; + font-weight: 500; + color: ${colors.WHITE}; + + &:hover { + box-shadow: none; + } +`; + +const resultTimeStyle = ({ flex }: Theme) => css` + ${flex.row} + + gap: 3rem; + + width: 100%; +`; + +const resultDateTimeStyle = ({ colors }: Theme) => css` + width: 100%; + padding: 6rem; + border-radius: 8px; + + background: ${colors.BLUE_500}; + + font-size: 4rem; + font-weight: 500; + color: white; + text-align: center; +`; + +export { + dateTimeFieldsetStyle, + dateTimeStyle, + formStyle, + labelStyle, + pageStyle, + resultDateTimeStyle, + resultStyle, + resultTimeStyle, + searchButtonStyle, + subscriptionSelectStyle, + subscriptionStyle, +}; diff --git a/frontend/src/pages/SchedulingPage/SchedulingPage.tsx b/frontend/src/pages/SchedulingPage/SchedulingPage.tsx new file mode 100644 index 00000000..9b37254d --- /dev/null +++ b/frontend/src/pages/SchedulingPage/SchedulingPage.tsx @@ -0,0 +1,153 @@ +import { useTheme } from '@emotion/react'; +import { AxiosError, AxiosResponse } from 'axios'; +import { useQuery } from 'react-query'; +import { useRecoilValue } from 'recoil'; + +import useControlledInput from '@/hooks/useControlledInput'; + +import { SchedulingResponseType } from '@/@types/scheduler'; +import { SubscriptionType } from '@/@types/subscription'; + +import { userState } from '@/recoil/atoms'; + +import Button from '@/components/@common/Button/Button'; +import Fieldset from '@/components/@common/Fieldset/Fieldset'; +import PageLayout from '@/components/@common/PageLayout/PageLayout'; + +import { CACHE_KEY } from '@/constants/api'; +import { CATEGORY_TYPE } from '@/constants/category'; + +import { getDate } from '@/utils/date'; + +import schedulerApi from '@/api/scheduler'; +import subscriptionApi from '@/api/subscription'; + +import { BsArrowRight } from 'react-icons/bs'; +import { GoSearch } from 'react-icons/go'; + +import { + dateTimeFieldsetStyle, + dateTimeStyle, + formStyle, + labelStyle, + pageStyle, + resultDateTimeStyle, + resultStyle, + resultTimeStyle, + searchButtonStyle, + subscriptionSelectStyle, + subscriptionStyle, +} from './SchedulingPage.styles'; + +function SchedulingPage() { + const { accessToken } = useRecoilValue(userState); + + const theme = useTheme(); + + const { isLoading: isGetSubscriptionsLoading, data: subscriptionsGetResponse } = useQuery< + AxiosResponse, + AxiosError + >(CACHE_KEY.SUBSCRIPTIONS, () => subscriptionApi.get(accessToken), { + onSuccess: (data) => onSuccessGetSubscriptions(data), + }); + + const category = useControlledInput(); + const startDateTime = useControlledInput(getDate(null)); + const endDateTime = useControlledInput(getDate(null)); + + const onSuccessGetSubscriptions = (data: AxiosResponse) => { + category.setInputValue(`${data.data[0].category.id}`); + refetch(); + }; + + const { data: schedulingGetResponse, refetch } = useQuery< + AxiosResponse, + AxiosError + >( + CACHE_KEY.SCHEDULER, + () => + schedulerApi.get( + accessToken, + Number(category.inputValue), + startDateTime.inputValue, + endDateTime.inputValue + ), + { + enabled: false, + } + ); + + if (isGetSubscriptionsLoading || subscriptionsGetResponse === undefined) { + return <>; + } + + const handleClickSearchButton = () => { + refetch(); + }; + + const formatDateTime = (dateTime: string) => { + return `${dateTime.replace('T', ' ').replace(':', '시 ').slice(0, -3)}분`; + }; + + const subscriptions = subscriptionsGetResponse.data.filter( + (subscription) => subscription.category.categoryType === CATEGORY_TYPE.NORMAL + ); + + return ( + +
+
+
+ 조회할 카테고리 + +
+
+
+ +
+
+
+ +
+ {schedulingGetResponse && + schedulingGetResponse.data.map((schedule) => ( +
+
{formatDateTime(schedule.startDateTime)}
+ +
{formatDateTime(schedule.endDateTime)}
+
+ ))} +
+
+
+ ); +} + +export default SchedulingPage; diff --git a/frontend/src/pages/StartPage/StartPage.styles.ts b/frontend/src/pages/StartPage/StartPage.styles.ts new file mode 100644 index 00000000..bd0ab98d --- /dev/null +++ b/frontend/src/pages/StartPage/StartPage.styles.ts @@ -0,0 +1,286 @@ +import { css, Theme } from '@emotion/react'; + +const calendarStyle = ({ flex, colors }: Theme) => css` + ${flex.column}; + + gap: 2rem; + position: relative; + left: -20%; + top: -10rem; + + width: 200rem; + border: 1px solid ${colors.GRAY_400}; + box-sizing: content-box; + box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.25); + + opacity: 0; + + transform: rotate(10deg); + + @keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } + } + + animation-name: fadeIn; + animation-duration: 1s; + animation-timing-function: ease-in-out; + animation-fill-mode: forwards; +`; + +const dateItemStyle = ({ colors }: Theme) => css` + width: 200rem; + height: 30rem; + padding: 6rem 6rem 4rem; + + background: ${colors.WHITE}; + + font-size: 20rem; + text-align: right; +`; + +const itemStyle = css` + position: relative; + left: -100%; + + width: 200rem; + height: 50rem; + padding: 4rem; + + font-size: 10rem; + font-weight: 600; + text-align: right; + + opacity: 0; + + @keyframes slideIn { + from { + opacity: 0; + left: -100%; + } + + to { + left: 0; + opacity: 1; + } + } + + animation-name: slideIn; + animation-duration: 2s; + animation-timing-function: ease-in-out; + animation-fill-mode: forwards; +`; + +const firstItemStyle = ({ colors }: Theme) => css` + ${itemStyle}; + + background: ${colors.YELLOW_500}; + + animation-delay: 0.5s; +`; + +const secondItemStyle = ({ colors }: Theme) => css` + ${itemStyle}; + + background: ${colors.ORANGE_500}; + + animation-delay: 1s; +`; + +const thirdItemStyle = ({ colors }: Theme) => css` + ${itemStyle}; + + background: ${colors.BLUE_500}; + + animation-delay: 1.5s; +`; + +const blackTextStyle = ({ colors }: Theme) => css` + width: 100%; + + font-size: 20rem; + font-weight: 700; + line-height: 25rem; + color: ${colors.BLACK}; + text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.25); +`; + +const whiteTextStyle = ({ colors }: Theme) => css` + width: 100%; + + font-size: 20rem; + font-weight: 700; + line-height: 25rem; + color: ${colors.WHITE}; + text-shadow: 5px 5px 10px rgba(0, 0, 0, 0.25); +`; + +const introductionStyle = css` + width: 100%; + margin: 20rem 20rem 0 0; + + text-align: right; +`; + +const detailTextStyle = ({ colors }: Theme) => css` + font-size: 3.5rem; + color: ${colors.GRAY_500}; +`; + +const loginText = css` + width: 100%; +`; + +const googleLoginButton = ({ colors, flex }: Theme) => css` + ${flex.row} + + position: relative; + justify-content: flex-start; + + width: 75rem; + height: 15rem; + padding: 4rem; + border-radius: 8px; + border: 1px solid ${colors.GRAY_400}; + box-shadow: 2px 2px 2px ${colors.GRAY_400}; + + background: ${colors.WHITE}; + + font-size: 4rem; + font-weight: 500; + color: ${colors.BLACK}8a; + + &:hover { + box-shadow: 3px 3px 3px ${colors.GRAY_500}; + } +`; + +const mainContentStyle = ({ flex }: Theme) => css` + ${flex.column}; + + align-items: flex-end; + gap: 6rem; +`; + +const firstSectionStyle = ({ flex }: Theme) => css` + ${flex.row}; + + align-items: flex-start; +`; + +const secondSectionStyle = ({ flex, colors }: Theme) => css` + ${flex.column}; + + align-items: center; + justify-content: flex-start; + + width: 100%; + height: 100%; + margin-top: 20rem; + padding: 20rem auto; + gap: 10rem; + + background: ${colors.GRAY_200}; +`; + +const pageStyle = ({ flex }: Theme) => css` + ${flex.column}; + + 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, + detailTextStyle, + dateItemStyle, + firstItemStyle, + firstSectionStyle, + googleLoginButton, + loginText, + imageStyle, + introductionStyle, + mainContentStyle, + methodHeaderStyle, + methodItemStyle, + methodTextStyle, + pageStyle, + refStyle, + secondItemStyle, + secondSectionStyle, + thirdItemStyle, + whiteTextStyle, +}; diff --git a/frontend/src/pages/StartPage/StartPage.tsx b/frontend/src/pages/StartPage/StartPage.tsx new file mode 100644 index 00000000..49a41e18 --- /dev/null +++ b/frontend/src/pages/StartPage/StartPage.tsx @@ -0,0 +1,153 @@ +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'; + +import { CACHE_KEY } from '@/constants/api'; + +import { getThisDate } from '@/utils/date'; + +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, + dateItemStyle, + detailTextStyle, + firstItemStyle, + firstSectionStyle, + googleLoginButton, + imageStyle, + introductionStyle, + loginText, + mainContentStyle, + methodHeaderStyle, + methodItemStyle, + methodTextStyle, + pageStyle, + refStyle, + secondItemStyle, + secondSectionStyle, + thirdItemStyle, + whiteTextStyle, +} from './StartPage.styles'; + +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), + }); + + const onSuccessGetLoginUrl = (loginUrl: string) => { + location.href = loginUrl; + }; + + const handleClickGoogleLoginButton = () => { + refetch(); + }; + + if (error) { + return <>Error; + } + + return ( + +
+
+
+
{getThisDate()}
+
운동 일정
+
스터디 일정
+
동아리 일정
+
+
+ +
+
+
+ 달력 + +
+ 기록 + +
+ 공유할때 +
+ 달록 +
+
+ + 공유 캘린더 플랫폼 달록은 달력과 카테고리를
+ 이용하여 누구나 자신의 일정을 공유할 수 있습니다. +
+ +
+
+
+
+ 달록은 이렇습니다 +
+
+ 달록 사용법 1 + + 공유 받고 싶은 카테고리를 구독하여
내 달력에서 일정을 볼 수 있습니다. +
+
+
+
+ + 구글 캘린더에서 원하는 캘린더를 +
가져올 수 있습니다. +
+ 달록 사용법 2 +
+
+
+ 달록 사용법 3 + + 카테고리 내에서 팀원들과
+ 겹치지 않는 시간을
+ 확인할 수 있습니다. +
+
+
+
+
+
+ ); +} + +export default StartPage; diff --git a/frontend/src/recoil/atoms/index.ts b/frontend/src/recoil/atoms/index.ts new file mode 100644 index 00000000..2dc19022 --- /dev/null +++ b/frontend/src/recoil/atoms/index.ts @@ -0,0 +1,26 @@ +import { atom } from 'recoil'; + +import { ATOM_KEY } from '@/constants'; + +import { getAccessToken } from '@/utils/storage'; + +const sideBarState = atom({ + key: ATOM_KEY.SIDE_BAR, + default: false, +}); + +const userState = atom({ + key: ATOM_KEY.USER, + default: { + accessToken: getAccessToken() ?? '', + }, +}); + +const snackBarState = atom({ + key: ATOM_KEY.SNACK_BAR, + default: { + text: '', + }, +}); + +export { snackBarState, sideBarState, userState }; diff --git a/frontend/src/recoil/selectors/index.ts b/frontend/src/recoil/selectors/index.ts new file mode 100644 index 00000000..4372137a --- /dev/null +++ b/frontend/src/recoil/selectors/index.ts @@ -0,0 +1,13 @@ +import { selector } from 'recoil'; + +import { sideBarState } from '@/recoil/atoms'; + +import { SELECTOR_KEY } from '@/constants'; + +const sideBarSelector = selector({ + key: SELECTOR_KEY.SIDE_BAR, + get: ({ get }) => get(sideBarState), + set: ({ set }) => set(sideBarState, (prev) => !prev), +}); + +export { sideBarSelector }; diff --git a/frontend/src/styles/GlobalStyle.tsx b/frontend/src/styles/GlobalStyle.tsx new file mode 100644 index 00000000..efdf9a00 --- /dev/null +++ b/frontend/src/styles/GlobalStyle.tsx @@ -0,0 +1,48 @@ +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} + + *, + *::after, + *::before { + margin: 0; + + box-sizing: border-box; + } + + body { + overflow: overlay; + + font-family: 'Pretendard', sans-serif; + font-size: 3rem; + + *::-webkit-scrollbar { + width: 2rem; + } + + *::-webkit-scrollbar-thumb { + border-radius: 10px; + background-clip: padding-box; + border: 2px solid transparent; + + background: ${colors.YELLOW_500}; + } + + *::-webkit-scrollbar-track { + border-radius: 10px; + box-shadow: inset 0px 0px 5px white; + + background: ${colors.GRAY_200}; + } + } +`; + +function GlobalStyle() { + return ; +} + +export default GlobalStyle; diff --git a/frontend/src/styles/theme.ts b/frontend/src/styles/theme.ts new file mode 100644 index 00000000..9a38c3e9 --- /dev/null +++ b/frontend/src/styles/theme.ts @@ -0,0 +1,61 @@ +import { css, Theme } from '@emotion/react'; + +const colors = { + YELLOW_000: '#fff9db', + YELLOW_100: '#fff3bf', + YELLOW_200: '#ffec99', + YELLOW_300: '#ffe066', + YELLOW_400: '#fee500', + YELLOW_500: '#F4BD68', + YELLOW_600: '#fab005', + YELLOW_700: '#f59f00', + YELLOW_800: '#f08c00', + YELLOW_900: '#e67700', + GREEN_500: '#03c75a', + WHITE: '#ffffff', + GRAY_000: '#f8f9fa', + GRAY_100: '#f1f3f5', + GRAY_200: '#e9ecef', + GRAY_300: '#dee2e6', + GRAY_400: '#ced4da', + GRAY_500: '#adb5bd', + GRAY_600: '#868e96', + GRAY_700: '#495057', + GRAY_800: '#343a40', + GRAY_900: '#212529', + BLACK: '#000000', + RED_000: '#fff5f5', + RED_100: '#ffe3e3', + RED_200: '#ffc9c9', + RED_300: '#ffa8a8', + RED_400: '#ff8787', + RED_500: '#ff6b6b', + RED_600: '#fa5252', + RED_700: '#f03e3e', + RED_800: '#e03131', + RED_900: '#c92a2a', + ORANGE_500: '#EE6C4C', + BLUE_500: '#88B6B9', +}; + +const flex = { + row: css` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + `, + column: css` + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + `, +}; + +const theme: Theme = { + colors, + flex, +}; + +export default theme; diff --git a/frontend/src/utils/date.ts b/frontend/src/utils/date.ts new file mode 100644 index 00000000..4b071421 --- /dev/null +++ b/frontend/src/utils/date.ts @@ -0,0 +1,165 @@ +import { CalendarType } from '@/@types/calendar'; + +import { DATE_TIME } from '@/constants/date'; + +import { zeroFill } from '.'; + +const checkAllDay = (startDateTime: string | undefined, endDateTime: string | undefined) => { + if (startDateTime === undefined || endDateTime === undefined) { + return null; + } + + return startDateTime.includes(DATE_TIME.START) && endDateTime.includes(DATE_TIME.END); +}; + +const getBeforeDate = (targetDay: Date, offset: number) => + new Date(targetDay.setDate(targetDay.getDate() - offset)); + +const getBeforeYearMonth = (targetYear: number, targetMonth: number) => { + if (targetMonth === 1) { + return { year: targetYear - 1, month: 12 }; + } + + return { year: targetYear, month: targetMonth - 1 }; +}; + +const getCalendarMonth = (year: number, month: number) => { + const firstDate = new Date(year, month - 1, 1); + + const calendarInfo: Date[] = []; + + while (firstDate.getMonth() === month - 1) { + calendarInfo.push(new Date(firstDate)); + firstDate.setDate(firstDate.getDate() + 1); + } + + const firstDay = calendarInfo[0].getDay(); + const lastDay = calendarInfo[calendarInfo.length - 1].getDay(); + + if (firstDay !== 0) { + Array(firstDay) + .fill(0) + .forEach((_, idx) => { + calendarInfo.unshift(getBeforeDate(new Date(year, month - 1, 1), idx + 1)); + }); + } + + if (lastDay !== 6) { + Array(6 - lastDay) + .fill(0) + .forEach((_, idx) => { + calendarInfo.push(getNextDate(new Date(year, month, 0), idx + 1)); + }); + } + + return calendarInfo.map((el) => { + return { + year: el.getFullYear(), + month: el.getMonth() + 1, + date: el.getDate(), + day: el.getDay(), + }; + }); +}; + +const getDate = (dateInfo: Omit | null) => { + if (dateInfo === null) { + return getISODateString(new Date(+new Date() + 3240 * 10000).toISOString()); + } + + const { year, month, date } = dateInfo; + + return getISODateString(new Date(+new Date(year, month - 1, date) + 3240 * 10000).toISOString()); +}; + +const getDateTime = (dateInfo: Omit | null) => { + if (dateInfo === null) { + return new Date(+new Date() + 3240 * 10000).toISOString().replace(/\..*/, '').slice(0, -3); + } + + const { year, month, date } = dateInfo; + + return new Date(+new Date(year, month - 1, date) + 3240 * 10000) + .toISOString() + .replace(/\..*/, '') + .slice(0, -3); +}; + +const getDayFromFormattedDate = (date: string) => { + return new Date(date).getDay(); +}; + +const getFormattedDate = (year: number | string, month: number | string, date: number | string) => { + return `${year}-${zeroFill(month.toString())}-${zeroFill(date.toString())}`; +}; + +const getISODateString = (ISOString: string) => { + return ISOString.split('T')[0]; +}; + +const getKoreaISOString = (time: number) => { + return new Date(time - new Date().getTimezoneOffset() * 60000).toISOString(); +}; + +const getNextDate = (targetDay: Date, offset: number) => + new Date(targetDay.setDate(targetDay.getDate() + offset)); + +const getNextYearMonth = (targetYear: number, targetMonth: number) => { + if (targetMonth === 12) { + return { year: targetYear + 1, month: 1 }; + } + + return { year: targetYear, month: targetMonth + 1 }; +}; + +const getOneHourEarlierISOString = (ISOString: string) => { + const hour = ISOString.split('T')[1].split(':')[0]; + + const oneHourEarlierISOString = getKoreaISOString(new Date(ISOString).setHours(Number(hour) - 1)) + .replace(/\..*/, '') + .slice(0, -3); + + return oneHourEarlierISOString; +}; + +const getOneHourLaterISOString = (ISOString: string) => { + const hour = ISOString.split('T')[1].split(':')[0]; + + const oneHourEarlierISOString = getKoreaISOString(new Date(ISOString).setHours(Number(hour) + 1)) + .replace(/\..*/, '') + .slice(0, -3); + + return oneHourEarlierISOString; +}; + +const getThisDate = () => { + return new Date().getDate(); +}; + +const getThisMonth = () => { + return new Date().getMonth() + 1; +}; + +const getThisYear = () => { + return new Date().getFullYear(); +}; + +export { + checkAllDay, + getBeforeDate, + getBeforeYearMonth, + getCalendarMonth, + getDate, + getDateTime, + getDayFromFormattedDate, + getFormattedDate, + getISODateString, + getKoreaISOString, + getNextDate, + getNextYearMonth, + getOneHourEarlierISOString, + getOneHourLaterISOString, + getThisDate, + getThisMonth, + getThisYear, +}; diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts new file mode 100644 index 00000000..cd5e7f3e --- /dev/null +++ b/frontend/src/utils/index.ts @@ -0,0 +1,34 @@ +import { InputRefType } from '@/@types'; + +const createPostBody = (inputRef: InputRefType) => { + const inputElements = Object.values(inputRef).map((el) => el.current); + const isValidInputRefs = inputElements.every((el) => el instanceof HTMLInputElement); + + if (!isValidInputRefs) { + return; + } + + const inputRefKeys = Object.keys(inputRef); + + const body = inputRefKeys.reduce((acc: any, cur: number | string) => { + acc[cur] = (inputRef[cur].current as HTMLInputElement).value; + + return acc; + }, {}); + + return body; +}; + +const getRandomNumber = (min: number, max: number) => { + return Math.floor(Math.random() * (max - min)) + min; +}; + +const getSearchParam = (key: string) => { + return new URLSearchParams(location.search).get(key); +}; + +const zeroFill = (str: string | number) => { + return str.toString().padStart(2, '0'); +}; + +export { createPostBody, getRandomNumber, getSearchParam, zeroFill }; diff --git a/frontend/src/utils/storage.ts b/frontend/src/utils/storage.ts new file mode 100644 index 00000000..52135302 --- /dev/null +++ b/frontend/src/utils/storage.ts @@ -0,0 +1,15 @@ +import { STORAGE_KEY } from '@/constants'; + +const getAccessToken = () => { + return localStorage.getItem(STORAGE_KEY.ACCESS_TOKEN); +}; + +const setAccessToken = (accessToken: string) => { + localStorage.setItem(STORAGE_KEY.ACCESS_TOKEN, accessToken); +}; + +const removeAccessToken = () => { + localStorage.removeItem(STORAGE_KEY.ACCESS_TOKEN); +}; + +export { getAccessToken, removeAccessToken, setAccessToken }; diff --git a/frontend/src/validation/index.ts b/frontend/src/validation/index.ts new file mode 100644 index 00000000..a4fe9a0f --- /dev/null +++ b/frontend/src/validation/index.ts @@ -0,0 +1,17 @@ +const validateLength = (target: string, min: number, max: number) => { + return min <= target.length && target.length <= max; +}; + +const validateNotEmpty = (target: string) => { + return target.length > 0; +}; + +const validateNotEqualString = (target: string, comparisonTarget: string) => { + return target.trim() !== comparisonTarget; +}; + +const validateStartEndDateTime = (startDate: string, endDate: string) => { + return startDate <= endDate; +}; + +export { validateLength, validateNotEmpty, validateNotEqualString, validateStartEndDateTime }; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..eaf372ba --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "esnext", + "module": "ESNext", + "jsx": "react-jsx", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "jsxImportSource": "@emotion/react", + "moduleResolution": "node", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } + }, + "exclude": ["node_modules"], + "include": ["src"] +} diff --git a/frontend/webpack.config.js b/frontend/webpack.config.js new file mode 100644 index 00000000..bf706698 --- /dev/null +++ b/frontend/webpack.config.js @@ -0,0 +1,62 @@ +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const path = require('path'); +const webpack = require('webpack'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const Dotenv = require('dotenv-webpack'); + +const prod = process.env.NODE_ENV === 'production'; + +module.exports = { + mode: prod ? 'production' : 'development', + devtool: prod ? 'hidden-source-map' : 'eval', + entry: './src/index.tsx', + resolve: { + alias: { + '@': path.resolve(__dirname, './src/'), + }, + extensions: ['.js', '.jsx', '.ts', '.tsx'], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: ['babel-loader', 'ts-loader'], + }, + { + test: /\\.css$/, + use: ['style-loader', 'css-loader'], + }, + { + test: /\.(png|jp(e*)g|gif)$/, + use: [ + { + loader: 'file-loader', + options: { + name: 'images/[hash]-[name].[ext]', + }, + }, + ], + }, + ], + }, + output: { + path: path.join(__dirname, '/dist'), + filename: 'bundle.js', + }, + plugins: [ + new webpack.ProvidePlugin({ + React: 'react', + }), + new HtmlWebpackPlugin({ + template: './src/index.html', + favicon: './src/assets/dallog_color.png', + }), + new CleanWebpackPlugin(), + new Dotenv(), + ], + devServer: { + historyApiFallback: true, + port: 3000, + hot: true, + }, +}; diff --git a/frontend/yarn.lock b/frontend/yarn.lock new file mode 100644 index 00000000..7589f939 --- /dev/null +++ b/frontend/yarn.lock @@ -0,0 +1,13329 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.1.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.0.tgz#56c133824780de3174aed5ab6834f3026790154d" + integrity sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w== + dependencies: + "@jridgewell/gen-mapping" "^0.1.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.18.6", "@babel/code-frame@^7.5.5", "@babel/code-frame@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" + integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + dependencies: + "@babel/highlight" "^7.18.6" + +"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.6.tgz#8b37d24e88e8e21c499d4328db80577d8882fa53" + integrity sha512-tzulrgDT0QD6U7BJ4TKVk2SDDg7wlP39P9yAx1RfLy7vP/7rsDRlWVfbWxElslu56+r7QOhB2NSDsabYYruoZQ== + +"@babel/compat-data@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" + integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== + +"@babel/core@7.12.9": + version "7.12.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.12.9.tgz#fd450c4ec10cdbb980e2928b7aa7a28484593fc8" + integrity sha512-gTXYh3M5wb7FRXQy+FErKFAv90BnlOuNn1QkCK2lREoPAjrQCO49+HVSrFoe5uakFAF5eenS75KbO2vQiLrTMQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.12.5" + "@babel/helper-module-transforms" "^7.12.1" + "@babel/helpers" "^7.12.5" + "@babel/parser" "^7.12.7" + "@babel/template" "^7.12.7" + "@babel/traverse" "^7.12.9" + "@babel/types" "^7.12.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.1" + json5 "^2.1.2" + lodash "^4.17.19" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/core@7.13.10": + version "7.13.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559" + integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.9" + "@babel/helper-compilation-targets" "^7.13.10" + "@babel/helper-module-transforms" "^7.13.0" + "@babel/helpers" "^7.13.10" + "@babel/parser" "^7.13.10" + "@babel/template" "^7.12.13" + "@babel/traverse" "^7.13.0" + "@babel/types" "^7.13.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + lodash "^4.17.19" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/core@^7.1.0", "@babel/core@^7.12.10", "@babel/core@^7.12.3", "@babel/core@^7.18.6", "@babel/core@^7.7.5": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" + integrity sha512-cQbWBpxcbbs/IUredIPkHiAGULLV8iwgNRMFzvbhEXISp4f3rUUXE5+TIw6KwUWUR3DwyI6gmBRnmAtYaWehwQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helpers" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/core@^7.11.6": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.10.tgz#39ad504991d77f1f3da91be0b8b949a5bc466fb8" + integrity sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-compilation-targets" "^7.18.9" + "@babel/helper-module-transforms" "^7.18.9" + "@babel/helpers" "^7.18.9" + "@babel/parser" "^7.18.10" + "@babel/template" "^7.18.10" + "@babel/traverse" "^7.18.10" + "@babel/types" "^7.18.10" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/generator@7.13.9": + version "7.13.9" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39" + integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw== + dependencies: + "@babel/types" "^7.13.0" + jsesc "^2.5.1" + source-map "^0.5.0" + +"@babel/generator@^7.12.11", "@babel/generator@^7.12.5", "@babel/generator@^7.13.0", "@babel/generator@^7.13.9", "@babel/generator@^7.18.6", "@babel/generator@^7.18.7": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.7.tgz#2aa78da3c05aadfc82dbac16c99552fc802284bd" + integrity sha512-shck+7VLlY72a2w9c3zYWuE1pwOKEiQHV7GTUbSnhyl5eu3i04t30tBY82ZRWrDfo3gkakCFtevExnxbkf2a3A== + dependencies: + "@babel/types" "^7.18.7" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/generator@^7.18.10", "@babel/generator@^7.7.2": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.18.10.tgz#794f328bfabdcbaf0ebf9bf91b5b57b61fa77a2a" + integrity sha512-0+sW7e3HjQbiHbj1NeU/vN8ornohYlacAfZIaXhdoGweQqgcNy69COVciYYqEXJ/v+9OBA7Frxm4CVAuNqKeNA== + dependencies: + "@babel/types" "^7.18.10" + "@jridgewell/gen-mapping" "^0.3.2" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" + integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.6.tgz#f14d640ed1ee9246fb33b8255f08353acfe70e6a" + integrity sha512-KT10c1oWEpmrIRYnthbzHgoOf6B+Xd6a5yhdbNtdhtG7aO1or5HViuf1TQR36xY/QprXA5nvxO6nAjhJ4y38jw== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.6.tgz#18d35bfb9f83b1293c22c55b3d576c1315b6ed96" + integrity sha512-vFjbfhNCzqdeAtZflUFrG5YIFqGTqsctrtkZ1D/NB0mDW9TwW3GmmUepYY4G9wCET5rY5ugz4OGTcLd614IzQg== + dependencies: + "@babel/compat-data" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-compilation-targets@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.18.9.tgz#69e64f57b524cde3e5ff6cc5a9f4a387ee5563bf" + integrity sha512-tzLCyVmqUiFlcFoAPLA/gL9TeYrF61VLNtb+hvkuVaB5SUjW7jcfrglBIX1vUIoT7CLP3bBlIMeyEsIl2eFQNg== + dependencies: + "@babel/compat-data" "^7.18.8" + "@babel/helper-validator-option" "^7.18.6" + browserslist "^4.20.2" + semver "^6.3.0" + +"@babel/helper-create-class-features-plugin@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz#6f15f8459f3b523b39e00a99982e2c040871ed72" + integrity sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-member-expression-to-functions" "^7.18.6" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + +"@babel/helper-create-regexp-features-plugin@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.18.6.tgz#3e35f4e04acbbf25f1b3534a657610a000543d3c" + integrity sha512-7LcpH1wnQLGrI+4v+nPp+zUvIkF9x0ddv1Hkdue10tg3gmRnLy97DXh4STiOf1qeIInyD69Qv5kKSZzKD8B/7A== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + regexpu-core "^5.1.0" + +"@babel/helper-define-polyfill-provider@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e" + integrity sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-define-polyfill-provider@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.1.tgz#52411b445bdb2e676869e5a74960d2d3826d2665" + integrity sha512-J9hGMpJQmtWmj46B3kBHmL38UhJGhYX7eqkcq+2gsstyYt341HmPeWspihX43yVRA0mS+8GGk2Gckc7bY/HCmA== + dependencies: + "@babel/helper-compilation-targets" "^7.13.0" + "@babel/helper-module-imports" "^7.12.13" + "@babel/helper-plugin-utils" "^7.13.0" + "@babel/traverse" "^7.13.0" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + semver "^6.1.2" + +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + +"@babel/helper-environment-visitor@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be" + integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg== + +"@babel/helper-explode-assignable-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096" + integrity sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.12.13", "@babel/helper-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.6.tgz#8334fecb0afba66e6d87a7e8c6bb7fed79926b83" + integrity sha512-0mWMxV1aC97dhjCah5U5Ua7668r5ZmSC2DLfH2EZnf9c3/dHZKiFa5pRLMH5tjSl471tY6496ZWk/kjNONBxhw== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-function-name@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.18.9.tgz#940e6084a55dee867d33b4e487da2676365e86b0" + integrity sha512-fJgWlZt7nxGksJS9a0XdSaI4XvpExnNIgRP+rVefWh5U7BL8pPuir6SJUmFKRfjWQ51OtWSzwOxhaH/EBWWc0A== + dependencies: + "@babel/template" "^7.18.6" + "@babel/types" "^7.18.9" + +"@babel/helper-hoist-variables@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678" + integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-member-expression-to-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz#44802d7d602c285e1692db0bad9396d007be2afc" + integrity sha512-CeHxqwwipekotzPDUuJOfIMtcIHBuc7WAzLmTYWctVigqS5RktNMQ5bEwQSuGewzYnCtTWa3BARXeiLxDTv+Ng== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.12.1", "@babel/helper-module-transforms@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.6.tgz#57e3ca669e273d55c3cda55e6ebf552f37f483c8" + integrity sha512-L//phhB4al5uucwzlimruukHB3jRd5JGClwRMD/ROrVjXfLqovYnvQrK/JK36WYyVwGGO7OD3kMyVTjx+WVPhw== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-module-transforms@^7.13.0": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.8.tgz#4f8408afead0188cfa48672f9d0e5787b61778c8" + integrity sha512-che3jvZwIcZxrwh63VfnFTUzcAM9v/lznYkkRxIBGMPt1SudOKHAEec0SIRCfiuIzTcF7VGj/CaTT6gY4eWxvA== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.8" + "@babel/types" "^7.18.8" + +"@babel/helper-module-transforms@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.18.9.tgz#5a1079c005135ed627442df31a42887e80fcb712" + integrity sha512-KYNqY0ICwfv19b31XzvmI/mfcylOzbLtowkw+mfvGPAQ3kfCnMLYbED3YecL5tPd8nAYFQFAd6JHp2LxZk/J1g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/helper-optimise-call-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" + integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-plugin-utils@7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz#2f75a831269d4f677de49986dff59927533cf375" + integrity sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg== + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + +"@babel/helper-remap-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.6.tgz#fa1f81acd19daee9d73de297c0308783cd3cfc23" + integrity sha512-z5wbmV55TveUPZlCLZvxWHtrjuJd+8inFhk7DG0WW87/oJuGDcjDiu7HIvGcpf5464L6xKCg3vNkmlVVz9hwyQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-wrap-function" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-replace-supers@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz#efedf51cfccea7b7b8c0f00002ab317e7abfe420" + integrity sha512-fTf7zoXnUGl9gF25fXCWE26t7Tvtyn6H4hkLSYhATwJvw2uYxd3aoXplMSe0g9XbwK7bmxNes7+FGO0rB/xC0g== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-member-expression-to-functions" "^7.18.6" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helper-simple-access@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea" + integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-skip-transparent-expression-wrappers@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.6.tgz#7dff00a5320ca4cf63270e5a0eca4b268b7380d9" + integrity sha512-4KoLhwGS9vGethZpAhYnMejWkX64wsnHPDwvOsKWU6Fg4+AlK2Jz3TyjQLMEPvz+1zemi/WBdkYxCD0bAfIkiw== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-split-export-declaration@^7.12.13", "@babel/helper-split-export-declaration@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075" + integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-string-parser@^7.18.10": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56" + integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw== + +"@babel/helper-validator-identifier@^7.12.11", "@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-option@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8" + integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw== + +"@babel/helper-wrap-function@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz#ec44ea4ad9d8988b90c3e465ba2382f4de81a073" + integrity sha512-I5/LZfozwMNbwr/b1vhhuYD+J/mU+gfGAj5td7l5Rv9WYmH6i3Om69WGKNmlIpsVW/mF6O5bvTKbvDQZVgjqOw== + dependencies: + "@babel/helper-function-name" "^7.18.6" + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helpers@^7.12.5", "@babel/helpers@^7.13.10", "@babel/helpers@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.6.tgz#4c966140eaa1fcaa3d5a8c09d7db61077d4debfd" + integrity sha512-vzSiiqbQOghPngUYt/zWGvK3LAsPhz55vc9XNN0xAl2gV4ieShI2OQli5duxWHD+72PZPTKAcfcZDE1Cwc5zsQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/helpers@^7.18.9": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.18.9.tgz#4bef3b893f253a1eced04516824ede94dcfe7ff9" + integrity sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ== + dependencies: + "@babel/template" "^7.18.6" + "@babel/traverse" "^7.18.9" + "@babel/types" "^7.18.9" + +"@babel/highlight@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" + integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@7.14.6": + version "7.14.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.6.tgz#d85cc68ca3cac84eae384c06f032921f5227f4b2" + integrity sha512-oG0ej7efjEXxb4UgE+klVx+3j4MVo+A2vCzm7OUN4CLo6WhQ+vSOD2yJ8m7B+DghObxtLxt3EfgMWpq+AsWehQ== + +"@babel/parser@^7.1.0", "@babel/parser@^7.18.10", "@babel/parser@^7.18.11": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" + integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== + +"@babel/parser@^7.12.11", "@babel/parser@^7.12.7", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.6.tgz#845338edecad65ebffef058d3be851f1d28a63bc" + integrity sha512-uQVSa9jJUe/G/304lXspfWVpKpK4euFLgGiMQFOCpM/bgcAdeoHwi/OQz23O9GK2osz26ZiXRRV9aV+Yl1O8tw== + +"@babel/parser@^7.13.0", "@babel/parser@^7.13.10", "@babel/parser@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf" + integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA== + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" + integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.6.tgz#b4e4dbc2cd1acd0133479918f7c6412961c9adb8" + integrity sha512-Udgu8ZRgrBrttVz6A0EVL0SJ1z+RLbIeqsu632SA1hf0awEppD6TvdznoH+orIF8wtFFAV/Enmw9Y+9oV8TQcw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.6" + +"@babel/plugin-proposal-async-generator-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.18.6.tgz#aedac81e6fc12bb643374656dd5f2605bf743d17" + integrity sha512-WAz4R9bvozx4qwf74M+sfqPMKfSqwM0phxPTR6iJIi8robgzXwkEgmeJG1gEKhm6sDqT/U9aV3lfcqybIpev8w== + dependencies: + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-proposal-class-properties@^7.12.1", "@babel/plugin-proposal-class-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" + integrity sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-class-static-block@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz#8aa81d403ab72d3962fc06c26e222dacfc9b9020" + integrity sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-proposal-decorators@^7.12.12": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.18.6.tgz#68e9fd0f022b944f84a8824bb28bfaee724d2595" + integrity sha512-gAdhsjaYmiZVxx5vTMiRfj31nB7LhwBJFMSLzeDxc7X4tKLixup0+k9ughn0RcpBrv9E3PBaXJW7jF5TCihAOg== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/plugin-syntax-decorators" "^7.18.6" + +"@babel/plugin-proposal-dynamic-import@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz#72bcf8d408799f547d759298c3c27c7e7faa4d94" + integrity sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-proposal-export-default-from@^7.12.1": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.6.tgz#121b3ba0152d0020865bc86271c8150e5115abc7" + integrity sha512-oTvzWB16T9cB4j5kX8c8DuUHo/4QtR2P9vnUNKed9xqFP8Jos/IRniz1FiIryn6luDYoltDJSYF7RCpbm2doMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-export-default-from" "^7.18.6" + +"@babel/plugin-proposal-export-namespace-from@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.6.tgz#1016f0aa5ab383bbf8b3a85a2dcaedf6c8ee7491" + integrity sha512-zr/QcUlUo7GPo6+X1wC98NJADqmy5QTFWWhqeQWiki4XHafJtLl/YMGkmRB2szDD2IYJCCdBTd4ElwhId9T7Xw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-proposal-json-strings@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz#7e8788c1811c393aff762817e7dbf1ebd0c05f0b" + integrity sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-proposal-logical-assignment-operators@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.6.tgz#3b9cac6f1ffc2aa459d111df80c12020dfc6b665" + integrity sha512-zMo66azZth/0tVd7gmkxOkOjs2rpHyhpcFo565PUP37hSp6hSd9uUKIfTDFMz58BwqgQKhJ9YxtM5XddjXVn+Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-proposal-nullish-coalescing-operator@^7.12.1", "@babel/plugin-proposal-nullish-coalescing-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz#fdd940a99a740e577d6c753ab6fbb43fdb9467e1" + integrity sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-proposal-numeric-separator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz#899b14fbafe87f053d2c5ff05b36029c62e13c75" + integrity sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-proposal-object-rest-spread@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz#def9bd03cea0f9b72283dac0ec22d289c7691069" + integrity sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.0" + "@babel/plugin-transform-parameters" "^7.12.1" + +"@babel/plugin-proposal-object-rest-spread@^7.12.1", "@babel/plugin-proposal-object-rest-spread@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.6.tgz#ec93bba06bfb3e15ebd7da73e953d84b094d5daf" + integrity sha512-9yuM6wr4rIsKa1wlUAbZEazkCrgw2sMPEXCr4Rnwetu7cEW1NydkCWytLuYletbf8vFxdJxFhwEZqMpOx2eZyw== + dependencies: + "@babel/compat-data" "^7.18.6" + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.18.6" + +"@babel/plugin-proposal-optional-catch-binding@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz#f9400d0e6a3ea93ba9ef70b09e72dd6da638a2cb" + integrity sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-proposal-optional-chaining@^7.12.7", "@babel/plugin-proposal-optional-chaining@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.6.tgz#46d4f2ffc20e87fad1d98bc4fa5d466366f6aa0b" + integrity sha512-PatI6elL5eMzoypFAiYDpYQyMtXTn+iMhuxxQt5mAXD4fEmKorpSI3PHd+i3JXBJN3xyA6MvJv7at23HffFHwA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-proposal-private-methods@^7.12.1", "@babel/plugin-proposal-private-methods@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" + integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-proposal-private-property-in-object@^7.12.1", "@babel/plugin-proposal-private-property-in-object@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503" + integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e" + integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-bigint@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" + integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13", "@babel/plugin-syntax-class-properties@^7.8.3": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-decorators@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.18.6.tgz#2e45af22835d0b0f8665da2bfd4463649ce5dbc1" + integrity sha512-fqyLgjcxf/1yhyZ6A+yo1u9gJ7eleFQod2lkaUsF9DQ7sbbY3Ligym3L0+I2c0WmqNKDpoD9UTb1AKP3qRMOAQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-default-from@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz#8df076711a4818c4ce4f23e61d622b0ba2ff84bc" + integrity sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-flow@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz#774d825256f2379d06139be0c723c4dd444f3ca1" + integrity sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-assertions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4" + integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-jsx@7.12.1": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.12.1.tgz#9d9d357cc818aa7ae7935917c1257f67677a0926" + integrity sha512-1yRi7yAtB0ETgxdY9ti/p2TivUxJkTdhu/ZbF9MshVGqOx1TdB3b7xCXs49Fupgg50N45KcAsRP/ZqWjs9SRjg== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.18.6", "@babel/plugin-syntax-jsx@^7.2.0": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0" + integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4", "@babel/plugin-syntax-numeric-separator@^7.8.3": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@7.8.3", "@babel/plugin-syntax-object-rest-spread@^7.8.0", "@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5", "@babel/plugin-syntax-top-level-await@^7.8.3": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-typescript@^7.18.6", "@babel/plugin-syntax-typescript@^7.7.2": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz#1c09cd25795c7c2b8a4ba9ae49394576d4133285" + integrity sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.12.1", "@babel/plugin-transform-arrow-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz#19063fcf8771ec7b31d742339dac62433d0611fe" + integrity sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-async-to-generator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz#ccda3d1ab9d5ced5265fdb13f1882d5476c71615" + integrity sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag== + dependencies: + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-remap-async-to-generator" "^7.18.6" + +"@babel/plugin-transform-block-scoped-functions@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8" + integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.6.tgz#b5f78318914615397d86a731ef2cc668796a726c" + integrity sha512-pRqwb91C42vs1ahSAWJkxOxU1RHWDn16XAa6ggQ72wjLlWyYeAcLvTtE0aM8ph3KNydy9CQF2nLYcjq1WysgxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-classes@^7.12.1", "@babel/plugin-transform-classes@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.18.6.tgz#3501a8f3f4c7d5697c27a3eedbee71d68312669f" + integrity sha512-XTg8XW/mKpzAF3actL554Jl/dOYoJtv3l8fxaEczpgz84IeeVf+T1u2CSvPHuZbt0w3JkIx4rdn/MRQI7mo0HQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-optimise-call-expression" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.6.tgz#5d15eb90e22e69604f3348344c91165c5395d032" + integrity sha512-9repI4BhNrR0KenoR9vm3/cIc1tSBIo+u1WVjKCAynahj25O8zfbiE6JtAtHPGQSs4yZ+bA8mRasRP+qc+2R5A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-destructuring@^7.12.1", "@babel/plugin-transform-destructuring@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.6.tgz#a98b0e42c7ffbf5eefcbcf33280430f230895c6f" + integrity sha512-tgy3u6lRp17ilY8r1kP4i2+HDUwxlVqq3RTc943eAWSzGgpU1qhiKpqZ5CMyHReIYPHdo3Kg8v8edKtDqSVEyQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8" + integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-duplicate-keys@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.6.tgz#e6c94e8cd3c9dd8a88144f7b78ae22975a7ff473" + integrity sha512-NJU26U/208+sxYszf82nmGYqVF9QN8py2HFTblPT9hbawi8+1C5a9JubODLTGFuT0qlkqVinmkwOD13s0sZktg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-exponentiation-operator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd" + integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-flow-strip-types@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.6.tgz#6d3dd9f9c0fe13349428569fef00b31310bb3f9f" + integrity sha512-wE0xtA7csz+hw4fKPwxmu5jnzAsXPIO57XnRwzXP3T19jWh1BODnPGoG9xKYwvAwusP7iUktHayRFbMPGtODaQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-flow" "^7.18.6" + +"@babel/plugin-transform-for-of@^7.12.1", "@babel/plugin-transform-for-of@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.6.tgz#e0fdb813be908e91ccc9ec87b30cc2eabf046f7c" + integrity sha512-WAjoMf4wIiSsy88KmG7tgj2nFdEK7E46tArVtcgED7Bkj6Fg/tG5SbvNIOKxbFS2VFgNh6+iaPswBeQZm4ox8w== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-function-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.6.tgz#6a7e4ae2893d336fd1b8f64c9f92276391d0f1b4" + integrity sha512-kJha/Gbs5RjzIu0CxZwf5e3aTTSlhZnHMT8zPWnJMjNpLOUgqevg+PN5oMH68nMCXnfiMo4Bhgxqj59KHTlAnA== + dependencies: + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.6.tgz#9d6af353b5209df72960baf4492722d56f39a205" + integrity sha512-x3HEw0cJZVDoENXOp20HlypIHfl0zMIhMVZEBVTfmqbObIpsMxMbmU5nOEO8R7LYT+z5RORKPlTI5Hj4OsO9/Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-member-expression-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e" + integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-modules-amd@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21" + integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" + integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-simple-access" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-systemjs@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.18.6.tgz#026511b7657d63bf5d4cf2fd4aeb963139914a54" + integrity sha512-UbPYpXxLjTw6w6yXX2BYNxF3p6QY225wcTkfQCy3OMnSlS/C3xGtwUjEzGkldb/sy6PWLiCQ3NbYfjWUTI3t4g== + dependencies: + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-identifier" "^7.18.6" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-umd@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9" + integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ== + dependencies: + "@babel/helper-module-transforms" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.18.6.tgz#c89bfbc7cc6805d692f3a49bc5fc1b630007246d" + integrity sha512-UmEOGF8XgaIqD74bC8g7iV3RYj8lMf0Bw7NJzvnS9qQhM4mg+1WHKotUIdjxgD2RGrgFLZZPCFPFj3P/kVDYhg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-new-target@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz#d128f376ae200477f37c4ddfcc722a8a1b3246a8" + integrity sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-object-super@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c" + integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-replace-supers" "^7.18.6" + +"@babel/plugin-transform-parameters@^7.12.1", "@babel/plugin-transform-parameters@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.6.tgz#cbe03d5a4c6385dd756034ac1baa63c04beab8dc" + integrity sha512-FjdqgMv37yVl/gwvzkcB+wfjRI8HQmc5EgOG9iGNvUY1ok+TjsoaMP7IqCDZBhkFcM5f3OPVMs6Dmp03C5k4/A== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-property-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3" + integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-display-name@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415" + integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-react-jsx-development@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5" + integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.18.6" + +"@babel/plugin-transform-react-jsx@^7.12.1", "@babel/plugin-transform-react-jsx@^7.12.12", "@babel/plugin-transform-react-jsx@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.18.6.tgz#2721e96d31df96e3b7ad48ff446995d26bc028ff" + integrity sha512-Mz7xMPxoy9kPS/JScj6fJs03TZ/fZ1dJPlMjRAgTaxaS0fUBk8FV/A2rRgfPsVCZqALNwMexD+0Uaf5zlcKPpw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-module-imports" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-jsx" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/plugin-transform-react-pure-annotations@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844" + integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-regenerator@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73" + integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + regenerator-transform "^0.15.0" + +"@babel/plugin-transform-reserved-words@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a" + integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-shorthand-properties@^7.12.1", "@babel/plugin-transform-shorthand-properties@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" + integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-spread@^7.12.1", "@babel/plugin-transform-spread@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.18.6.tgz#82b080241965f1689f0a60ecc6f1f6575dbdb9d6" + integrity sha512-ayT53rT/ENF8WWexIRg9AiV9h0aIteyWn5ptfZTZQrjk/+f3WdrJGCY4c9wcgl2+MKkKPhzbYp97FTsquZpDCw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-skip-transparent-expression-wrappers" "^7.18.6" + +"@babel/plugin-transform-sticky-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc" + integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-template-literals@^7.12.1", "@babel/plugin-transform-template-literals@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.6.tgz#b763f4dc9d11a7cce58cf9a490d82e80547db9c2" + integrity sha512-UuqlRrQmT2SWRvahW46cGSany0uTlcj8NYOS5sRGYi8FxPYPoLd5DDmMd32ZXEj2Jq+06uGVQKHxa/hJx2EzKw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-typeof-symbol@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.6.tgz#486bb39d5a18047358e0d04dc0d2f322f0b92e92" + integrity sha512-7m71iS/QhsPk85xSjFPovHPcH3H9qeyzsujhTc+vcdnsXavoWYJ74zx0lP5RhpC5+iDnVLO+PPMHzC11qels1g== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.6.tgz#8f4ade1a9cf253e5cf7c7c20173082c2c08a50a7" + integrity sha512-ijHNhzIrLj5lQCnI6aaNVRtGVuUZhOXFLRVFs7lLrkXTHip4FKty5oAuQdk4tywG0/WjXmjTfQCWmuzrvFer1w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/plugin-syntax-typescript" "^7.18.6" + +"@babel/plugin-transform-unicode-escapes@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.6.tgz#0d01fb7fb2243ae1c033f65f6e3b4be78db75f27" + integrity sha512-XNRwQUXYMP7VLuy54cr/KS/WeL3AZeORhrmeZ7iewgu+X2eBqmpaLI/hzqr9ZxCeUoq0ASK4GUzSM0BDhZkLFw== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-unicode-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca" + integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/preset-env@^7.12.11", "@babel/preset-env@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.18.6.tgz#953422e98a5f66bc56cd0b9074eaea127ec86ace" + integrity sha512-WrthhuIIYKrEFAwttYzgRNQ5hULGmwTj+D6l7Zdfsv5M7IWV/OZbUfbeL++Qrzx1nVJwWROIFhCHRYQV4xbPNw== + dependencies: + "@babel/compat-data" "^7.18.6" + "@babel/helper-compilation-targets" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.6" + "@babel/plugin-proposal-async-generator-functions" "^7.18.6" + "@babel/plugin-proposal-class-properties" "^7.18.6" + "@babel/plugin-proposal-class-static-block" "^7.18.6" + "@babel/plugin-proposal-dynamic-import" "^7.18.6" + "@babel/plugin-proposal-export-namespace-from" "^7.18.6" + "@babel/plugin-proposal-json-strings" "^7.18.6" + "@babel/plugin-proposal-logical-assignment-operators" "^7.18.6" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6" + "@babel/plugin-proposal-numeric-separator" "^7.18.6" + "@babel/plugin-proposal-object-rest-spread" "^7.18.6" + "@babel/plugin-proposal-optional-catch-binding" "^7.18.6" + "@babel/plugin-proposal-optional-chaining" "^7.18.6" + "@babel/plugin-proposal-private-methods" "^7.18.6" + "@babel/plugin-proposal-private-property-in-object" "^7.18.6" + "@babel/plugin-proposal-unicode-property-regex" "^7.18.6" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.18.6" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-transform-arrow-functions" "^7.18.6" + "@babel/plugin-transform-async-to-generator" "^7.18.6" + "@babel/plugin-transform-block-scoped-functions" "^7.18.6" + "@babel/plugin-transform-block-scoping" "^7.18.6" + "@babel/plugin-transform-classes" "^7.18.6" + "@babel/plugin-transform-computed-properties" "^7.18.6" + "@babel/plugin-transform-destructuring" "^7.18.6" + "@babel/plugin-transform-dotall-regex" "^7.18.6" + "@babel/plugin-transform-duplicate-keys" "^7.18.6" + "@babel/plugin-transform-exponentiation-operator" "^7.18.6" + "@babel/plugin-transform-for-of" "^7.18.6" + "@babel/plugin-transform-function-name" "^7.18.6" + "@babel/plugin-transform-literals" "^7.18.6" + "@babel/plugin-transform-member-expression-literals" "^7.18.6" + "@babel/plugin-transform-modules-amd" "^7.18.6" + "@babel/plugin-transform-modules-commonjs" "^7.18.6" + "@babel/plugin-transform-modules-systemjs" "^7.18.6" + "@babel/plugin-transform-modules-umd" "^7.18.6" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.18.6" + "@babel/plugin-transform-new-target" "^7.18.6" + "@babel/plugin-transform-object-super" "^7.18.6" + "@babel/plugin-transform-parameters" "^7.18.6" + "@babel/plugin-transform-property-literals" "^7.18.6" + "@babel/plugin-transform-regenerator" "^7.18.6" + "@babel/plugin-transform-reserved-words" "^7.18.6" + "@babel/plugin-transform-shorthand-properties" "^7.18.6" + "@babel/plugin-transform-spread" "^7.18.6" + "@babel/plugin-transform-sticky-regex" "^7.18.6" + "@babel/plugin-transform-template-literals" "^7.18.6" + "@babel/plugin-transform-typeof-symbol" "^7.18.6" + "@babel/plugin-transform-unicode-escapes" "^7.18.6" + "@babel/plugin-transform-unicode-regex" "^7.18.6" + "@babel/preset-modules" "^0.1.5" + "@babel/types" "^7.18.6" + babel-plugin-polyfill-corejs2 "^0.3.1" + babel-plugin-polyfill-corejs3 "^0.5.2" + babel-plugin-polyfill-regenerator "^0.3.1" + core-js-compat "^3.22.1" + semver "^6.3.0" + +"@babel/preset-flow@^7.12.1": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.18.6.tgz#83f7602ba566e72a9918beefafef8ef16d2810cb" + integrity sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-flow-strip-types" "^7.18.6" + +"@babel/preset-modules@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9" + integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.4.4" + "@babel/plugin-transform-dotall-regex" "^7.4.4" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/preset-react@^7.12.10", "@babel/preset-react@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d" + integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-react-display-name" "^7.18.6" + "@babel/plugin-transform-react-jsx" "^7.18.6" + "@babel/plugin-transform-react-jsx-development" "^7.18.6" + "@babel/plugin-transform-react-pure-annotations" "^7.18.6" + +"@babel/preset-typescript@^7.12.7", "@babel/preset-typescript@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz#ce64be3e63eddc44240c6358daefac17b3186399" + integrity sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ== + dependencies: + "@babel/helper-plugin-utils" "^7.18.6" + "@babel/helper-validator-option" "^7.18.6" + "@babel/plugin-transform-typescript" "^7.18.6" + +"@babel/register@^7.12.1": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/register/-/register-7.18.6.tgz#48a4520f1b2a7d7ac861e8148caeb0cefe6c59db" + integrity sha512-tkYtONzaO8rQubZzpBnvZPFcHgh8D9F55IjOsYton4X2IBoyRn2ZSWQqySTZnUn2guZbxbQiAB27hJEbvXamhQ== + dependencies: + clone-deep "^4.0.1" + find-cache-dir "^2.0.0" + make-dir "^2.1.0" + pirates "^4.0.5" + source-map-support "^0.5.16" + +"@babel/runtime@7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.2.tgz#111a78002a5c25fc8e3361bedc9529c696b85a6a" + integrity sha512-JONRbXbTXc9WQE2mAZd1p0Z3DZ/6vaQIkgYMSTP3KjRCyd7rCZCcfhCyX+YjwcKxcZ82UrxbRD358bpExNgrjw== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/runtime@^7.0.0", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.6.tgz#6a1ef59f838debd670421f8c7f2cbb8da9751580" + integrity sha512-t9wi7/AW6XtKahAe20Yw0/mMljKq0B1r2fPdvaAdV/KPDZewFXdaaa6K7lxmZBZ8FBNpCiAT6iHPmd6QO9bKfQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@~7.5.4": + version "7.5.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" + integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== + dependencies: + regenerator-runtime "^0.13.2" + +"@babel/template@^7.12.13", "@babel/template@^7.12.7", "@babel/template@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/template@^7.18.10", "@babel/template@^7.3.3": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.10.tgz#6f9134835970d1dbf0835c0d100c9f38de0c5e71" + integrity sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.10" + "@babel/types" "^7.18.10" + +"@babel/traverse@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc" + integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ== + dependencies: + "@babel/code-frame" "^7.12.13" + "@babel/generator" "^7.13.0" + "@babel/helper-function-name" "^7.12.13" + "@babel/helper-split-export-declaration" "^7.12.13" + "@babel/parser" "^7.13.0" + "@babel/types" "^7.13.0" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.19" + +"@babel/traverse@^7.1.6", "@babel/traverse@^7.12.11", "@babel/traverse@^7.12.9", "@babel/traverse@^7.13.0", "@babel/traverse@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.6.tgz#a228562d2f46e89258efa4ddd0416942e2fd671d" + integrity sha512-zS/OKyqmD7lslOtFqbscH6gMLFYOfG1YPqCKfAW5KrTeolKqvB8UelR49Fpr6y93kYkW2Ik00mT1LOGiAGvizw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.6" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.18.10", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2": + version "7.18.11" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.11.tgz#3d51f2afbd83ecf9912bcbb5c4d94e3d2ddaa16f" + integrity sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.10" + "@babel/helper-environment-visitor" "^7.18.9" + "@babel/helper-function-name" "^7.18.9" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.11" + "@babel/types" "^7.18.10" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/traverse@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.8.tgz#f095e62ab46abf1da35e5a2011f43aee72d8d5b0" + integrity sha512-UNg/AcSySJYR/+mIcJQDCv00T+AqRO7j/ZEJLzpaYtgM48rMg5MnkJgyNqkzo88+p4tfRvZJCEiwwfG6h4jkRg== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.18.7" + "@babel/helper-environment-visitor" "^7.18.6" + "@babel/helper-function-name" "^7.18.6" + "@babel/helper-hoist-variables" "^7.18.6" + "@babel/helper-split-export-declaration" "^7.18.6" + "@babel/parser" "^7.18.8" + "@babel/types" "^7.18.8" + debug "^4.1.0" + globals "^11.1.0" + +"@babel/types@7.13.0": + version "7.13.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80" + integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA== + dependencies: + "@babel/helper-validator-identifier" "^7.12.11" + lodash "^4.17.19" + to-fast-properties "^2.0.0" + +"@babel/types@^7.0.0", "@babel/types@^7.18.10", "@babel/types@^7.18.9", "@babel/types@^7.3.0", "@babel/types@^7.3.3": + version "7.18.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.10.tgz#4908e81b6b339ca7c6b7a555a5fc29446f26dde6" + integrity sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ== + dependencies: + "@babel/helper-string-parser" "^7.18.10" + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.12.11", "@babel/types@^7.12.7", "@babel/types@^7.18.6", "@babel/types@^7.18.7", "@babel/types@^7.2.0", "@babel/types@^7.4.4": + version "7.18.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.7.tgz#a4a2c910c15040ea52cdd1ddb1614a65c8041726" + integrity sha512-QG3yxTcTIBoAcQmkCs+wAPYZhu7Dk9rXKacINfNbdJDNERTbLQbHGyVG8q/YGMPeCJRIhSY0+fTc5+xuh6WPSQ== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.13.0", "@babel/types@^7.18.8": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f" + integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@base2/pretty-print-object@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.1.tgz#371ba8be66d556812dc7fb169ebc3c08378f69d4" + integrity sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA== + +"@bcoe/v8-coverage@^0.2.3": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" + integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== + +"@cnakazawa/watch@^1.0.3": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" + integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== + dependencies: + exec-sh "^0.3.2" + minimist "^1.2.0" + +"@colors/colors@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9" + integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ== + +"@design-systems/utils@2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@design-systems/utils/-/utils-2.12.0.tgz#955c108be07cb8f01532207cbfea8f848fa760c9" + integrity sha512-Y/d2Zzr+JJfN6u1gbuBUb1ufBuLMJJRZQk+dRmw8GaTpqKx5uf7cGUYGTwN02dIb3I+Tf+cW8jcGBTRiFxdYFg== + dependencies: + "@babel/runtime" "^7.11.2" + clsx "^1.0.4" + focus-lock "^0.8.0" + react-merge-refs "^1.0.0" + +"@devtools-ds/object-inspector@^1.1.2": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@devtools-ds/object-inspector/-/object-inspector-1.2.0.tgz#64a132fbd4159affa5a87c8cf6cf8540c337aed2" + integrity sha512-VztcwqVwScSvYdvJVZBJYsVO/2Pew3JPpFV3T9fuCHQLlHcLYOV3aU/kBS2ScuE2O1JN0ZbobLqFLa3vQF54Fw== + dependencies: + "@babel/runtime" "7.7.2" + "@devtools-ds/object-parser" "^1.2.0" + "@devtools-ds/themes" "^1.2.0" + "@devtools-ds/tree" "^1.2.0" + clsx "1.1.0" + +"@devtools-ds/object-parser@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@devtools-ds/object-parser/-/object-parser-1.2.0.tgz#8da39bf481687afdf113c78dbac5ced6fd8e30d1" + integrity sha512-SjGGyiFFY8dtUpiWXAvRSzRT+hE11EAAysrq2PsC/GVLf2ZLyT2nHlQO5kDStywyTz+fjw7S7pyDRj1HG9YTTA== + dependencies: + "@babel/runtime" "~7.5.4" + +"@devtools-ds/themes@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@devtools-ds/themes/-/themes-1.2.0.tgz#2fda60af9741e97bc09257b512e49a7aecf6f4bc" + integrity sha512-LimEITorE6yWZWWuMc6OiBfLQgPrQqWbyMEmfRUDPa3PHXoAY4SpDxczfg31fgyRDUNWnZhjaJH5bBbu8VEbIw== + dependencies: + "@babel/runtime" "~7.5.4" + "@design-systems/utils" "2.12.0" + clsx "1.1.0" + +"@devtools-ds/tree@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@devtools-ds/tree/-/tree-1.2.0.tgz#e882d10ae13a30f2aa02e75c3eeb6c44a47a80c3" + integrity sha512-hC4g4ocuo2eg7jsnzKdauxH0sDQiPW3KSM2+uK3kRgcmr9PzpBD5Kob+Y/WFSVKswFleftOGKL4BQLuRv0sPxA== + dependencies: + "@babel/runtime" "7.7.2" + "@devtools-ds/themes" "^1.2.0" + clsx "1.1.0" + +"@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@emotion/babel-plugin-jsx-pragmatic@^0.1.5": + version "0.1.5" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin-jsx-pragmatic/-/babel-plugin-jsx-pragmatic-0.1.5.tgz#27debfe9c27c4d83574d509787ae553bf8a34d7e" + integrity sha512-y+3AJ0SItMDaAgGPVkQBC/S/BaqaPACkQ6MyCI2CUlrjTxKttTVfD3TMtcs7vLEcLxqzZ1xiG0vzwCXjhopawQ== + dependencies: + "@babel/plugin-syntax-jsx" "^7.2.0" + +"@emotion/babel-plugin@^11.2.0", "@emotion/babel-plugin@^11.7.1", "@emotion/babel-plugin@^11.9.2": + version "11.9.2" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" + integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/runtime" "^7.13.10" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.2" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.0.13" + +"@emotion/babel-preset-css-prop@^11.2.0": + version "11.2.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-preset-css-prop/-/babel-preset-css-prop-11.2.0.tgz#c7e945f56b2610b438f0dc8ae5253fc55488de0e" + integrity sha512-9XLQm2eLPYTho+Cx1LQTDA1rATjoAaB4O+ds55XDvoAa+Z16Hhg8y5Vihj3C8E6+ilDM8SV5A9Z6z+yj0YIRBg== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.12.1" + "@babel/runtime" "^7.7.2" + "@emotion/babel-plugin" "^11.2.0" + "@emotion/babel-plugin-jsx-pragmatic" "^0.1.5" + +"@emotion/cache@^11.9.3": + version "11.9.3" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.9.3.tgz#96638449f6929fd18062cfe04d79b29b44c0d6cb" + integrity sha512-0dgkI/JKlCXa+lEXviaMtGBL0ynpx4osh7rjOXE71q9bIF8G+XhJgvi+wDu0B0IdCVx37BffiwXlN9I3UuzFvg== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.1" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + +"@emotion/hash@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" + integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== + +"@emotion/is-prop-valid@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.1.3.tgz#f0907a416368cf8df9e410117068e20fe87c0a3a" + integrity sha512-RFg04p6C+1uO19uG8N+vqanzKqiM9eeV1LDOG3bmkYmuOj7NbKNlFC/4EZq5gnwAIlcC/jOT24f8Td0iax2SXA== + dependencies: + "@emotion/memoize" "^0.7.4" + +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.9.3": + version "11.9.3" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.3.tgz#f4f4f34444f6654a2e550f5dab4f2d360c101df9" + integrity sha512-g9Q1GcTOlzOEjqwuLF/Zd9LC+4FljjPjDfxSM7KmEakm+hsHXk+bYZ2q+/hTJzr0OUNkujo72pXLQvXj6H+GJQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/cache" "^11.9.3" + "@emotion/serialize" "^1.0.4" + "@emotion/utils" "^1.1.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.0.2", "@emotion/serialize@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.4.tgz#ff31fd11bb07999611199c2229e152faadc21a3c" + integrity sha512-1JHamSpH8PIfFwAMryO2bNka+y8+KA5yga5Ocf2d7ZEiJjb7xlLW7aknBGZqJLajuLOvJ+72vN+IBSwPlXD1Pg== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + +"@emotion/sheet@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.1.tgz#015756e2a9a3c7c5f11d8ec22966a8dbfbfac787" + integrity sha512-J3YPccVRMiTZxYAY0IOq3kd+hUP8idY8Kz6B/Cyo+JuXq52Ek+zbPbSQUrVQp95aJ+lsAW7DPL1P2Z+U1jGkKA== + +"@emotion/styled@^11.9.3": + version "11.9.3" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.9.3.tgz#47f0c71137fec7c57035bf3659b52fb536792340" + integrity sha512-o3sBNwbtoVz9v7WB1/Y/AmXl69YHmei2mrVnK7JgyBJ//Rst5yqPZCecEJlMlJrFeWHp+ki/54uN265V2pEcXA== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/is-prop-valid" "^1.1.3" + "@emotion/serialize" "^1.0.4" + "@emotion/utils" "^1.1.0" + +"@emotion/unitless@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" + integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== + +"@emotion/utils@^1.0.0", "@emotion/utils@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf" + integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ== + +"@emotion/weak-memoize@^0.2.5": + version "0.2.5" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" + integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== + +"@eslint/eslintrc@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" + integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== + dependencies: + ajv "^6.12.4" + debug "^4.3.2" + espree "^9.3.2" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.2.1" + js-yaml "^4.1.0" + minimatch "^3.1.2" + strip-json-comments "^3.1.1" + +"@gar/promisify@^1.0.1": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" + integrity sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw== + +"@humanwhocodes/config-array@^0.9.2": + version "0.9.5" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.5.tgz#2cbaf9a89460da24b5ca6531b8bbfc23e1df50c7" + integrity sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw== + dependencies: + "@humanwhocodes/object-schema" "^1.2.1" + debug "^4.1.1" + minimatch "^3.0.4" + +"@humanwhocodes/object-schema@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" + integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== + +"@istanbuljs/load-nyc-config@^1.0.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" + integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== + dependencies: + camelcase "^5.3.1" + find-up "^4.1.0" + get-package-type "^0.1.0" + js-yaml "^3.13.1" + resolve-from "^5.0.0" + +"@istanbuljs/schema@^0.1.2", "@istanbuljs/schema@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" + integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== + +"@jest/console@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/console/-/console-28.1.3.tgz#2030606ec03a18c31803b8a36382762e447655df" + integrity sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + +"@jest/core@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/core/-/core-28.1.3.tgz#0ebf2bd39840f1233cd5f2d1e6fc8b71bd5a1ac7" + integrity sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/reporters" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + ci-info "^3.2.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + jest-changed-files "^28.1.3" + jest-config "^28.1.3" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-resolve-dependencies "^28.1.3" + jest-runner "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + jest-watcher "^28.1.3" + micromatch "^4.0.4" + pretty-format "^28.1.3" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + +"@jest/environment@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-28.1.3.tgz#abed43a6b040a4c24fdcb69eab1f97589b2d663e" + integrity sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA== + dependencies: + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + +"@jest/expect-utils@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect-utils/-/expect-utils-28.1.3.tgz#58561ce5db7cd253a7edddbc051fb39dda50f525" + integrity sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA== + dependencies: + jest-get-type "^28.0.2" + +"@jest/expect@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/expect/-/expect-28.1.3.tgz#9ac57e1d4491baca550f6bdbd232487177ad6a72" + integrity sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw== + dependencies: + expect "^28.1.3" + jest-snapshot "^28.1.3" + +"@jest/fake-timers@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-28.1.3.tgz#230255b3ad0a3d4978f1d06f70685baea91c640e" + integrity sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw== + dependencies: + "@jest/types" "^28.1.3" + "@sinonjs/fake-timers" "^9.1.2" + "@types/node" "*" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +"@jest/globals@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-28.1.3.tgz#a601d78ddc5fdef542728309894895b4a42dc333" + integrity sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/types" "^28.1.3" + +"@jest/reporters@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-28.1.3.tgz#9adf6d265edafc5fc4a434cfb31e2df5a67a369a" + integrity sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + "@types/node" "*" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^5.1.0" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + jest-worker "^28.1.3" + slash "^3.0.0" + string-length "^4.0.1" + strip-ansi "^6.0.0" + terminal-link "^2.0.0" + v8-to-istanbul "^9.0.1" + +"@jest/schemas@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-28.1.3.tgz#ad8b86a66f11f33619e3d7e1dcddd7f2d40ff905" + integrity sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg== + dependencies: + "@sinclair/typebox" "^0.24.1" + +"@jest/source-map@^28.1.2": + version "28.1.2" + resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-28.1.2.tgz#7fe832b172b497d6663cdff6c13b0a920e139e24" + integrity sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww== + dependencies: + "@jridgewell/trace-mapping" "^0.3.13" + callsites "^3.0.0" + graceful-fs "^4.2.9" + +"@jest/test-result@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-28.1.3.tgz#5eae945fd9f4b8fcfce74d239e6f725b6bf076c5" + integrity sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg== + dependencies: + "@jest/console" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + +"@jest/test-sequencer@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz#9d0c283d906ac599c74bde464bc0d7e6a82886c3" + integrity sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw== + dependencies: + "@jest/test-result" "^28.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + slash "^3.0.0" + +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + +"@jest/transform@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-28.1.3.tgz#59d8098e50ab07950e0f2fc0fc7ec462371281b0" + integrity sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA== + dependencies: + "@babel/core" "^7.11.6" + "@jest/types" "^28.1.3" + "@jridgewell/trace-mapping" "^0.3.13" + babel-plugin-istanbul "^6.1.1" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + micromatch "^4.0.4" + pirates "^4.0.4" + slash "^3.0.0" + write-file-atomic "^4.0.1" + +"@jest/types@^26.6.2": + version "26.6.2" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" + integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + +"@jest/types@^27.5.1": + version "27.5.1" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80" + integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^16.0.0" + chalk "^4.0.0" + +"@jest/types@^28.1.3": + version "28.1.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-28.1.3.tgz#b05de80996ff12512bc5ceb1d208285a7d11748b" + integrity sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ== + dependencies: + "@jest/schemas" "^28.1.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + +"@jridgewell/gen-mapping@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz#e5d2e450306a9491e3bd77e323e38d7aff315996" + integrity sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w== + dependencies: + "@jridgewell/set-array" "^1.0.0" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" + integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== + dependencies: + "@jridgewell/set-array" "^1.0.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/resolve-uri@^3.0.3": + version "3.0.8" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz#687cc2bbf243f4e9a868ecf2262318e2658873a1" + integrity sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w== + +"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" + integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== + +"@jridgewell/source-map@^0.3.2": + version "0.3.2" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" + integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.0" + "@jridgewell/trace-mapping" "^0.3.9" + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.13", "@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9": + version "0.3.14" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed" + integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + +"@leichtgewicht/ip-codec@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" + integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== + +"@mdx-js/mdx@^1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba" + integrity sha512-AMxuLxPz2j5/6TpF/XSdKpQP1NlG0z11dFOlq+2IP/lSgl11GY8ji6S/rgsViN/L0BDvHvUMruRb7ub+24LUYA== + dependencies: + "@babel/core" "7.12.9" + "@babel/plugin-syntax-jsx" "7.12.1" + "@babel/plugin-syntax-object-rest-spread" "7.8.3" + "@mdx-js/util" "1.6.22" + babel-plugin-apply-mdx-type-prop "1.6.22" + babel-plugin-extract-import-names "1.6.22" + camelcase-css "2.0.1" + detab "2.0.4" + hast-util-raw "6.0.1" + lodash.uniq "4.5.0" + mdast-util-to-hast "10.0.1" + remark-footnotes "2.0.0" + remark-mdx "1.6.22" + remark-parse "8.0.3" + remark-squeeze-paragraphs "4.0.0" + style-to-object "0.3.0" + unified "9.2.0" + unist-builder "2.0.3" + unist-util-visit "2.0.3" + +"@mdx-js/react@^1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/react/-/react-1.6.22.tgz#ae09b4744fddc74714ee9f9d6f17a66e77c43573" + integrity sha512-TDoPum4SHdfPiGSAaRBw7ECyI8VaHpK8GJugbJIJuqyh6kzw9ZLJZW3HGL3NNrJGxcAixUvqROm+YuQOo5eXtg== + +"@mdx-js/util@1.6.22": + version "1.6.22" + resolved "https://registry.yarnpkg.com/@mdx-js/util/-/util-1.6.22.tgz#219dfd89ae5b97a8801f015323ffa4b62f45718b" + integrity sha512-H1rQc1ZOHANWBvPcW+JpGwr+juXSxM8Q8YCkm3GhZd8REu1fHR3z99CErO1p9pkcfcxZnMdIZdIsXkOHY0NilA== + +"@mrmlnc/readdir-enhanced@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" + integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== + dependencies: + call-me-maybe "^1.0.1" + glob-to-regexp "^0.3.0" + +"@mswjs/cookies@^0.2.0": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.2.1.tgz#66a283b45970ffc5350d22657983a1377ea6bf97" + integrity sha512-0tDfcPw5/s7QsNQqS3knAvAD5w5PF1nNPagRhKO/yECY+sMbJxoC2sLWnH7Lzmh52mTSVLKDhd1r92Q3kfljnQ== + dependencies: + "@types/set-cookie-parser" "^2.4.0" + set-cookie-parser "^2.4.6" + +"@mswjs/interceptors@^0.16.3": + version "0.16.6" + resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.16.6.tgz#c1a777ed3f69b55bbbc725b2deb827f160c0107c" + integrity sha512-7ax1sRx5s4ZWl0KvVhhcPOUoPbCCkVh8M8hYaqOyvoAQOiqLVzy+Z6Mh2ywPhYw4zudr5Mo/E8UT/zJBO/Wxrw== + dependencies: + "@open-draft/until" "^1.0.3" + "@xmldom/xmldom" "^0.7.5" + debug "^4.3.3" + headers-polyfill "^3.0.4" + outvariant "^1.2.1" + strict-event-emitter "^0.2.4" + +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== + dependencies: + "@nodelib/fs.stat" "2.0.5" + run-parallel "^1.1.9" + +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== + +"@nodelib/fs.stat@^1.1.2": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" + integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== + +"@nodelib/fs.walk@^1.2.3": + version "1.2.8" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" + integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== + dependencies: + "@nodelib/fs.scandir" "2.1.5" + fastq "^1.6.0" + +"@npmcli/fs@^1.0.0": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-1.1.1.tgz#72f719fe935e687c56a4faecf3c03d06ba593257" + integrity sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ== + dependencies: + "@gar/promisify" "^1.0.1" + semver "^7.3.5" + +"@npmcli/move-file@^1.0.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@npmcli/move-file/-/move-file-1.1.2.tgz#1a82c3e372f7cae9253eb66d72543d6b8685c674" + integrity sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg== + dependencies: + mkdirp "^1.0.4" + rimraf "^3.0.2" + +"@open-draft/until@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" + integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== + +"@pmmmwh/react-refresh-webpack-plugin@^0.5.3": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz#58f8217ba70069cc6a73f5d7e05e85b458c150e2" + integrity sha512-bcKCAzF0DV2IIROp9ZHkRJa6O4jy7NlnHdWL3GmcUxYWNjLXkK5kfELELwEfSP5hXPfVL/qOGMAROuMQb9GG8Q== + dependencies: + ansi-html-community "^0.0.8" + common-path-prefix "^3.0.0" + core-js-pure "^3.8.1" + error-stack-parser "^2.0.6" + find-up "^5.0.0" + html-entities "^2.1.0" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + source-map "^0.7.3" + +"@sinclair/typebox@^0.24.1": + version "0.24.27" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.27.tgz#d55643516a1546174e10da681a8aaa81e757452d" + integrity sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg== + +"@sinonjs/commons@^1.7.0": + version "1.8.3" + resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" + integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== + dependencies: + type-detect "4.0.8" + +"@sinonjs/fake-timers@^9.1.2": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz#4eaab737fab77332ab132d396a3c0d364bd0ea8c" + integrity sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw== + dependencies: + "@sinonjs/commons" "^1.7.0" + +"@storybook/addon-actions@6.5.9", "@storybook/addon-actions@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-6.5.9.tgz#d50d65631403e1a5b680961429d9c0d7bd383e68" + integrity sha512-wDYm3M1bN+zcYZV3Q24M03b/P8DDpvj1oSoY6VLlxDAi56h8qZB/voeIS2I6vWXOB79C5tbwljYNQO0GsufS0g== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + polished "^4.2.2" + prop-types "^15.7.2" + react-inspector "^5.1.0" + regenerator-runtime "^0.13.7" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + uuid-browser "^3.1.0" + +"@storybook/addon-backgrounds@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-backgrounds/-/addon-backgrounds-6.5.9.tgz#a9579fc9d73f783a768c6c6ceb97193c5a1ee708" + integrity sha512-9k+GiY5aiANLOct34ar29jqgdi5ZpCqpZ86zPH0GsEC6ifH6nzP4trLU0vFUe260XDCvB4g8YaI7JZKPhozERg== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/addon-controls@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-controls/-/addon-controls-6.5.9.tgz#8f6ef939c87b3dbad98f8bda7e124f0b34f668d2" + integrity sha512-VvjkgK32bGURKyWU2No6Q2B0RQZjLZk8D3neVNCnrWxwrl1G82StegxjRPn/UZm9+MZVPvTvI46nj1VdgOktnw== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/node-logger" "6.5.9" + "@storybook/store" "6.5.9" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + lodash "^4.17.21" + ts-dedent "^2.0.0" + +"@storybook/addon-docs@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-docs/-/addon-docs-6.5.9.tgz#32b27fb298624afd738c1371a764d7ff4831fe6d" + integrity sha512-9lwOZyiOJFUgGd9ADVfcgpels5o0XOXqGMeVLuzT1160nopbZjNjo/3+YLJ0pyHRPpMJ4rmq2+vxRQR6PVRgPg== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.12.12" + "@babel/preset-env" "^7.12.11" + "@jest/transform" "^26.6.2" + "@mdx-js/react" "^1.6.22" + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/docs-tools" "6.5.9" + "@storybook/mdx1-csf" "^0.0.1" + "@storybook/node-logger" "6.5.9" + "@storybook/postinstall" "6.5.9" + "@storybook/preview-web" "6.5.9" + "@storybook/source-loader" "6.5.9" + "@storybook/store" "6.5.9" + "@storybook/theming" "6.5.9" + babel-loader "^8.0.0" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + regenerator-runtime "^0.13.7" + remark-external-links "^8.0.0" + remark-slug "^6.0.0" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/addon-essentials@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-essentials/-/addon-essentials-6.5.9.tgz#32ba63acba4d153f4cf6ac33cbbf14b87d260788" + integrity sha512-V9ThjKQsde4A2Es20pLFBsn0MWx2KCJuoTcTsANP4JDcbvEmj8UjbDWbs8jAU+yzJT5r+CI6NoWmQudv12ZOgw== + dependencies: + "@storybook/addon-actions" "6.5.9" + "@storybook/addon-backgrounds" "6.5.9" + "@storybook/addon-controls" "6.5.9" + "@storybook/addon-docs" "6.5.9" + "@storybook/addon-measure" "6.5.9" + "@storybook/addon-outline" "6.5.9" + "@storybook/addon-toolbars" "6.5.9" + "@storybook/addon-viewport" "6.5.9" + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/node-logger" "6.5.9" + core-js "^3.8.2" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + +"@storybook/addon-interactions@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-interactions/-/addon-interactions-6.5.9.tgz#4356e96beae8f44000955d8870c3acc6c8d1fb3a" + integrity sha512-p3xBbrhmYTHvRO8MqAIr2DucgrXt38nJE71rogLNLsJ01rUN4JsLI8OkQAMQbqfIpwC27irMjQxJTp4HSzkFJA== + dependencies: + "@devtools-ds/object-inspector" "^1.1.2" + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/instrumenter" "6.5.9" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + jest-mock "^27.0.6" + polished "^4.2.2" + ts-dedent "^2.2.0" + +"@storybook/addon-links@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-links/-/addon-links-6.5.9.tgz#91cbca0c044796badf2498723fdd10dacea5748b" + integrity sha512-4BYC7pkxL3NLRnEgTA9jpIkObQKril+XFj1WtmY/lngF90vvK0Kc/TtvTA2/5tSgrHfxEuPevIdxMIyLJ4ejWQ== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.9" + "@types/qs" "^6.9.5" + core-js "^3.8.2" + global "^4.4.0" + prop-types "^15.7.2" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + +"@storybook/addon-measure@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-measure/-/addon-measure-6.5.9.tgz#f949d4f5f4025c839634114365f1399ea04bd0ae" + integrity sha512-0aA22wD0CIEUccsEbWkckCOXOwr4VffofMH1ToVCOeqBoyLOMB0gxFubESeprqM54CWsYh2DN1uujgD6508cwA== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + core-js "^3.8.2" + global "^4.4.0" + +"@storybook/addon-outline@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-outline/-/addon-outline-6.5.9.tgz#6ce9b3fb77e6a1a59607d7657c359c69f26cf6dd" + integrity sha512-oJ1DK3BDJr6aTlZc9axfOxV1oDkZO7hOohgUQDaKO1RZrSpyQsx2ViK2X6p/W7JhFJHKh7rv+nGCaVlLz3YIZA== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + +"@storybook/addon-toolbars@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-toolbars/-/addon-toolbars-6.5.9.tgz#feedfdac08482d43bb1f3cc00840d80322c5eace" + integrity sha512-6JFQNHYVZUwp17p5rppc+iQJ2QOIWPTF+ni1GMMThjc84mzXs2+899Sf1aPFTvrFJTklmT+bPX6x4aUTouVa1w== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + regenerator-runtime "^0.13.7" + +"@storybook/addon-viewport@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addon-viewport/-/addon-viewport-6.5.9.tgz#fc390ccebea56d2e874ed2fda085c09fe04dd240" + integrity sha512-thKS+iw6M7ueDQQ7M66STZ5rgtJKliAcIX6UCopo0Ffh4RaRYmX6MCjqtvBKk8joyXUvm9SpWQemJD9uBQrjgw== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + memoizerific "^1.11.3" + prop-types "^15.7.2" + regenerator-runtime "^0.13.7" + +"@storybook/addons@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.5.9.tgz#5a9d7395c579a9cbc44dfc122362fb3c95dfb9d5" + integrity sha512-adwdiXg+mntfPocLc1KXjZXyLgGk7Aac699Fwe+OUYPEC5tW347Rm/kFatcE556d42o5czcRiq3ZSIGWnm9ieQ== + dependencies: + "@storybook/api" "6.5.9" + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.9" + "@storybook/theming" "6.5.9" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + +"@storybook/api@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.5.9.tgz#303733214c9de0422d162f7c54ae05d088b89bf9" + integrity sha512-9ylztnty4Y+ALU/ehW3BML9czjCAFsWvrwuCi6UgcwNjswwjSX3VRLhfD1KT3pl16ho//95LgZ0LnSwROCcPOA== + dependencies: + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/router" "6.5.9" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/builder-webpack4@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.5.9.tgz#4b37e1fa23a25aa4bfeaba640e5d318fcd511f95" + integrity sha512-YOeA4++9uRZ8Hog1wC60yjaxBOiI1FRQNtax7b9E7g+kP8UlSCPCGcv4gls9hFmzbzTOPfQTWnToA9Oa6jzRVw== + dependencies: + "@babel/core" "^7.12.10" + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/channel-postmessage" "6.5.9" + "@storybook/channels" "6.5.9" + "@storybook/client-api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/preview-web" "6.5.9" + "@storybook/router" "6.5.9" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.5.9" + "@storybook/theming" "6.5.9" + "@storybook/ui" "6.5.9" + "@types/node" "^14.0.10 || ^16.0.0" + "@types/webpack" "^4.41.26" + autoprefixer "^9.8.6" + babel-loader "^8.0.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + core-js "^3.8.2" + css-loader "^3.6.0" + file-loader "^6.2.0" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^4.1.6" + glob "^7.1.6" + glob-promise "^3.4.0" + global "^4.4.0" + html-webpack-plugin "^4.0.0" + pnp-webpack-plugin "1.6.4" + postcss "^7.0.36" + postcss-flexbugs-fixes "^4.2.1" + postcss-loader "^4.2.0" + raw-loader "^4.0.2" + stable "^0.1.8" + style-loader "^1.3.0" + terser-webpack-plugin "^4.2.3" + ts-dedent "^2.0.0" + url-loader "^4.1.1" + util-deprecate "^1.0.2" + webpack "4" + webpack-dev-middleware "^3.7.3" + webpack-filter-warnings-plugin "^1.2.1" + webpack-hot-middleware "^2.25.1" + webpack-virtual-modules "^0.2.2" + +"@storybook/builder-webpack5@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.5.9.tgz#30b4e08622daff104bcccd015d3ee7902f99dd99" + integrity sha512-NUVZ4Qci6HWPuoH8U/zQkdBO5soGgu7QYrGC/LWU0tRfmmZxkjr7IUU14ppDpGPYgx3r7jkaQI1J/E1YEmSCWQ== + dependencies: + "@babel/core" "^7.12.10" + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/channel-postmessage" "6.5.9" + "@storybook/channels" "6.5.9" + "@storybook/client-api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/preview-web" "6.5.9" + "@storybook/router" "6.5.9" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.5.9" + "@storybook/theming" "6.5.9" + "@types/node" "^14.0.10 || ^16.0.0" + babel-loader "^8.0.0" + babel-plugin-named-exports-order "^0.0.2" + browser-assert "^1.2.1" + case-sensitive-paths-webpack-plugin "^2.3.0" + core-js "^3.8.2" + css-loader "^5.0.1" + fork-ts-checker-webpack-plugin "^6.0.4" + glob "^7.1.6" + glob-promise "^3.4.0" + html-webpack-plugin "^5.0.0" + path-browserify "^1.0.1" + process "^0.11.10" + stable "^0.1.8" + style-loader "^2.0.0" + terser-webpack-plugin "^5.0.3" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack "^5.9.0" + webpack-dev-middleware "^4.1.0" + webpack-hot-middleware "^2.25.1" + webpack-virtual-modules "^0.4.1" + +"@storybook/channel-postmessage@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.5.9.tgz#9cf4530f0364cee0d5e58f92d6fb5ce98e10257b" + integrity sha512-pX/0R8UW7ezBhCrafRaL20OvMRcmESYvQQCDgjqSzJyHkcG51GOhsd6Ge93eJ6QvRMm9+w0Zs93N2VKjVtz0Qw== + dependencies: + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + qs "^6.10.0" + telejson "^6.0.8" + +"@storybook/channel-websocket@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.5.9.tgz#6b7a0127fec58ee5be4f6aebcf460adc564f2f34" + integrity sha512-xtHvSNwuOhkgALwVshKWsoFhDmuvcosdYfxcfFGEiYKXIu46tRS5ZXmpmgEC/0JAVkVoFj5nL8bV7IY5np6oaA== + dependencies: + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + telejson "^6.0.8" + +"@storybook/channels@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.5.9.tgz#abfab89a6587a2688e9926d4aafeb11c9d8b2e79" + integrity sha512-FvGA35nV38UPXWOl9ERapFTJaxwSTamQ339s2Ev7E9riyRG+GRkgTWzf5kECJgS1PAYKd/7m/RqKJT9BVv6A5g== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/client-api@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.5.9.tgz#3e4a8ec1d277fd81325c5d959c553161a85fa182" + integrity sha512-pc7JKJoWLesixUKvG2nV36HukUuYoGRyAgD3PpIV7qSBS4JixqZ3VAHFUtqV1UzfOSQTovLSl4a0rIRnpie6gA== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/channel-postmessage" "6.5.9" + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.9" + "@types/qs" "^6.9.5" + "@types/webpack-env" "^1.16.0" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/client-logger@6.5.9", "@storybook/client-logger@^6.4.0": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.5.9.tgz#dc1669abe8c45af1cc38f74c6f4b15ff33e63014" + integrity sha512-DOHL6p0uiDd3gV/Sb2FR+Vh6OiPrrf8BrA06uvXWsMRIIvEEvnparxv9EvPg7FlmUX0T3nq7d3juwjx4F8Wbcg== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + +"@storybook/components@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.5.9.tgz#97e07ffe11ab76c01ccee380888991bd161f75b2" + integrity sha512-BhfX980O9zn/1J4FNMeDo8ZvL1m5Ml3T4HRpfYmEBnf8oW5b5BeF6S2K2cwFStZRjWqm1feUcwNpZxCBVMkQnQ== + dependencies: + "@storybook/client-logger" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/theming" "6.5.9" + "@types/react-syntax-highlighter" "11.0.5" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + react-syntax-highlighter "^15.4.5" + regenerator-runtime "^0.13.7" + util-deprecate "^1.0.2" + +"@storybook/core-client@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.5.9.tgz#ea6035d1c90d2c68e860e3cf629979491856cd88" + integrity sha512-LY0QbhShowO+PQx3gao3wdVjpKMH1AaSLmuI95FrcjoMmSXGf96jVLKQp9mJRGeHIsAa93EQBYuCihZycM3Kbg== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/channel-postmessage" "6.5.9" + "@storybook/channel-websocket" "6.5.9" + "@storybook/client-api" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/preview-web" "6.5.9" + "@storybook/store" "6.5.9" + "@storybook/ui" "6.5.9" + airbnb-js-shims "^2.2.1" + ansi-to-html "^0.6.11" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.21" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + unfetch "^4.2.0" + util-deprecate "^1.0.2" + +"@storybook/core-common@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.5.9.tgz#7ca8258ea2634b1d64695c1e4262f71cc7457989" + integrity sha512-NxOK0mrOCo0TWZ7Npc5HU66EKoRHlrtg18/ZixblLDWQMIqY9XCck8K1kJ8QYpYCHla+aHIsYUArFe2vhlEfZA== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-proposal-class-properties" "^7.12.1" + "@babel/plugin-proposal-decorators" "^7.12.12" + "@babel/plugin-proposal-export-default-from" "^7.12.1" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1" + "@babel/plugin-proposal-object-rest-spread" "^7.12.1" + "@babel/plugin-proposal-optional-chaining" "^7.12.7" + "@babel/plugin-proposal-private-methods" "^7.12.1" + "@babel/plugin-proposal-private-property-in-object" "^7.12.1" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-transform-arrow-functions" "^7.12.1" + "@babel/plugin-transform-block-scoping" "^7.12.12" + "@babel/plugin-transform-classes" "^7.12.1" + "@babel/plugin-transform-destructuring" "^7.12.1" + "@babel/plugin-transform-for-of" "^7.12.1" + "@babel/plugin-transform-parameters" "^7.12.1" + "@babel/plugin-transform-shorthand-properties" "^7.12.1" + "@babel/plugin-transform-spread" "^7.12.1" + "@babel/preset-env" "^7.12.11" + "@babel/preset-react" "^7.12.10" + "@babel/preset-typescript" "^7.12.7" + "@babel/register" "^7.12.1" + "@storybook/node-logger" "6.5.9" + "@storybook/semver" "^7.3.2" + "@types/node" "^14.0.10 || ^16.0.0" + "@types/pretty-hrtime" "^1.0.0" + babel-loader "^8.0.0" + babel-plugin-macros "^3.0.1" + babel-plugin-polyfill-corejs3 "^0.1.0" + chalk "^4.1.0" + core-js "^3.8.2" + express "^4.17.1" + file-system-cache "^1.0.5" + find-up "^5.0.0" + fork-ts-checker-webpack-plugin "^6.0.4" + fs-extra "^9.0.1" + glob "^7.1.6" + handlebars "^4.7.7" + interpret "^2.2.0" + json5 "^2.1.3" + lazy-universal-dotenv "^3.0.1" + picomatch "^2.3.0" + pkg-dir "^5.0.0" + pretty-hrtime "^1.0.3" + resolve-from "^5.0.0" + slash "^3.0.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack "4" + +"@storybook/core-events@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.5.9.tgz#5b0783c7d22a586c0f5e927a61fe1b1223e19637" + integrity sha512-tXt7a3ZvJOCeEKpNa/B5rQM5VI7UJLlOh3IHOImWn4HqoBRrZvbourmac+PRZAtXpos0h3c6554Hjapj/Sny5Q== + dependencies: + core-js "^3.8.2" + +"@storybook/core-server@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.5.9.tgz#749a881c1a81d7cf1a69f3782c06a7f0c39a505c" + integrity sha512-YeePGUrd5fQPvGzMhowh124KrcZURFpFXg1VB0Op3ESqCIsInoMZeObci4Gc+binMXC7vcv7aw3EwSLU37qJzQ== + dependencies: + "@discoveryjs/json-ext" "^0.5.3" + "@storybook/builder-webpack4" "6.5.9" + "@storybook/core-client" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/csf-tools" "6.5.9" + "@storybook/manager-webpack4" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.5.9" + "@storybook/telemetry" "6.5.9" + "@types/node" "^14.0.10 || ^16.0.0" + "@types/node-fetch" "^2.5.7" + "@types/pretty-hrtime" "^1.0.0" + "@types/webpack" "^4.41.26" + better-opn "^2.1.1" + boxen "^5.1.2" + chalk "^4.1.0" + cli-table3 "^0.6.1" + commander "^6.2.1" + compression "^1.7.4" + core-js "^3.8.2" + cpy "^8.1.2" + detect-port "^1.3.0" + express "^4.17.1" + fs-extra "^9.0.1" + global "^4.4.0" + globby "^11.0.2" + ip "^2.0.0" + lodash "^4.17.21" + node-fetch "^2.6.7" + open "^8.4.0" + pretty-hrtime "^1.0.3" + prompts "^2.4.0" + regenerator-runtime "^0.13.7" + serve-favicon "^2.5.0" + slash "^3.0.0" + telejson "^6.0.8" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + watchpack "^2.2.0" + webpack "4" + ws "^8.2.3" + x-default-browser "^0.4.0" + +"@storybook/core@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.5.9.tgz#da4f237391d99aed1228323f24b335cafbdf3499" + integrity sha512-Mt3TTQnjQt2/pa60A+bqDsAOrYpohapdtt4DDZEbS8h0V6u11KyYYh3w7FCySlL+sPEyogj63l5Ec76Jah3l2w== + dependencies: + "@storybook/core-client" "6.5.9" + "@storybook/core-server" "6.5.9" + +"@storybook/csf-tools@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.5.9.tgz#8e01df2305b53e228229f0b45ada3720e6e42a1c" + integrity sha512-RAdhsO2XmEDyWy0qNQvdKMLeIZAuyfD+tYlUwBHRU6DbByDucvwgMOGy5dF97YNJFmyo93EUYJzXjUrJs3U1LQ== + dependencies: + "@babel/core" "^7.12.10" + "@babel/generator" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/plugin-transform-react-jsx" "^7.12.12" + "@babel/preset-env" "^7.12.11" + "@babel/traverse" "^7.12.11" + "@babel/types" "^7.12.11" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/mdx1-csf" "^0.0.1" + core-js "^3.8.2" + fs-extra "^9.0.1" + global "^4.4.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + +"@storybook/csf@0.0.2--canary.4566f4d.1": + version "0.0.2--canary.4566f4d.1" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz#dac52a21c40ef198554e71fe4d20d61e17f65327" + integrity sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ== + dependencies: + lodash "^4.17.15" + +"@storybook/csf@0.0.2--canary.87bc651.0": + version "0.0.2--canary.87bc651.0" + resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44" + integrity sha512-ajk1Uxa+rBpFQHKrCcTmJyQBXZ5slfwHVEaKlkuFaW77it8RgbPJp/ccna3sgoi8oZ7FkkOyvv1Ve4SmwFqRqw== + dependencies: + lodash "^4.17.15" + +"@storybook/docs-tools@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/docs-tools/-/docs-tools-6.5.9.tgz#5ff304f881e972ce14923a5ffcfed3f052094889" + integrity sha512-UoTaXLvec8x+q+4oYIk/t8DBju9C3ZTGklqOxDIt+0kS3TFAqEgI3JhKXqQOXgN5zDcvLVSxi8dbVAeSxk2ktA== + dependencies: + "@babel/core" "^7.12.10" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.9" + core-js "^3.8.2" + doctrine "^3.0.0" + lodash "^4.17.21" + regenerator-runtime "^0.13.7" + +"@storybook/instrumenter@6.5.9", "@storybook/instrumenter@^6.4.0": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/instrumenter/-/instrumenter-6.5.9.tgz#885d9dec31b7b7fa6ea29b446105480450e527b8" + integrity sha512-I2nu/6H0MAy8d+d3LY/G6oYEFyWlc8f2Qs2DhpYh5FiCgIpzvY0DMN05Lf8oaXdKHL3lPF/YLJH17FttekXs1w== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + core-js "^3.8.2" + global "^4.4.0" + +"@storybook/manager-webpack4@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.5.9.tgz#c75d2cced4550c8a786f00b0e57b203d613e706c" + integrity sha512-49LZlHqWc7zj9tQfOOANixPYmLxqWTTZceA6DSXnKd9xDiO2Gl23Y+l/CSPXNZGDB8QFAwpimwqyKJj/NLH45A== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/preset-react" "^7.12.10" + "@storybook/addons" "6.5.9" + "@storybook/core-client" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/theming" "6.5.9" + "@storybook/ui" "6.5.9" + "@types/node" "^14.0.10 || ^16.0.0" + "@types/webpack" "^4.41.26" + babel-loader "^8.0.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + chalk "^4.1.0" + core-js "^3.8.2" + css-loader "^3.6.0" + express "^4.17.1" + file-loader "^6.2.0" + find-up "^5.0.0" + fs-extra "^9.0.1" + html-webpack-plugin "^4.0.0" + node-fetch "^2.6.7" + pnp-webpack-plugin "1.6.4" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + resolve-from "^5.0.0" + style-loader "^1.3.0" + telejson "^6.0.8" + terser-webpack-plugin "^4.2.3" + ts-dedent "^2.0.0" + url-loader "^4.1.1" + util-deprecate "^1.0.2" + webpack "4" + webpack-dev-middleware "^3.7.3" + webpack-virtual-modules "^0.2.2" + +"@storybook/manager-webpack5@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.5.9.tgz#ce9dd6ea6298ab426b111f170c23deea7085ba08" + integrity sha512-J1GamphSsaZLNBEhn1awgxzOS8KfvzrHtVlAm2VHwW7j1E1DItROFJhGCgduYYuBiN9eqm+KIYrxcr6cRuoolQ== + dependencies: + "@babel/core" "^7.12.10" + "@babel/plugin-transform-template-literals" "^7.12.1" + "@babel/preset-react" "^7.12.10" + "@storybook/addons" "6.5.9" + "@storybook/core-client" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/theming" "6.5.9" + "@storybook/ui" "6.5.9" + "@types/node" "^14.0.10 || ^16.0.0" + babel-loader "^8.0.0" + case-sensitive-paths-webpack-plugin "^2.3.0" + chalk "^4.1.0" + core-js "^3.8.2" + css-loader "^5.0.1" + express "^4.17.1" + find-up "^5.0.0" + fs-extra "^9.0.1" + html-webpack-plugin "^5.0.0" + node-fetch "^2.6.7" + process "^0.11.10" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + resolve-from "^5.0.0" + style-loader "^2.0.0" + telejson "^6.0.8" + terser-webpack-plugin "^5.0.3" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack "^5.9.0" + webpack-dev-middleware "^4.1.0" + webpack-virtual-modules "^0.4.1" + +"@storybook/mdx1-csf@^0.0.1": + version "0.0.1" + resolved "https://registry.yarnpkg.com/@storybook/mdx1-csf/-/mdx1-csf-0.0.1.tgz#d4184e3f6486fade9f7a6bfaf934d9bc07718d5b" + integrity sha512-4biZIWWzoWlCarMZmTpqcJNgo/RBesYZwGFbQeXiGYsswuvfWARZnW9RE9aUEMZ4XPn7B1N3EKkWcdcWe/K2tg== + dependencies: + "@babel/generator" "^7.12.11" + "@babel/parser" "^7.12.11" + "@babel/preset-env" "^7.12.11" + "@babel/types" "^7.12.11" + "@mdx-js/mdx" "^1.6.22" + "@types/lodash" "^4.14.167" + js-string-escape "^1.0.1" + loader-utils "^2.0.0" + lodash "^4.17.21" + prettier ">=2.2.1 <=2.3.0" + ts-dedent "^2.0.0" + +"@storybook/node-logger@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.5.9.tgz#129cfe0d0f79cab4f6a2ba194d39516680b1626f" + integrity sha512-nZZNZG2Wtwv6Trxi3FrnIqUmB55xO+X/WQGPT5iKlqNjdRIu/T72mE7addcp4rbuWCQfZUhcDDGpBOwKtBxaGg== + dependencies: + "@types/npmlog" "^4.1.2" + chalk "^4.1.0" + core-js "^3.8.2" + npmlog "^5.0.1" + pretty-hrtime "^1.0.3" + +"@storybook/postinstall@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.5.9.tgz#a5a2565808e9d7bc310e78c279b09ce337fe3457" + integrity sha512-KQBupK+FMRrtSt8IL0MzCZ/w9qbd25Yxxp/+ajfWgZTRgsWgVFOqcDyMhS16eNbBp5qKIBCBDXfEF+/mK8HwQQ== + dependencies: + core-js "^3.8.2" + +"@storybook/preview-web@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.5.9.tgz#557d919e6df50d66259521aa36ebf4055bbd236e" + integrity sha512-4eMrO2HJyZUYyL/j+gUaDvry6iGedshwT5MQqe7J9FaA+Q2pNARQRB1X53f410w7S4sObRmYIAIluWPYdWym9w== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/channel-postmessage" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/store" "6.5.9" + ansi-to-html "^0.6.11" + core-js "^3.8.2" + global "^4.4.0" + lodash "^4.17.21" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + unfetch "^4.2.0" + util-deprecate "^1.0.2" + +"@storybook/react-docgen-typescript-plugin@1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0": + version "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" + resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0.tgz#3103532ff494fb7dc3cf835f10740ecf6a26c0f9" + integrity sha512-eVg3BxlOm2P+chijHBTByr90IZVUtgRW56qEOLX7xlww2NBuKrcavBlcmn+HH7GIUktquWkMPtvy6e0W0NgA5w== + dependencies: + debug "^4.1.1" + endent "^2.0.1" + find-cache-dir "^3.3.1" + flat-cache "^3.0.4" + micromatch "^4.0.2" + react-docgen-typescript "^2.1.1" + tslib "^2.0.0" + +"@storybook/react@^6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-6.5.9.tgz#687ec1f6b785822a392b7ac115b61800f69fb7cd" + integrity sha512-Rp+QaTQAzxJhwuzJXVd49mnIBLQRlF8llTxPT2YoGHdrGkku/zl/HblQ6H2yzEf15367VyzaAv/BpLsO9Jlfxg== + dependencies: + "@babel/preset-flow" "^7.12.1" + "@babel/preset-react" "^7.12.10" + "@pmmmwh/react-refresh-webpack-plugin" "^0.5.3" + "@storybook/addons" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core" "6.5.9" + "@storybook/core-common" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + "@storybook/docs-tools" "6.5.9" + "@storybook/node-logger" "6.5.9" + "@storybook/react-docgen-typescript-plugin" "1.0.2-canary.6.9d540b91e815f8fc2f8829189deb00553559ff63.0" + "@storybook/semver" "^7.3.2" + "@storybook/store" "6.5.9" + "@types/estree" "^0.0.51" + "@types/node" "^14.14.20 || ^16.0.0" + "@types/webpack-env" "^1.16.0" + acorn "^7.4.1" + acorn-jsx "^5.3.1" + acorn-walk "^7.2.0" + babel-plugin-add-react-displayname "^0.0.5" + babel-plugin-react-docgen "^4.2.1" + core-js "^3.8.2" + escodegen "^2.0.0" + fs-extra "^9.0.1" + global "^4.4.0" + html-tags "^3.1.0" + lodash "^4.17.21" + prop-types "^15.7.2" + react-element-to-jsx-string "^14.3.4" + react-refresh "^0.11.0" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + webpack ">=4.43.0 <6.0.0" + +"@storybook/router@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.5.9.tgz#4740248f8517425b2056273fb366ace8a17c65e8" + integrity sha512-G2Xp/2r8vU2O34eelE+G5VbEEVFDeHcCURrVJEROh6dq2asFJAPbzslVXSeCqgOTNLSpRDJ2NcN5BckkNqmqJg== + dependencies: + "@storybook/client-logger" "6.5.9" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + +"@storybook/semver@^7.3.2": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" + integrity sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg== + dependencies: + core-js "^3.6.5" + find-up "^4.1.0" + +"@storybook/source-loader@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/source-loader/-/source-loader-6.5.9.tgz#7b6f065c6a6108c4b4ca7e45bfd78707373d84ac" + integrity sha512-H03nFKaP6borfWMTTa9igBA+Jm2ph+FoVJImWC/X+LAmLSJYYSXuqSgmiZ/DZvbjxS4k8vccE2HXogne1IvaRA== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + core-js "^3.8.2" + estraverse "^5.2.0" + global "^4.4.0" + loader-utils "^2.0.0" + lodash "^4.17.21" + prettier ">=2.2.1 <=2.3.0" + regenerator-runtime "^0.13.7" + +"@storybook/store@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.5.9.tgz#dc9963fc013636569082bd8f7200804866373735" + integrity sha512-80pcDTcCwK6wUA63aWOp13urI77jfipIVee9mpVvbNyfrNN8kGv1BS0z/JHDxuV6rC4g7LG1fb+BurR0yki7BA== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/csf" "0.0.2--canary.4566f4d.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + slash "^3.0.0" + stable "^0.1.8" + synchronous-promise "^2.0.15" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + +"@storybook/telemetry@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/telemetry/-/telemetry-6.5.9.tgz#8e1e0d4a89fc2387620045e5ea96c109d16a7247" + integrity sha512-JluoHCRhHAr4X0eUNVBSBi1JIBA92404Tu1TPdbN7x6gCZxHXXPTSUTAnspXp/21cTdMhY2x+kfZQ8fmlGK4MQ== + dependencies: + "@storybook/client-logger" "6.5.9" + "@storybook/core-common" "6.5.9" + chalk "^4.1.0" + core-js "^3.8.2" + detect-package-manager "^2.0.1" + fetch-retry "^5.0.2" + fs-extra "^9.0.1" + global "^4.4.0" + isomorphic-unfetch "^3.1.0" + nanoid "^3.3.1" + read-pkg-up "^7.0.1" + regenerator-runtime "^0.13.7" + +"@storybook/testing-library@^0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@storybook/testing-library/-/testing-library-0.0.13.tgz#417c87d4ea62895092ec5fdf67027ae201254f45" + integrity sha512-vRMeIGer4EjJkTgI8sQyK9W431ekPWYCWL//OmSDJ64IT3h7FnW7Xg6p+eqM3oII98/O5pcya5049GxnjaPtxw== + dependencies: + "@storybook/client-logger" "^6.4.0" + "@storybook/instrumenter" "^6.4.0" + "@testing-library/dom" "^8.3.0" + "@testing-library/user-event" "^13.2.1" + ts-dedent "^2.2.0" + +"@storybook/testing-react@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@storybook/testing-react/-/testing-react-1.3.0.tgz#cd69cae6f48b337ee992520e142c7904cb0bbea7" + integrity sha512-TfxzflxwBHSPhetWKuYt239t+1iN8gnnUN8OKo5UGtwwirghKQlApjH23QXW6j8YBqFhmq+yP29Oqf8HgKCFLw== + dependencies: + "@storybook/csf" "0.0.2--canary.87bc651.0" + +"@storybook/theming@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.5.9.tgz#13f60a3a3cd73ceb5caf9f188e1627e79f1891aa" + integrity sha512-KM0AMP5jMQPAdaO8tlbFCYqx9uYM/hZXGSVUhznhLYu7bhNAIK7ZVmXxyE/z/khM++8eUHzRoZGiO/cwCkg9Xw== + dependencies: + "@storybook/client-logger" "6.5.9" + core-js "^3.8.2" + memoizerific "^1.11.3" + regenerator-runtime "^0.13.7" + +"@storybook/ui@6.5.9": + version "6.5.9" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.5.9.tgz#41e59279323cccc0d613974ec9782d797220c8a7" + integrity sha512-ryuPxJgtbb0gPXKGgGAUC+Z185xGAd1IvQ0jM5fJ0SisHXI8jteG3RaWhntOehi9qCg+64Vv6eH/cj9QYNHt1Q== + dependencies: + "@storybook/addons" "6.5.9" + "@storybook/api" "6.5.9" + "@storybook/channels" "6.5.9" + "@storybook/client-logger" "6.5.9" + "@storybook/components" "6.5.9" + "@storybook/core-events" "6.5.9" + "@storybook/router" "6.5.9" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.5.9" + core-js "^3.8.2" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + resolve-from "^5.0.0" + +"@testing-library/dom@^8.3.0": + version "8.14.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.14.0.tgz#c9830a21006d87b9ef6e1aae306cf49b0283e28e" + integrity sha512-m8FOdUo77iMTwVRCyzWcqxlEIk+GnopbrRI15a0EaLbpZSCinIVI4kSQzWhkShK83GogvEFJSsHF3Ws0z1vrqA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/dom@^8.5.0": + version "8.16.1" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.16.1.tgz#96e528c9752d60061128f043e2b43566c0aba2d9" + integrity sha512-XEV2mBxgv6DKjL3+U3WEUzBgT2CjYksoXGlLrrJXYP8OvRfGkBonvelkorazpFlp8tkEecO06r43vN4DIEyegQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^5.0.0" + chalk "^4.1.0" + dom-accessibility-api "^0.5.9" + lz-string "^1.4.4" + pretty-format "^27.0.2" + +"@testing-library/react@^13.3.0": + version "13.3.0" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-13.3.0.tgz#bf298bfbc5589326bbcc8052b211f3bb097a97c5" + integrity sha512-DB79aA426+deFgGSjnf5grczDPiL4taK3hFaa+M5q7q20Kcve9eQottOG5kZ74KEr55v0tU2CQormSSDK87zYQ== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^8.5.0" + "@types/react-dom" "^18.0.0" + +"@testing-library/user-event@^13.2.1": + version "13.5.0" + resolved "https://registry.yarnpkg.com/@testing-library/user-event/-/user-event-13.5.0.tgz#69d77007f1e124d55314a2b73fd204b333b13295" + integrity sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg== + dependencies: + "@babel/runtime" "^7.12.5" + +"@tootallnate/once@2": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" + integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== + +"@trivago/prettier-plugin-sort-imports@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-3.2.0.tgz#7a32b6b3085c436eda0143b2710c440a4a56db90" + integrity sha512-DnwLe+z8t/dZX5xBbYZV1+C5STkyK/P6SSq3Nk6NXlJZsgvDZX2eN4ND7bMFgGV/NL/YChWzcNf6ziGba1ktQQ== + dependencies: + "@babel/core" "7.13.10" + "@babel/generator" "7.13.9" + "@babel/parser" "7.14.6" + "@babel/traverse" "7.13.0" + "@babel/types" "7.13.0" + javascript-natural-sort "0.7.1" + lodash "4.17.21" + +"@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" + integrity sha512-HnYpAE1Y6kRyKM/XkEuiRQhTHvkzMBurTHnpFLYLBGPIylZNPs9jJcuOOYWxPLJCSEtmZT0Y8rHDokKN7rRTig== + +"@types/babel__core@^7.1.14": + version "7.1.19" + resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + +"@types/babel__generator@*": + version "7.6.4" + resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7" + integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg== + dependencies: + "@babel/types" "^7.0.0" + +"@types/babel__template@*": + version "7.4.1" + resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" + integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + +"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6": + version "7.18.0" + resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.18.0.tgz#8134fd78cb39567465be65b9fdc16d378095f41f" + integrity sha512-v4Vwdko+pgymgS+A2UIaJru93zQd85vIGWObM5ekZNdXCKtDYqATlEYnWgfo86Q6I1Lh0oXnksDnMU1cwmlPDw== + dependencies: + "@babel/types" "^7.3.0" + +"@types/body-parser@*", "@types/body-parser@^1.19.2": + version "1.19.2" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" + integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/bonjour@^3.5.9": + version "3.5.10" + resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.10.tgz#0f6aadfe00ea414edc86f5d106357cda9701e275" + integrity sha512-p7ienRMiS41Nu2/igbJxxLDWrSZ0WxM8UQgCeO9KhoVF7cOVFkrKsiDr1EsJIla8vV3oEEjGcz11jc5yimhzZw== + dependencies: + "@types/node" "*" + +"@types/connect-history-api-fallback@^1.3.5": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" + integrity sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw== + dependencies: + "@types/express-serve-static-core" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.35" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" + integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + dependencies: + "@types/node" "*" + +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + +"@types/eslint-scope@^3.7.3": + version "3.7.4" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" + integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "8.4.5" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.5.tgz#acdfb7dd36b91cc5d812d7c093811a8f3d9b31e4" + integrity sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*": + version "0.0.52" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.52.tgz#7f1f57ad5b741f3d5b210d3b1f145640d89bf8fe" + integrity sha512-BZWrtCU0bMVAIliIV+HJO1f1PR41M7NKjfxrFJwwhKI1KwhwOxYw1SXg9ao+CIMt774nFuGiG6eU+udtbEI9oQ== + +"@types/estree@^0.0.51": + version "0.0.51" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" + integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== + +"@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.18": + version "4.17.29" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.29.tgz#2a1795ea8e9e9c91b4a4bbe475034b20c1ec711c" + integrity sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + +"@types/express@*", "@types/express@^4.17.13": + version "4.17.13" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" + integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.18" + "@types/qs" "*" + "@types/serve-static" "*" + +"@types/glob@*", "@types/glob@^7.1.1": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" + integrity sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA== + dependencies: + "@types/minimatch" "*" + "@types/node" "*" + +"@types/graceful-fs@^4.1.2", "@types/graceful-fs@^4.1.3", "@types/graceful-fs@^4.1.5": + version "4.1.5" + resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" + integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== + dependencies: + "@types/node" "*" + +"@types/hast@^2.0.0": + version "2.3.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" + integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== + dependencies: + "@types/unist" "*" + +"@types/html-minifier-terser@^5.0.0": + version "5.1.2" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz#693b316ad323ea97eed6b38ed1a3cc02b1672b57" + integrity sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w== + +"@types/html-minifier-terser@^6.0.0": + version "6.1.0" + resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" + integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== + +"@types/http-proxy@^1.17.8": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" + integrity sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw== + dependencies: + "@types/node" "*" + +"@types/is-function@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/is-function/-/is-function-1.0.1.tgz#2d024eace950c836d9e3335a66b97960ae41d022" + integrity sha512-A79HEEiwXTFtfY+Bcbo58M2GRYzCr9itHWzbzHVFNEYCcoU/MMGwYYf721gBrnhpj1s6RGVVha/IgNFnR0Iw/Q== + +"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44" + integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g== + +"@types/istanbul-lib-report@*": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" + integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== + dependencies: + "@types/istanbul-lib-coverage" "*" + +"@types/istanbul-reports@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" + integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== + dependencies: + "@types/istanbul-lib-report" "*" + +"@types/jest@^28.1.6": + version "28.1.6" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-28.1.6.tgz#d6a9cdd38967d2d746861fb5be6b120e38284dd4" + integrity sha512-0RbGAFMfcBJKOmqRazM8L98uokwuwD5F8rHrv/ZMbrZBwVOWZUyPG6VFNscjYr/vjM3Vu4fRrCPbOs42AfemaQ== + dependencies: + jest-matcher-utils "^28.0.0" + pretty-format "^28.0.0" + +"@types/js-levenshtein@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" + integrity sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g== + +"@types/jsdom@^16.2.4": + version "16.2.15" + resolved "https://registry.yarnpkg.com/@types/jsdom/-/jsdom-16.2.15.tgz#6c09990ec43b054e49636cba4d11d54367fc90d6" + integrity sha512-nwF87yjBKuX/roqGYerZZM0Nv1pZDMAT5YhOHYeM/72Fic+VEqJh4nyoqoapzJnW3pUlfxPY5FhgsJtM+dRnQQ== + dependencies: + "@types/node" "*" + "@types/parse5" "^6.0.3" + "@types/tough-cookie" "*" + +"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": + version "7.0.11" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" + integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + +"@types/lodash@^4.14.167": + version "4.14.182" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" + integrity sha512-/THyiqyQAP9AfARo4pF+aCGcyiQ94tX/Is2I7HofNRqoYLgN1PBoOWu2/zTA5zMxzP5EFutMtWtGAFRKUe961Q== + +"@types/mdast@^3.0.0": + version "3.0.10" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" + integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== + dependencies: + "@types/unist" "*" + +"@types/mime@^1": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" + integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== + +"@types/minimatch@*": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" + integrity sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ== + +"@types/node-fetch@^2.5.7": + version "2.6.2" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.2.tgz#d1a9c5fd049d9415dce61571557104dec3ec81da" + integrity sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + +"@types/node@*", "@types/node@^18.0.1": + version "18.0.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.1.tgz#e91bd73239b338557a84d1f67f7b9e0f25643870" + integrity sha512-CmR8+Tsy95hhwtZBKJBs0/FFq4XX7sDZHlGGf+0q+BRZfMbOTkzkj0AFAuTyXbObDIoanaBBW0+KEW+m3N16Wg== + +"@types/node@^14.0.10 || ^16.0.0", "@types/node@^14.14.20 || ^16.0.0": + version "16.11.43" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.43.tgz#555e5a743f76b6b897d47f945305b618525ddbe6" + integrity sha512-GqWykok+3uocgfAJM8imbozrqLnPyTrpFlrryURQlw1EesPUCx5XxTiucWDSFF9/NUEXDuD4bnvHm8xfVGWTpQ== + +"@types/normalize-package-data@^2.4.0": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" + integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== + +"@types/npmlog@^4.1.2": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" + integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== + +"@types/parse-json@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" + integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== + +"@types/parse5@^5.0.0": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" + integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== + +"@types/parse5@^6.0.3": + version "6.0.3" + resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" + integrity sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g== + +"@types/prettier@^2.1.5": + version "2.7.0" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.0.tgz#ea03e9f0376a4446f44797ca19d9c46c36e352dc" + integrity sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A== + +"@types/pretty-hrtime@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/pretty-hrtime/-/pretty-hrtime-1.0.1.tgz#72a26101dc567b0d68fd956cf42314556e42d601" + integrity sha512-VjID5MJb1eGKthz2qUerWT8+R4b9N+CHvGCzg9fn4kWZgaF9AhdYikQio3R7wV8YY1NsQKPaCwKz1Yff+aHNUQ== + +"@types/prop-types@*": + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== + +"@types/qs@*", "@types/qs@^6.9.5": + version "6.9.7" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" + integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + +"@types/range-parser@*": + version "1.2.4" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" + integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + +"@types/react-dom@^18.0.0": + version "18.0.6" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.6.tgz#36652900024842b74607a17786b6662dd1e103a1" + integrity sha512-/5OFZgfIPSwy+YuIBP/FgJnQnsxhZhjjrnxudMddeblOouIodEQ75X14Rr4wGSG/bknL+Omy9iWlLo1u/9GzAA== + dependencies: + "@types/react" "*" + +"@types/react-dom@^18.0.5": + version "18.0.5" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.5.tgz#330b2d472c22f796e5531446939eacef8378444a" + integrity sha512-OWPWTUrY/NIrjsAPkAk1wW9LZeIjSvkXRhclsFO8CZcZGCOg2G0YZy4ft+rOyYxy8B7ui5iZzi9OkDebZ7/QSA== + dependencies: + "@types/react" "*" + +"@types/react-syntax-highlighter@11.0.5": + version "11.0.5" + resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-11.0.5.tgz#0d546261b4021e1f9d85b50401c0a42acb106087" + integrity sha512-VIOi9i2Oj5XsmWWoB72p3KlZoEbdRAcechJa8Ztebw7bDl2YmR+odxIqhtJGp1q2EozHs02US+gzxJ9nuf56qg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@^18.0.14": + version "18.0.14" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.14.tgz#e016616ffff51dba01b04945610fe3671fdbe06d" + integrity sha512-x4gGuASSiWmo0xjDLpm5mPb52syZHJx02VKbqUKdLmKtAwIh63XClGsiTI1K6DO5q7ox4xAsQrU+Gl3+gGXF9Q== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + +"@types/serve-index@^1.9.1": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.1.tgz#1b5e85370a192c01ec6cec4735cf2917337a6278" + integrity sha512-d/Hs3nWDxNL2xAczmOVZNj92YZCS6RGxfBPjKzuu/XirCgXdpKEb88dYNbrYGint6IVWLNP+yonwVAuRC0T2Dg== + dependencies: + "@types/express" "*" + +"@types/serve-static@*", "@types/serve-static@^1.13.10": + version "1.13.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" + integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/set-cookie-parser@^2.4.0": + version "2.4.2" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.2.tgz#b6a955219b54151bfebd4521170723df5e13caad" + integrity sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w== + dependencies: + "@types/node" "*" + +"@types/sockjs@^0.3.33": + version "0.3.33" + resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.33.tgz#570d3a0b99ac995360e3136fd6045113b1bd236f" + integrity sha512-f0KEEe05NvUnat+boPTZ0dgaLZ4SfSouXUgv5noUiefG2ajgKjmETo9ZJyuqsl7dfl2aHlLJUiki6B4ZYldiiw== + dependencies: + "@types/node" "*" + +"@types/source-list-map@*": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@types/source-list-map/-/source-list-map-0.1.2.tgz#0078836063ffaf17412349bba364087e0ac02ec9" + integrity sha512-K5K+yml8LTo9bWJI/rECfIPrGgxdpeNbj+d53lwN4QjW1MCwlkhUms+gtdzigTeUyBr09+u8BwOIY3MXvHdcsA== + +"@types/stack-utils@^2.0.0": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" + integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== + +"@types/tapable@^1", "@types/tapable@^1.0.5": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" + integrity sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ== + +"@types/tough-cookie@*": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397" + integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw== + +"@types/uglify-js@*": + version "3.16.0" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.16.0.tgz#2cf74a0e6ebb6cd54c0d48e509d5bd91160a9602" + integrity sha512-0yeUr92L3r0GLRnBOvtYK1v2SjqMIqQDHMl7GLb+l2L8+6LSFWEEWEIgVsPdMn5ImLM8qzWT8xFPtQYpp8co0g== + dependencies: + source-map "^0.6.1" + +"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2", "@types/unist@^2.0.3": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" + integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== + +"@types/webpack-env@^1.16.0": + version "1.17.0" + resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.17.0.tgz#f99ce359f1bfd87da90cc4a57cab0a18f34a48d0" + integrity sha512-eHSaNYEyxRA5IAG0Ym/yCyf86niZUIF/TpWKofQI/CVfh5HsMEUyfE2kwFxha4ow0s5g0LfISQxpDKjbRDrizw== + +"@types/webpack-sources@*": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" + integrity sha512-Ft7YH3lEVRQ6ls8k4Ff1oB4jN6oy/XmU6tQISKdhfh+1mR+viZFphS6WL0IrtDOzvefmJg5a0s7ZQoRXwqTEFg== + dependencies: + "@types/node" "*" + "@types/source-list-map" "*" + source-map "^0.7.3" + +"@types/webpack@^4.41.26", "@types/webpack@^4.41.8": + version "4.41.32" + resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-4.41.32.tgz#a7bab03b72904070162b2f169415492209e94212" + integrity sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg== + dependencies: + "@types/node" "*" + "@types/tapable" "^1" + "@types/uglify-js" "*" + "@types/webpack-sources" "*" + anymatch "^3.0.0" + source-map "^0.6.0" + +"@types/ws@^8.5.1": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + +"@types/yargs-parser@*": + version "21.0.0" + resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b" + integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA== + +"@types/yargs@^15.0.0": + version "15.0.14" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" + integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^16.0.0": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977" + integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw== + dependencies: + "@types/yargs-parser" "*" + +"@types/yargs@^17.0.8": + version "17.0.10" + resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" + integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== + dependencies: + "@types/yargs-parser" "*" + +"@typescript-eslint/eslint-plugin@^5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.30.4.tgz#a46c8c0ab755a936cb63786a6222876ce51675e4" + integrity sha512-xjujQISAIa4HAaos8fcMZXmqkuZqMx6icdxkI88jMM/eNe4J8AuTLYnLK+zdm0mBYLyctdFf//UE4/xFCcQzYQ== + dependencies: + "@typescript-eslint/scope-manager" "5.30.4" + "@typescript-eslint/type-utils" "5.30.4" + "@typescript-eslint/utils" "5.30.4" + debug "^4.3.4" + functional-red-black-tree "^1.0.1" + ignore "^5.2.0" + regexpp "^3.2.0" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/parser@^5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.30.4.tgz#659411e8700b22c8d5400798ef24838425bf4567" + integrity sha512-/ge1HtU63wVoED4VnlU2o+FPFmi017bPYpeSrCmd8Ycsti4VSxXrmcpXXm7JpI4GT0Aa7qviabv1PEp6L5bboQ== + dependencies: + "@typescript-eslint/scope-manager" "5.30.4" + "@typescript-eslint/types" "5.30.4" + "@typescript-eslint/typescript-estree" "5.30.4" + debug "^4.3.4" + +"@typescript-eslint/scope-manager@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.30.4.tgz#8140efd2bc12d41d74e8af23872a89f3edbe552e" + integrity sha512-DNzlQwGSiGefz71JwaHrpcaAX3zYkEcy8uVuan3YMKOa6qeW/y+7SaD8KIsIAruASwq6P+U4BjWBWtM2O+mwBQ== + dependencies: + "@typescript-eslint/types" "5.30.4" + "@typescript-eslint/visitor-keys" "5.30.4" + +"@typescript-eslint/type-utils@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.30.4.tgz#00ff19073cd01f7d27e9af49ce08d6a69f1e4f01" + integrity sha512-55cf1dZviwwv+unDB+mF8vZkfta5muTK6bppPvenWWCD7slZZ0DEsXUjZerqy7Rq8s3J4SXdg4rMIY8ngCtTmA== + dependencies: + "@typescript-eslint/utils" "5.30.4" + debug "^4.3.4" + tsutils "^3.21.0" + +"@typescript-eslint/types@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.30.4.tgz#3bc99eca8ba3fcfd6a21480e216b09dab81c3999" + integrity sha512-NTEvqc+Vvu8Q6JeAKryHk2eqLKqsr2St3xhIjhOjQv5wQUBhaTuix4WOSacqj0ONWfKVU12Eug3LEAB95GBkMA== + +"@typescript-eslint/typescript-estree@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.30.4.tgz#ac4be8a2f8fb1f1c3b346d5992a36163121ddb3f" + integrity sha512-V4VnEs6/J9/nNizaA12IeU4SAeEYaiKr7XndLNfV5+3zZSB4hIu6EhHJixTKhvIqA+EEHgBl6re8pivBMLLO1w== + dependencies: + "@typescript-eslint/types" "5.30.4" + "@typescript-eslint/visitor-keys" "5.30.4" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + +"@typescript-eslint/utils@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.30.4.tgz#07a2b7ce80b2527ea506829f190591b76c70ba9f" + integrity sha512-a+GQrJzOUhn4WT1mUumXDyam+22Oo4c5K/jnZ+6r/4WTQF3q8e4CsC9PLHb4SnOClzOqo/5GLZWvkE1aa5UGKQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@typescript-eslint/scope-manager" "5.30.4" + "@typescript-eslint/types" "5.30.4" + "@typescript-eslint/typescript-estree" "5.30.4" + eslint-scope "^5.1.1" + eslint-utils "^3.0.0" + +"@typescript-eslint/visitor-keys@5.30.4": + version "5.30.4" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.30.4.tgz#b4969df1a440cc999d4bb7f7b7932dce05537089" + integrity sha512-ulKGse3mruSc8x6l8ORSc6+1ORyJzKmZeIaRTu/WpaF/jx3vHvEn5XZUKF9XaVg2710mFmTAUlLcLYLPp/Zf/Q== + dependencies: + "@typescript-eslint/types" "5.30.4" + eslint-visitor-keys "^3.3.0" + +"@webassemblyjs/ast@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" + integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + +"@webassemblyjs/ast@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" + integrity sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA== + dependencies: + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + +"@webassemblyjs/floating-point-hex-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" + integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== + +"@webassemblyjs/floating-point-hex-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz#3c3d3b271bddfc84deb00f71344438311d52ffb4" + integrity sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA== + +"@webassemblyjs/helper-api-error@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" + integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== + +"@webassemblyjs/helper-api-error@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz#203f676e333b96c9da2eeab3ccef33c45928b6a2" + integrity sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw== + +"@webassemblyjs/helper-buffer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" + integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== + +"@webassemblyjs/helper-buffer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz#a1442d269c5feb23fcbc9ef759dac3547f29de00" + integrity sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA== + +"@webassemblyjs/helper-code-frame@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz#647f8892cd2043a82ac0c8c5e75c36f1d9159f27" + integrity sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA== + dependencies: + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/helper-fsm@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz#c05256b71244214671f4b08ec108ad63b70eddb8" + integrity sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw== + +"@webassemblyjs/helper-module-context@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz#25d8884b76839871a08a6c6f806c3979ef712f07" + integrity sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g== + dependencies: + "@webassemblyjs/ast" "1.9.0" + +"@webassemblyjs/helper-numbers@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" + integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" + integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== + +"@webassemblyjs/helper-wasm-bytecode@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz#4fed8beac9b8c14f8c58b70d124d549dd1fe5790" + integrity sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw== + +"@webassemblyjs/helper-wasm-section@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" + integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + +"@webassemblyjs/helper-wasm-section@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz#5a4138d5a6292ba18b04c5ae49717e4167965346" + integrity sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + +"@webassemblyjs/ieee754@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" + integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/ieee754@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz#15c7a0fbaae83fb26143bbacf6d6df1702ad39e4" + integrity sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" + integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/leb128@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.9.0.tgz#f19ca0b76a6dc55623a09cffa769e838fa1e1c95" + integrity sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" + integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== + +"@webassemblyjs/utf8@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.9.0.tgz#04d33b636f78e6a6813227e82402f7637b6229ab" + integrity sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w== + +"@webassemblyjs/wasm-edit@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" + integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/helper-wasm-section" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-opt" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + "@webassemblyjs/wast-printer" "1.11.1" + +"@webassemblyjs/wasm-edit@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz#3fe6d79d3f0f922183aa86002c42dd256cfee9cf" + integrity sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/helper-wasm-section" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-opt" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + "@webassemblyjs/wast-printer" "1.9.0" + +"@webassemblyjs/wasm-gen@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" + integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-gen@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz#50bc70ec68ded8e2763b01a1418bf43491a7a49c" + integrity sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wasm-opt@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" + integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-buffer" "1.11.1" + "@webassemblyjs/wasm-gen" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + +"@webassemblyjs/wasm-opt@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz#2211181e5b31326443cc8112eb9f0b9028721a61" + integrity sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-buffer" "1.9.0" + "@webassemblyjs/wasm-gen" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + +"@webassemblyjs/wasm-parser@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" + integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/helper-api-error" "1.11.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.1" + "@webassemblyjs/ieee754" "1.11.1" + "@webassemblyjs/leb128" "1.11.1" + "@webassemblyjs/utf8" "1.11.1" + +"@webassemblyjs/wasm-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz#9d48e44826df4a6598294aa6c87469d642fff65e" + integrity sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-wasm-bytecode" "1.9.0" + "@webassemblyjs/ieee754" "1.9.0" + "@webassemblyjs/leb128" "1.9.0" + "@webassemblyjs/utf8" "1.9.0" + +"@webassemblyjs/wast-parser@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz#3031115d79ac5bd261556cecc3fa90a3ef451914" + integrity sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/floating-point-hex-parser" "1.9.0" + "@webassemblyjs/helper-api-error" "1.9.0" + "@webassemblyjs/helper-code-frame" "1.9.0" + "@webassemblyjs/helper-fsm" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.11.1": + version "1.11.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" + integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== + dependencies: + "@webassemblyjs/ast" "1.11.1" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/wast-printer@1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz#4935d54c85fef637b00ce9f52377451d00d47899" + integrity sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/wast-parser" "1.9.0" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" + integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== + +"@webpack-cli/info@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" + integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" + integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== + +"@xmldom/xmldom@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" + integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +abab@^2.0.5, abab@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" + integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== + +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" + integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== + dependencies: + mime-types "~2.1.34" + negotiator "0.6.3" + +acorn-globals@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" + integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== + dependencies: + acorn "^7.1.1" + acorn-walk "^7.1.1" + +acorn-import-assertions@^1.7.6: + version "1.8.0" + resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" + integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== + +acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" + integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== + +acorn-walk@^7.1.1, acorn-walk@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" + integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== + +acorn@^6.4.1: + version "6.4.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" + integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== + +acorn@^7.1.1, acorn@^7.4.1: + version "7.4.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" + integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== + +acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.1: + version "8.7.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30" + integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A== + +address@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/address/-/address-1.2.0.tgz#d352a62c92fee90f89a693eccd2a8b2139ab02d9" + integrity sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig== + +agent-base@6: + version "6.0.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" + integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== + dependencies: + debug "4" + +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + +airbnb-js-shims@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz#db481102d682b98ed1daa4c5baa697a05ce5c040" + integrity sha512-wJNXPH66U2xjgo1Zwyjf9EydvJ2Si94+vSdk6EERcBfB2VZkeltpqIats0cqIZMLCXP3zcyaUKGYQeIBT6XjsQ== + dependencies: + array-includes "^3.0.3" + array.prototype.flat "^1.2.1" + array.prototype.flatmap "^1.2.1" + es5-shim "^4.5.13" + es6-shim "^0.35.5" + function.prototype.name "^1.1.0" + globalthis "^1.0.0" + object.entries "^1.1.0" + object.fromentries "^2.0.0 || ^1.0.0" + object.getownpropertydescriptors "^2.0.3" + object.values "^1.1.0" + promise.allsettled "^1.0.0" + promise.prototype.finally "^3.1.0" + string.prototype.matchall "^4.0.0 || ^3.0.1" + string.prototype.padend "^3.0.0" + string.prototype.padstart "^3.0.0" + symbol.prototype.description "^1.0.0" + +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== + +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + +ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv-keywords@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" + integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== + dependencies: + fast-deep-equal "^3.1.3" + +ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + +ajv@^8.0.0, ajv@^8.8.0: + version "8.11.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" + integrity sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + +ansi-align@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-3.0.1.tgz#0cdf12e111ace773a86e9a1fad1225c43cb19a59" + integrity sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w== + dependencies: + string-width "^4.1.0" + +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + +ansi-escapes@^4.2.1: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + +ansi-html-community@0.0.8, ansi-html-community@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/ansi-html-community/-/ansi-html-community-0.0.8.tgz#69fbc4d6ccbe383f9736934ae34c3f8290f1bf41" + integrity sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +ansi-styles@^5.0.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" + integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== + +ansi-to-html@^0.6.11: + version "0.6.15" + resolved "https://registry.yarnpkg.com/ansi-to-html/-/ansi-to-html-0.6.15.tgz#ac6ad4798a00f6aa045535d7f6a9cb9294eebea7" + integrity sha512-28ijx2aHJGdzbs+O5SNQF65r6rrKYnkuwTYm8lZlChuoJ9P1vVzIpWO20sQTqTPDXYp6NFwk326vApTtLVFXpQ== + dependencies: + entities "^2.0.0" + +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + +anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +app-root-dir@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" + integrity sha512-jlpIfsOoNoafl92Sz//64uQHGSyMrD2vYG5d8o2a4qGvyNCvXur7bzIsWtAC/6flI2RYAp3kv8rsfBtaLm7w0g== + +"aproba@^1.0.3 || ^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" + integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== + +aproba@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" + integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw== + dependencies: + delegates "^1.0.0" + readable-stream "^3.6.0" + +argparse@^1.0.7: + version "1.0.10" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" + integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== + dependencies: + sprintf-js "~1.0.2" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +aria-query@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c" + integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg== + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA== + +arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q== + +array-find-index@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw== + +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + +array-flatten@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.2.tgz#24ef80a28c1a893617e2149b0c6d0d788293b099" + integrity sha512-hNfzcOV8W4NdualtqBFPyVO+54DSJuZGY9qT4pRroB6S9e3iiido2ISIC5h9R2sPJ8H3FHCIiEnsv1lPXO3KtQ== + +array-includes@^3.0.3, array-includes@^3.1.4, array-includes@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.5.tgz#2c320010db8d31031fd2a5f6b3bbd4b1aad31bdb" + integrity sha512-iSDYZMMyTPkiFasVqfuAQnWAYcvO/SeBSCGKePoEthjp4LEMTe4uLc7b025o4jAZpHhihh8xPo99TNWUWWkGDQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + get-intrinsic "^1.1.1" + is-string "^1.0.7" + +array-union@^1.0.1, array-union@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + integrity sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng== + dependencies: + array-uniq "^1.0.1" + +array-union@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" + integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + integrity sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q== + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ== + +array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.5: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz#0b0c1567bf57b38b56b4c97b8aa72ab45e4adc7b" + integrity sha512-12IUEkHsAhA4DY5s0FPgNXIdc8VRSqD9Zp78a5au9abH/SOBrsp082JOWFNTjkMozh8mqcdiKuaLGhPeYztxSw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + +array.prototype.flatmap@^1.2.1, array.prototype.flatmap@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.3.0.tgz#a7e8ed4225f4788a70cd910abcf0791e76a5534f" + integrity sha512-PZC9/8TKAIxcWKdyeb77EzULHPrIX/tIZebLJUQOMR1OwYosT8yggdfWScfTBCDj5utONvOuPQQumYsU2ULbkg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-shim-unscopables "^1.0.0" + +array.prototype.map@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.map/-/array.prototype.map-1.0.4.tgz#0d97b640cfdd036c1b41cfe706a5e699aa0711f2" + integrity sha512-Qds9QnX7A0qISY7JT5WuJO0NJPE9CMlC6JzHQfhpqAAQQzufVRoeH7EzUY5GcPTx72voG8LV/5eo+b8Qi8hmhA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +array.prototype.reduce@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz#8167e80089f78bff70a99e20bd4201d4663b0a6f" + integrity sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.2" + es-array-method-boxes-properly "^1.0.0" + is-string "^1.0.7" + +arrify@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" + integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== + +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw== + +ast-types@^0.14.2: + version "0.14.2" + resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.14.2.tgz#600b882df8583e3cd4f2df5fa20fa83759d4bdfd" + integrity sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA== + dependencies: + tslib "^2.0.1" + +async-each@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" + integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +at-least-node@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" + integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== + +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +autoprefixer@^9.8.6: + version "9.8.8" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.8.tgz#fd4bd4595385fa6f06599de749a4d5f7a474957a" + integrity sha512-eM9d/swFopRt5gdJ7jrpCwgvEMIayITpojhkkSMRsFHYuH5bkSQ4p/9qTEHtmNudUZh22Tehu7I6CxAW0IXTKA== + dependencies: + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + normalize-range "^0.1.2" + num2fraction "^1.2.2" + picocolors "^0.2.1" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" + +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + +babel-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-28.1.3.tgz#c1187258197c099072156a0a121c11ee1e3917d5" + integrity sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q== + dependencies: + "@jest/transform" "^28.1.3" + "@types/babel__core" "^7.1.14" + babel-plugin-istanbul "^6.1.1" + babel-preset-jest "^28.1.3" + chalk "^4.0.0" + graceful-fs "^4.2.9" + slash "^3.0.0" + +babel-loader@^8.0.0, babel-loader@^8.2.5: + version "8.2.5" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e" + integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^2.0.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-add-react-displayname@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/babel-plugin-add-react-displayname/-/babel-plugin-add-react-displayname-0.0.5.tgz#339d4cddb7b65fd62d1df9db9fe04de134122bd5" + integrity sha512-LY3+Y0XVDYcShHHorshrDbt4KFWL4bSeniCtl4SYZbask+Syngk1uMPCeN9+nSiZo6zX5s0RTq/J9Pnaaf/KHw== + +babel-plugin-apply-mdx-type-prop@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/babel-plugin-apply-mdx-type-prop/-/babel-plugin-apply-mdx-type-prop-1.6.22.tgz#d216e8fd0de91de3f1478ef3231e05446bc8705b" + integrity sha512-VefL+8o+F/DfK24lPZMtJctrCVOfgbqLAGZSkxwhazQv4VxPg3Za/i40fu22KR2m8eEda+IfSOlPLUSIiLcnCQ== + dependencies: + "@babel/helper-plugin-utils" "7.10.4" + "@mdx-js/util" "1.6.22" + +babel-plugin-dynamic-import-node@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3" + integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ== + dependencies: + object.assign "^4.1.0" + +babel-plugin-extract-import-names@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/babel-plugin-extract-import-names/-/babel-plugin-extract-import-names-1.6.22.tgz#de5f9a28eb12f3eb2578bf74472204e66d1a13dc" + integrity sha512-yJ9BsJaISua7d8zNT7oRG1ZLBJCIdZ4PZqmH8qa9N5AK01ifk3fnkc98AXhtzE7UkfCsEumvoQWgoYLhOnJ7jQ== + dependencies: + "@babel/helper-plugin-utils" "7.10.4" + +babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" + integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@istanbuljs/load-nyc-config" "^1.0.0" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-instrument "^5.0.4" + test-exclude "^6.0.0" + +babel-plugin-jest-hoist@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz#1952c4d0ea50f2d6d794353762278d1d8cca3fbe" + integrity sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.1.14" + "@types/babel__traverse" "^7.0.6" + +babel-plugin-macros@^2.6.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" + integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== + dependencies: + "@babel/runtime" "^7.7.2" + cosmiconfig "^6.0.0" + resolve "^1.12.0" + +babel-plugin-macros@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + +babel-plugin-named-exports-order@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/babel-plugin-named-exports-order/-/babel-plugin-named-exports-order-0.0.2.tgz#ae14909521cf9606094a2048239d69847540cb09" + integrity sha512-OgOYHOLoRK+/mvXU9imKHlG6GkPLYrUCvFXG/CM93R/aNNO8pOOF4aS+S8CCHMDQoNSeiOYEZb/G6RwL95Jktw== + +babel-plugin-polyfill-corejs2@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.1.tgz#440f1b70ccfaabc6b676d196239b138f8a2cfba5" + integrity sha512-v7/T6EQcNfVLfcN2X8Lulb7DjprieyLWJK/zOWH5DUYcAgex9sP3h25Q+DLsX9TloXe3y1O8l2q2Jv9q8UVB9w== + dependencies: + "@babel/compat-data" "^7.13.11" + "@babel/helper-define-polyfill-provider" "^0.3.1" + semver "^6.1.1" + +babel-plugin-polyfill-corejs3@^0.1.0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0" + integrity sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.1.5" + core-js-compat "^3.8.1" + +babel-plugin-polyfill-corejs3@^0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.2.tgz#aabe4b2fa04a6e038b688c5e55d44e78cd3a5f72" + integrity sha512-G3uJih0XWiID451fpeFaYGVuxHEjzKTHtc9uGFEjR6hHrvNzeS/PX+LLLcetJcytsB5m4j+K3o/EpXJNb/5IEQ== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + core-js-compat "^3.21.0" + +babel-plugin-polyfill-regenerator@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.3.1.tgz#2c0678ea47c75c8cc2fbb1852278d8fb68233990" + integrity sha512-Y2B06tvgHYt1x0yz17jGkGeeMr5FeKUu+ASJ+N6nB5lQ8Dapfg42i0OVrf8PNGJ3zKL4A23snMi1IRwrqqND7A== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.3.1" + +babel-plugin-react-docgen@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/babel-plugin-react-docgen/-/babel-plugin-react-docgen-4.2.1.tgz#7cc8e2f94e8dc057a06e953162f0810e4e72257b" + integrity sha512-UQ0NmGHj/HAqi5Bew8WvNfCk8wSsmdgNd8ZdMjBCICtyCJCq9LiqgqvjCYe570/Wg7AQArSq1VQ60Dd/CHN7mQ== + dependencies: + ast-types "^0.14.2" + lodash "^4.17.15" + react-docgen "^5.0.0" + +babel-preset-current-node-syntax@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" + integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== + dependencies: + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-bigint" "^7.8.3" + "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.8.3" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + "@babel/plugin-syntax-top-level-await" "^7.8.3" + +babel-preset-jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz#5dfc20b99abed5db994406c2b9ab94c73aaa419d" + integrity sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A== + dependencies: + babel-plugin-jest-hoist "^28.1.3" + babel-preset-current-node-syntax "^1.0.0" + +bail@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.5.tgz#b6fa133404a392cbc1f8c4bf63f5953351e7a776" + integrity sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +base64-js@^1.0.2, base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" + integrity sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw== + +better-opn@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/better-opn/-/better-opn-2.1.1.tgz#94a55b4695dc79288f31d7d0e5f658320759f7c6" + integrity sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA== + dependencies: + open "^7.0.3" + +big-integer@^1.6.16, big-integer@^1.6.7: + version "1.6.51" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" + integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg== + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^1.0.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" + integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + +bluebird@^3.5.5: + version "3.7.2" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" + integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== + +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" + integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== + +body-parser@1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" + integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== + dependencies: + bytes "3.1.2" + content-type "~1.0.4" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.10.3" + raw-body "2.5.1" + type-is "~1.6.18" + unpipe "1.0.0" + +bonjour-service@^1.0.11: + version "1.0.13" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.0.13.tgz#4ac003dc1626023252d58adf2946f57e5da450c1" + integrity sha512-LWKRU/7EqDUC9CTAQtuZl5HzBALoCYwtLhffW3et7vZMwv3bWLpJf8bRYlMD5OCcDpTfnPgNCV4yo9ZIaJGMiA== + dependencies: + array-flatten "^2.1.2" + dns-equal "^1.0.0" + fast-deep-equal "^3.1.3" + multicast-dns "^7.2.5" + +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + +boxen@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/boxen/-/boxen-5.1.2.tgz#788cb686fc83c1f486dfa8a40c68fc2b831d2b50" + integrity sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ== + dependencies: + ansi-align "^3.0.0" + camelcase "^6.2.0" + chalk "^4.1.0" + cli-boxes "^2.2.1" + string-width "^4.2.2" + type-fest "^0.20.2" + widest-line "^3.1.0" + wrap-ansi "^7.0.0" + +bplist-parser@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.1.1.tgz#d60d5dcc20cba6dc7e1f299b35d3e1f95dafbae6" + integrity sha512-2AEM0FXy8ZxVLBuqX0hqt1gDwcnz2zygEkQ6zaD5Wko/sB9paUNwlpawrFtKeHUAQUOzjVy9AO4oeonqIHKA9Q== + dependencies: + big-integer "^1.6.7" + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^2.3.1, braces@^2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +braces@^3.0.2, braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +broadcast-channel@^3.4.1: + version "3.7.0" + resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937" + integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg== + dependencies: + "@babel/runtime" "^7.7.2" + detect-node "^2.1.0" + js-sha3 "0.8.0" + microseconds "0.2.0" + nano-time "1.0.0" + oblivious-set "1.0.0" + rimraf "3.0.2" + unload "2.2.0" + +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w== + +browser-assert@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/browser-assert/-/browser-assert-1.2.1.tgz#9aaa5a2a8c74685c2ae05bfe46efd606f068c200" + integrity sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ== + +browser-process-hrtime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" + integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== + +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + +browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.0: + version "4.21.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.1.tgz#c9b9b0a54c7607e8dc3e01a0d311727188011a00" + integrity sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ== + dependencies: + caniuse-lite "^1.0.30001359" + electron-to-chromium "^1.4.172" + node-releases "^2.0.5" + update-browserslist-db "^1.0.4" + +bser@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" + integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== + dependencies: + node-int64 "^0.4.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ== + +bytes@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" + integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== + +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + +c8@^7.6.0: + version "7.11.3" + resolved "https://registry.yarnpkg.com/c8/-/c8-7.11.3.tgz#88c8459c1952ed4f701b619493c9ae732b057163" + integrity sha512-6YBmsaNmqRm9OS3ZbIiL2EZgi1+Xc4O24jL3vMYGE6idixYuGdy76rIfIdltSKDj9DpLNrcXSonUTR1miBD0wA== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@istanbuljs/schema" "^0.1.3" + find-up "^5.0.0" + foreground-child "^2.0.0" + istanbul-lib-coverage "^3.2.0" + istanbul-lib-report "^3.0.0" + istanbul-reports "^3.1.4" + rimraf "^3.0.2" + test-exclude "^6.0.0" + v8-to-istanbul "^9.0.0" + yargs "^16.2.0" + yargs-parser "^20.2.9" + +cacache@^12.0.2: + version "12.0.4" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c" + integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ== + dependencies: + bluebird "^3.5.5" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.4" + graceful-fs "^4.1.15" + infer-owner "^1.0.3" + lru-cache "^5.1.1" + mississippi "^3.0.0" + mkdirp "^0.5.1" + move-concurrently "^1.0.1" + promise-inflight "^1.0.1" + rimraf "^2.6.3" + ssri "^6.0.1" + unique-filename "^1.1.1" + y18n "^4.0.0" + +cacache@^15.0.5: + version "15.3.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-15.3.0.tgz#dc85380fb2f556fe3dda4c719bfa0ec875a7f1eb" + integrity sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ== + dependencies: + "@npmcli/fs" "^1.0.0" + "@npmcli/move-file" "^1.0.1" + chownr "^2.0.0" + fs-minipass "^2.0.0" + glob "^7.1.4" + infer-owner "^1.0.4" + lru-cache "^6.0.0" + minipass "^3.1.1" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.2" + mkdirp "^1.0.3" + p-map "^4.0.0" + promise-inflight "^1.0.1" + rimraf "^3.0.2" + ssri "^8.0.1" + tar "^6.0.2" + unique-filename "^1.1.1" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +call-bind@^1.0.0, call-bind@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" + integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== + dependencies: + function-bind "^1.1.1" + get-intrinsic "^1.0.2" + +call-me-maybe@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" + integrity sha512-wCyFsDQkKPwwF8BDwOiWNx/9K45L/hvggQiDbve+viMNMQnWhrlYIuBk09offfwCRtCO9P6XwUttufzU11WCVw== + +callsites@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" + integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== + +camel-case@^4.1.1, camel-case@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a" + integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw== + dependencies: + pascal-case "^3.1.2" + tslib "^2.0.3" + +camelcase-css@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" + integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== + +camelcase-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" + integrity sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ== + dependencies: + camelcase "^2.0.0" + map-obj "^1.0.0" + +camelcase@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" + integrity sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw== + +camelcase@^5.3.1: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + +camelcase@^6.2.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001359: + version "1.0.30001363" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001363.tgz#26bec2d606924ba318235944e1193304ea7c4f15" + integrity sha512-HpQhpzTGGPVMnCjIomjt+jvyUu8vNFo3TaDiZ/RcoTrlOq/5+tC8zHdsbgFB6MxmaY+jCpsH09aD80Bb4Ow3Sg== + +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + +case-sensitive-paths-webpack-plugin@^2.3.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4" + integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw== + +ccount@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" + integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== + +chalk@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^2.0.0, chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +char-regex@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" + integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== + +character-entities-legacy@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" + integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== + +character-entities@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" + integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw== + +character-reference-invalid@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" + integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== + +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + +chokidar@^2.1.8: + version "2.1.8" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" + integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.1" + optionalDependencies: + fsevents "^1.2.7" + +chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + +chownr@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" + integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== + +chrome-trace-event@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" + integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + +ci-info@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" + integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== + +ci-info@^3.2.0: + version "3.3.2" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.2.tgz#6d2967ffa407466481c6c90b6e16b3098f080128" + integrity sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg== + +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +cjs-module-lexer@^1.0.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" + integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +clean-css@^4.2.3: + version "4.2.4" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.4.tgz#733bf46eba4e607c6891ea57c24a989356831178" + integrity sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A== + dependencies: + source-map "~0.6.0" + +clean-css@^5.2.2: + version "5.3.0" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.0.tgz#ad3d8238d5f3549e83d5f87205189494bc7cbb59" + integrity sha512-YYuuxv4H/iNb1Z/5IbMRoxgrzjWGhOEFfd+groZ5dMCVkpENiMZmwspdrzBo9286JjM1gZJPAyL7ZIdzuvu2AQ== + dependencies: + source-map "~0.6.0" + +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== + +clean-webpack-plugin@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/clean-webpack-plugin/-/clean-webpack-plugin-4.0.0.tgz#72947d4403d452f38ed61a9ff0ada8122aacd729" + integrity sha512-WuWE1nyTNAyW5T7oNyys2EN0cfP2fdRxhxnIQWiAp0bMabPdHhoGxM8A6YL2GhqwgrPnnaemVE7nv5XJ2Fhh2w== + dependencies: + del "^4.1.1" + +cli-boxes@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-2.2.1.tgz#ddd5035d25094fce220e9cab40a45840a440318f" + integrity sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw== + +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + +cli-table3@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.2.tgz#aaf5df9d8b5bf12634dc8b3040806a0c07120d2a" + integrity sha512-QyavHCaIC80cMivimWu4aWHilIpiDpfm3hGmqAmXVL1UsnbLuBSMd21hTX6VY4ZSDSM73ESLeF8TOYId3rBTbw== + dependencies: + string-width "^4.2.0" + optionalDependencies: + "@colors/colors" "1.5.0" + +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== + +clsx@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.0.tgz#62937c6adfea771247c34b54d320fb99624f5702" + integrity sha512-3avwM37fSK5oP6M5rQ9CNe99lwxhXDOeSWVPAOYF6OazUTgZCMb0yWlJpmdD74REy1gkEaFiub2ULv4fq9GUhA== + +clsx@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.0.tgz#b0e415ea7537dbac01b169c5cec1caeb11d86566" + integrity sha512-EPRP7XJsM1y0iCU3Z7C7jFKdQboXSeHgEfzQUTlz7m5NP3hDrlz48aUsmNGp4pC+JOW9WA3vIRqlYuo/bl4Drw== + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ== + +collapse-white-space@^1.0.2: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + +collect-v8-coverage@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" + integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw== + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-support@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" + integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== + +colorette@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + +colorette@^2.0.10, colorette@^2.0.14: + version "2.0.19" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" + integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ== + +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + +comma-separated-tokens@^1.0.0: + version "1.0.8" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" + integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== + +commander@^2.19.0, commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" + integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== + +commander@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + +commander@^7.0.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== + +commander@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" + integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== + +common-path-prefix@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/common-path-prefix/-/common-path-prefix-3.0.0.tgz#7d007a7e07c58c4b4d5f433131a19141b29f11e0" + integrity sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +component-emitter@^1.2.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" + integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + +compressible@~2.0.16: + version "2.0.18" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" + integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg== + dependencies: + mime-db ">= 1.43.0 < 2" + +compression@^1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f" + integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ== + dependencies: + accepts "~1.3.5" + bytes "3.0.0" + compressible "~2.0.16" + debug "2.6.9" + on-headers "~1.0.2" + safe-buffer "5.1.2" + vary "~1.1.2" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== + +concat-stream@^1.5.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +connect-history-api-fallback@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz#647264845251a0daf25b97ce87834cace0f5f1c8" + integrity sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA== + +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + +console-control-strings@^1.0.0, console-control-strings@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ== + +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ== + +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" + integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== + dependencies: + safe-buffer "~5.1.1" + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" + integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== + +cookie@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + +copy-concurrently@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== + dependencies: + aproba "^1.1.1" + fs-write-stream-atomic "^1.0.8" + iferr "^0.1.5" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.0" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw== + +core-js-compat@^3.21.0, core-js-compat@^3.22.1, core-js-compat@^3.8.1: + version "3.23.3" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.23.3.tgz#7d8503185be76bb6d8d592c291a4457a8e440aa9" + integrity sha512-WSzUs2h2vvmKsacLHNTdpyOC9k43AEhcGoFlVgCY4L7aw98oSBKtPL6vD0/TqZjRWRQYdDSLkzZIni4Crbbiqw== + dependencies: + browserslist "^4.21.0" + semver "7.0.0" + +core-js-pure@^3.8.1: + version "3.23.3" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.23.3.tgz#bcd02d3d8ec68ad871ef50d5ccbb248ddb54f401" + integrity sha512-XpoouuqIj4P+GWtdyV8ZO3/u4KftkeDVMfvp+308eGMhCrA3lVDSmAxO0c6GGOcmgVlaKDrgWVMo49h2ab/TDA== + +core-js@^3.0.4, core-js@^3.6.5, core-js@^3.8.2: + version "3.23.3" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.23.3.tgz#3b977612b15da6da0c9cc4aec487e8d24f371112" + integrity sha512-oAKwkj9xcWNBAvGbT//WiCdOMpb9XQG92/Fe3ABFM/R16BsHgePG00mFOgKf7IsCtfj8tA1kHtf/VwErhriz5Q== + +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + +cosmiconfig@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982" + integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.1.0" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.7.2" + +cosmiconfig@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" + integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + +cp-file@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cp-file/-/cp-file-7.0.0.tgz#b9454cfd07fe3b974ab9ea0e5f29655791a9b8cd" + integrity sha512-0Cbj7gyvFVApzpK/uhCtQ/9kE9UnYpxMzaq5nQQC/Dh4iaj5fxp7iEFIullrYwzj8nf0qnsI1Qsx34hAeAebvw== + dependencies: + graceful-fs "^4.1.2" + make-dir "^3.0.0" + nested-error-stacks "^2.0.0" + p-event "^4.1.0" + +cpy@^8.1.2: + version "8.1.2" + resolved "https://registry.yarnpkg.com/cpy/-/cpy-8.1.2.tgz#e339ea54797ad23f8e3919a5cffd37bfc3f25935" + integrity sha512-dmC4mUesv0OYH2kNFEidtf/skUwv4zePmGeepjyyJ0qTo5+8KhA1o99oIAwVVLzQMAeDJml74d6wPPKb6EZUTg== + dependencies: + arrify "^2.0.1" + cp-file "^7.0.0" + globby "^9.2.0" + has-glob "^1.0.0" + junk "^3.1.0" + nested-error-stacks "^2.1.0" + p-all "^2.1.0" + p-filter "^2.1.0" + p-map "^3.0.0" + +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +cross-spawn@^6.0.0: + version "6.0.5" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== + dependencies: + nice-try "^1.0.4" + path-key "^2.0.1" + semver "^5.5.0" + shebang-command "^1.2.0" + which "^1.2.9" + +cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + +css-loader@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-3.6.0.tgz#2e4b2c7e6e2d27f8c8f28f61bffcd2e6c91ef645" + integrity sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ== + dependencies: + camelcase "^5.3.1" + cssesc "^3.0.0" + icss-utils "^4.1.1" + loader-utils "^1.2.3" + normalize-path "^3.0.0" + postcss "^7.0.32" + postcss-modules-extract-imports "^2.0.0" + postcss-modules-local-by-default "^3.0.2" + postcss-modules-scope "^2.2.0" + postcss-modules-values "^3.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^2.7.0" + semver "^6.3.0" + +css-loader@^5.0.1: + version "5.2.7" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" + integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== + dependencies: + icss-utils "^5.1.0" + loader-utils "^2.0.0" + postcss "^8.2.15" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.1.0" + schema-utils "^3.0.0" + semver "^7.3.5" + +css-loader@^6.7.1: + version "6.7.1" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-6.7.1.tgz#e98106f154f6e1baf3fc3bc455cb9981c1d5fd2e" + integrity sha512-yB5CNFa14MbPJcomwNh3wLThtkZgcNyI2bNMRt8iE5Z8Vwl7f8vQXFAzn2HDOJvtDq2NTZBUGMSUNNyrv3/+cw== + dependencies: + icss-utils "^5.1.0" + postcss "^8.4.7" + postcss-modules-extract-imports "^3.0.0" + postcss-modules-local-by-default "^4.0.0" + postcss-modules-scope "^3.0.0" + postcss-modules-values "^4.0.0" + postcss-value-parser "^4.2.0" + semver "^7.3.5" + +css-select@^4.1.3: + version "4.3.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" + integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== + dependencies: + boolbase "^1.0.0" + css-what "^6.0.1" + domhandler "^4.3.1" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-what@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== + +cssom@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.5.0.tgz#d254fa92cd8b6fbd83811b9fbaed34663cc17c36" + integrity sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw== + +cssom@~0.3.6: + version "0.3.8" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" + integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== + +cssstyle@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" + integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== + dependencies: + cssom "~0.3.6" + +csstype@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.0.tgz#4ddcac3718d787cf9df0d1b7d15033925c8f29f2" + integrity sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA== + +currently-unhandled@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" + integrity sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng== + dependencies: + array-find-index "^1.0.1" + +cyclist@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" + integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A== + +data-urls@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-3.0.2.tgz#9cf24a477ae22bcef5cd5f6f0bfbc1d2d3be9143" + integrity sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ== + dependencies: + abab "^2.0.6" + whatwg-mimetype "^3.0.0" + whatwg-url "^11.0.0" + +debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + +debug@^3.0.0, debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +decamelize@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== + +decimal.js@^10.3.1: + version "10.3.1" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" + integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== + +dedent@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" + integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA== + +deep-is@^0.1.3, deep-is@~0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" + integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== + +deepmerge@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" + integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== + +default-browser-id@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-1.0.4.tgz#e59d09a5d157b828b876c26816e61c3d2a2c203a" + integrity sha512-qPy925qewwul9Hifs+3sx1ZYn14obHxpkX+mPD369w4Rzg+YkJBgi3SOvwUq81nWSjqGUegIgEPwD8u+HUnxlw== + dependencies: + bplist-parser "^0.1.0" + meow "^3.1.0" + untildify "^2.0.0" + +default-gateway@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" + integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== + dependencies: + execa "^5.0.0" + +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA== + dependencies: + clone "^1.0.2" + +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== + +define-properties@^1.1.2, define-properties@^1.1.3, define-properties@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" + integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== + dependencies: + has-property-descriptors "^1.0.0" + object-keys "^1.1.1" + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA== + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA== + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +del@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/del/-/del-4.1.1.tgz#9e8f117222ea44a31ff3a156c049b99052a9f0b4" + integrity sha512-QwGuEUouP2kVwQenAsOof5Fv8K9t3D8Ca8NxcXKrIpEHjTXK5J2nXLdP+ALI1cgv8wj7KuwBhTwBkOZSJKM5XQ== + dependencies: + "@types/glob" "^7.1.1" + globby "^6.1.0" + is-path-cwd "^2.0.0" + is-path-in-cwd "^2.0.0" + p-map "^2.0.0" + pify "^4.0.1" + rimraf "^2.6.3" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== + +depd@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== + +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + +destroy@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" + integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== + +detab@2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" + integrity sha512-8zdsQA5bIkoRECvCrNKPla84lyoR7DSAyf7p0YgXzBO9PDJx8KntPUay7NS6yp+KdxdVtiE5SpHKtbp2ZQyA9g== + dependencies: + repeat-string "^1.5.4" + +detect-newline@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" + integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== + +detect-node@^2.0.4, detect-node@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1" + integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g== + +detect-package-manager@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-package-manager/-/detect-package-manager-2.0.1.tgz#6b182e3ae5e1826752bfef1de9a7b828cffa50d8" + integrity sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A== + dependencies: + execa "^5.1.1" + +detect-port@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/detect-port/-/detect-port-1.3.0.tgz#d9c40e9accadd4df5cac6a782aefd014d573d1f1" + integrity sha512-E+B1gzkl2gqxt1IhUzwjrxBKRqx1UzC3WLONHinn8S3T6lwV/agVCyitiFOsGJ/eYuEUBvD71MZHy3Pv1G9doQ== + dependencies: + address "^1.0.1" + debug "^2.6.0" + +diff-sequences@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-28.1.1.tgz#9989dc731266dc2903457a70e996f3a041913ac6" + integrity sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw== + +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + +dir-glob@^2.2.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" + integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== + dependencies: + path-type "^3.0.0" + +dir-glob@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" + integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== + dependencies: + path-type "^4.0.0" + +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + integrity sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg== + +dns-packet@^5.2.2: + version "5.4.0" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-5.4.0.tgz#1f88477cf9f27e78a213fb6d118ae38e759a879b" + integrity sha512-EgqGeaBB8hLiHLZtp/IbaDQTL8pZ0+IvwzSHA6d7VyMDM+B9hgddEMa9xjK5oYnw0ci0JQ6g2XCD7/f6cafU6g== + dependencies: + "@leichtgewicht/ip-codec" "^2.0.1" + +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + +doctrine@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + +dom-accessibility-api@^0.5.9: + version "0.5.14" + resolved "https://registry.yarnpkg.com/dom-accessibility-api/-/dom-accessibility-api-0.5.14.tgz#56082f71b1dc7aac69d83c4285eef39c15d93f56" + integrity sha512-NMt+m9zFMPZe0JcY9gN224Qvk6qLIdqex29clBvc/y75ZBX9YA9wNK3frsYvu2DI1xcCIwxwnX+TlsJ2DSOADg== + +dom-converter@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768" + integrity sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA== + dependencies: + utila "~0.4" + +dom-serializer@^1.0.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" + integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +dom-walk@^0.1.0: + version "0.1.2" + resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.2.tgz#0c548bef048f4d1f2a97249002236060daa3fd84" + integrity sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w== + +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domexception@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" + integrity sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw== + dependencies: + webidl-conversions "^7.0.0" + +domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.5.2, domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + +dot-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" + integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +dotenv-defaults@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dotenv-defaults/-/dotenv-defaults-2.0.2.tgz#6b3ec2e4319aafb70940abda72d3856770ee77ac" + integrity sha512-iOIzovWfsUHU91L5i8bJce3NYK5JXeAwH50Jh6+ARUdLiiGlYWfGw6UkzsYqaXZH/hjE/eCd/PlfM/qqyK0AMg== + dependencies: + dotenv "^8.2.0" + +dotenv-expand@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" + integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== + +dotenv-webpack@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/dotenv-webpack/-/dotenv-webpack-8.0.0.tgz#bde8750eebda8fd1c6d7134f02bf56ad1f865772" + integrity sha512-vsWj11yWbIxLUPcQDbifCGW1+Mp03XfApFHJTC+/Ag9g3D/AnxoaVZcp76LpuBmReRwIJ+YO1fVdhmpzh+LL1A== + dependencies: + dotenv-defaults "^2.0.2" + +dotenv@^8.0.0, dotenv@^8.2.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + +duplexify@^3.4.2, duplexify@^3.6.0: + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== + dependencies: + end-of-stream "^1.0.0" + inherits "^2.0.1" + readable-stream "^2.0.0" + stream-shift "^1.0.0" + +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== + +electron-to-chromium@^1.4.172: + version "1.4.177" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.177.tgz#b6a4436eb788ca732556cd69f384b8a3c82118c5" + integrity sha512-FYPir3NSBEGexSZUEeht81oVhHfLFl6mhUKSkjHN/iB/TwEIt/WHQrqVGfTLN5gQxwJCQkIJBe05eOXjI7omgg== + +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + +emittery@^0.10.2: + version "0.10.2" + resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.10.2.tgz#902eec8aedb8c41938c46e9385e9db7e03182933" + integrity sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +emotion-reset@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/emotion-reset/-/emotion-reset-3.0.1.tgz#1445e66bab7e8fd9975ce0d8cd3324589981f0a6" + integrity sha512-v6scW83qSu+wtxg7lX1s0+/2U4EAAGFxDQMkvXE10jhKtyuXCzy3/su5/MU9ZjXeNv6ZjxZH51WktrKosKUy9g== + +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +end-of-stream@^1.0.0, end-of-stream@^1.1.0: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + +endent@^2.0.1: + version "2.1.0" + resolved "https://registry.yarnpkg.com/endent/-/endent-2.1.0.tgz#5aaba698fb569e5e18e69e1ff7a28ff35373cd88" + integrity sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w== + dependencies: + dedent "^0.7.0" + fast-json-parse "^1.0.3" + objectorarray "^1.0.5" + +enhanced-resolve@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.0.0, enhanced-resolve@^5.9.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" + integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + +envinfo@^7.7.3: + version "7.8.1" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475" + integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw== + +errno@^0.1.3, errno@~0.1.7: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" + +error-ex@^1.2.0, error-ex@^1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" + integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== + dependencies: + is-arrayish "^0.2.1" + +error-stack-parser@^2.0.6: + version "2.1.4" + resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.1.4.tgz#229cb01cdbfa84440bfa91876285b94680188286" + integrity sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ== + dependencies: + stackframe "^1.3.4" + +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.19.2, es-abstract@^1.19.5, es-abstract@^1.20.1: + version "1.20.1" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.1.tgz#027292cd6ef44bd12b1913b828116f54787d1814" + integrity sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA== + dependencies: + call-bind "^1.0.2" + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + function.prototype.name "^1.1.5" + get-intrinsic "^1.1.1" + get-symbol-description "^1.0.0" + has "^1.0.3" + has-property-descriptors "^1.0.0" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + is-callable "^1.2.4" + is-negative-zero "^2.0.2" + is-regex "^1.1.4" + is-shared-array-buffer "^1.0.2" + is-string "^1.0.7" + is-weakref "^1.0.2" + object-inspect "^1.12.0" + object-keys "^1.1.1" + object.assign "^4.1.2" + regexp.prototype.flags "^1.4.3" + string.prototype.trimend "^1.0.5" + string.prototype.trimstart "^1.0.5" + unbox-primitive "^1.0.2" + +es-array-method-boxes-properly@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz#873f3e84418de4ee19c5be752990b2e44718d09e" + integrity sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA== + +es-get-iterator@^1.0.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/es-get-iterator/-/es-get-iterator-1.1.2.tgz#9234c54aba713486d7ebde0220864af5e2b283f7" + integrity sha512-+DTO8GYwbMCwbywjimwZMHp8AuYXOS2JZFWoi2AlPOS3ebnII9w/NLpNZtA7A0YLaVDw+O7KFCeoIV7OPvM7hQ== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.0" + has-symbols "^1.0.1" + is-arguments "^1.1.0" + is-map "^2.0.2" + is-set "^2.0.2" + is-string "^1.0.5" + isarray "^2.0.5" + +es-module-lexer@^0.9.0: + version "0.9.3" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19" + integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ== + +es-shim-unscopables@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241" + integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w== + dependencies: + has "^1.0.3" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + +es5-shim@^4.5.13: + version "4.6.7" + resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.6.7.tgz#bc67ae0fc3dd520636e0a1601cc73b450ad3e955" + integrity sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ== + +es6-shim@^0.35.5: + version "0.35.6" + resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.6.tgz#d10578301a83af2de58b9eadb7c2c9945f7388a0" + integrity sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +escape-string-regexp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" + integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== + +escape-string-regexp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + +eslint-config-prettier@^8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" + integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + +eslint-import-resolver-node@^0.3.6: + version "0.3.6" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" + integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== + dependencies: + debug "^3.2.7" + resolve "^1.20.0" + +eslint-module-utils@^2.7.3: + version "2.7.3" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" + integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== + dependencies: + debug "^3.2.7" + find-up "^2.1.0" + +eslint-plugin-import@^2.26.0: + version "2.26.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.26.0.tgz#f812dc47be4f2b72b478a021605a59fc6fe8b88b" + integrity sha512-hYfi3FXaM8WPLf4S1cikh/r4IxnO6zrhZbEGz2b660EJRbuxgpDS5gkCuYgGWg2xxh2rBuIr4Pvhve/7c31koA== + dependencies: + array-includes "^3.1.4" + array.prototype.flat "^1.2.5" + debug "^2.6.9" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.6" + eslint-module-utils "^2.7.3" + has "^1.0.3" + is-core-module "^2.8.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.values "^1.1.5" + resolve "^1.22.0" + tsconfig-paths "^3.14.1" + +eslint-plugin-prettier@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" + integrity sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ== + dependencies: + prettier-linter-helpers "^1.0.0" + +eslint-plugin-react-hooks@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz#4c3e697ad95b77e93f8646aaa1630c1ba607edd3" + integrity sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g== + +eslint-plugin-react@^7.30.1: + version "7.30.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.30.1.tgz#2be4ab23ce09b5949c6631413ba64b2810fd3e22" + integrity sha512-NbEvI9jtqO46yJA3wcRF9Mo0lF9T/jhdHqhCHXiXtD+Zcb98812wvokjWpU7Q4QH5edo6dmqrukxVvWWXHlsUg== + dependencies: + array-includes "^3.1.5" + array.prototype.flatmap "^1.3.0" + doctrine "^2.1.0" + estraverse "^5.3.0" + jsx-ast-utils "^2.4.1 || ^3.0.0" + minimatch "^3.1.2" + object.entries "^1.1.5" + object.fromentries "^2.0.5" + object.hasown "^1.1.1" + object.values "^1.1.5" + prop-types "^15.8.1" + resolve "^2.0.0-next.3" + semver "^6.3.0" + string.prototype.matchall "^4.0.7" + +eslint-scope@5.1.1, eslint-scope@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +eslint-scope@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" + integrity sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg== + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint-scope@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" + integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== + dependencies: + esrecurse "^4.3.0" + estraverse "^5.2.0" + +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" + integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== + +eslint-visitor-keys@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" + integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== + +eslint@^8.19.0: + version "8.19.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.19.0.tgz#7342a3cbc4fbc5c106a1eefe0fd0b50b6b1a7d28" + integrity sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw== + dependencies: + "@eslint/eslintrc" "^1.3.0" + "@humanwhocodes/config-array" "^0.9.2" + ajv "^6.10.0" + chalk "^4.0.0" + cross-spawn "^7.0.2" + debug "^4.3.2" + doctrine "^3.0.0" + escape-string-regexp "^4.0.0" + eslint-scope "^7.1.1" + eslint-utils "^3.0.0" + eslint-visitor-keys "^3.3.0" + espree "^9.3.2" + esquery "^1.4.0" + esutils "^2.0.2" + fast-deep-equal "^3.1.3" + file-entry-cache "^6.0.1" + functional-red-black-tree "^1.0.1" + glob-parent "^6.0.1" + globals "^13.15.0" + ignore "^5.2.0" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + is-glob "^4.0.0" + js-yaml "^4.1.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.4.1" + lodash.merge "^4.6.2" + minimatch "^3.1.2" + natural-compare "^1.4.0" + optionator "^0.9.1" + regexpp "^3.2.0" + strip-ansi "^6.0.1" + strip-json-comments "^3.1.0" + text-table "^0.2.0" + v8-compile-cache "^2.0.3" + +espree@^9.3.2: + version "9.3.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596" + integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA== + dependencies: + acorn "^8.7.1" + acorn-jsx "^5.3.2" + eslint-visitor-keys "^3.3.0" + +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + +esquery@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" + integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== + dependencies: + estraverse "^5.1.0" + +esrecurse@^4.1.0, esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +estree-to-babel@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/estree-to-babel/-/estree-to-babel-3.2.1.tgz#82e78315275c3ca74475fdc8ac1a5103c8a75bf5" + integrity sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg== + dependencies: + "@babel/traverse" "^7.1.6" + "@babel/types" "^7.2.0" + c8 "^7.6.0" + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== + +eventemitter3@^4.0.0: + version "4.0.7" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" + integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== + +events@^3.0.0, events@^3.2.0, events@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + +execa@^5.0.0, execa@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^6.0.0" + human-signals "^2.1.0" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.1" + onetime "^5.1.2" + signal-exit "^3.0.3" + strip-final-newline "^2.0.0" + +exit@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" + integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA== + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expect@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/expect/-/expect-28.1.3.tgz#90a7c1a124f1824133dd4533cce2d2bdcb6603ec" + integrity sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g== + dependencies: + "@jest/expect-utils" "^28.1.3" + jest-get-type "^28.0.2" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + +express@^4.17.1, express@^4.17.3: + version "4.18.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" + integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.0" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.5.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.10.3" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug== + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q== + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +external-editor@^3.0.3: + version "3.1.0" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" + integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-diff@^1.1.2: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" + integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== + +fast-glob@^2.2.6: + version "2.2.7" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" + integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== + dependencies: + "@mrmlnc/readdir-enhanced" "^2.2.1" + "@nodelib/fs.stat" "^1.1.2" + glob-parent "^3.1.0" + is-glob "^4.0.0" + merge2 "^1.2.3" + micromatch "^3.1.10" + +fast-glob@^3.2.9: + version "3.2.11" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" + integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + +fast-json-parse@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fast-json-parse/-/fast-json-parse-1.0.3.tgz#43e5c61ee4efa9265633046b770fb682a7577c4d" + integrity sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + +fastq@^1.6.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" + integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + dependencies: + reusify "^1.0.4" + +fault@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" + integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== + dependencies: + format "^0.2.0" + +faye-websocket@^0.11.3: + version "0.11.4" + resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.4.tgz#7f0d9275cfdd86a1c963dc8b65fcc451edcbb1da" + integrity sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g== + dependencies: + websocket-driver ">=0.5.1" + +fb-watchman@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" + integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== + dependencies: + bser "2.1.1" + +fetch-retry@^5.0.2: + version "5.0.3" + resolved "https://registry.yarnpkg.com/fetch-retry/-/fetch-retry-5.0.3.tgz#edfa3641892995f9afee94f25b168827aa97fe3d" + integrity sha512-uJQyMrX5IJZkhoEUBQ3EjxkeiZkppBd5jS/fMTJmfZxLSiaQjv2zD0kTvuvkSH89uFvgSlB6ueGpjD3HWN7Bxw== + +figgy-pudding@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" + integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== + +figures@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" + integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" + integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== + dependencies: + flat-cache "^3.0.4" + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +file-system-cache@^1.0.5: + version "1.1.0" + resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.1.0.tgz#984de17b976b75a77a27e08d6828137c1aa80fa1" + integrity sha512-IzF5MBq+5CR0jXx5RxPe4BICl/oEhBSXKaL9fLhAXrIfIUS77Hr4vzrYyqYMHN6uTt+BOqi3fDCTjjEBCjERKw== + dependencies: + fs-extra "^10.1.0" + ramda "^0.28.0" + +file-uri-to-path@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" + integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ== + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + +find-cache-dir@^2.0.0, find-cache-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" + integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ== + dependencies: + commondir "^1.0.1" + make-dir "^2.0.0" + pkg-dir "^3.0.0" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + integrity sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA== + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + integrity sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ== + dependencies: + locate-path "^2.0.0" + +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== + dependencies: + locate-path "^3.0.0" + +find-up@^4.0.0, find-up@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat-cache@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" + integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + dependencies: + flatted "^3.1.0" + rimraf "^3.0.2" + +flatted@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.6.tgz#022e9218c637f9f3fc9c35ab9c9193f05add60b2" + integrity sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ== + +flush-write-stream@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== + dependencies: + inherits "^2.0.3" + readable-stream "^2.3.6" + +focus-lock@^0.8.0: + version "0.8.1" + resolved "https://registry.yarnpkg.com/focus-lock/-/focus-lock-0.8.1.tgz#bb36968abf77a2063fa173cb6c47b12ac8599d33" + integrity sha512-/LFZOIo82WDsyyv7h7oc0MJF9ACOvDRdx9rWPZ2pgMfNWu/z8hQDBtOchuB/0BVLmuFOZjV02YwUVzNsWx/EzA== + dependencies: + tslib "^1.9.3" + +follow-redirects@^1.0.0, follow-redirects@^1.14.9: + version "1.15.1" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.1.tgz#0ca6a452306c9b276e4d3127483e29575e207ad5" + integrity sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA== + +for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ== + +foreground-child@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" + integrity sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^3.0.2" + +fork-ts-checker-webpack-plugin@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-4.1.6.tgz#5055c703febcf37fa06405d400c122b905167fc5" + integrity sha512-DUxuQaKoqfNne8iikd14SAkh5uw4+8vNifp6gmA73yYNS6ywLIWSLD/n/mBzHQRpW3J7rbATEakmiA8JvkTyZw== + dependencies: + "@babel/code-frame" "^7.5.5" + chalk "^2.4.1" + micromatch "^3.1.10" + minimatch "^3.0.4" + semver "^5.6.0" + tapable "^1.0.0" + worker-rpc "^0.1.0" + +fork-ts-checker-webpack-plugin@^6.0.4: + version "6.5.2" + resolved "https://registry.yarnpkg.com/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-6.5.2.tgz#4f67183f2f9eb8ba7df7177ce3cf3e75cdafb340" + integrity sha512-m5cUmF30xkZ7h4tWUgTAcEaKmUW7tfyUyTqNNOz7OxWJ0v1VWKTcOvH8FWHUwSjlW/356Ijc9vi3XfcPstpQKA== + dependencies: + "@babel/code-frame" "^7.8.3" + "@types/json-schema" "^7.0.5" + chalk "^4.1.0" + chokidar "^3.4.2" + cosmiconfig "^6.0.0" + deepmerge "^4.2.2" + fs-extra "^9.0.0" + glob "^7.1.6" + memfs "^3.1.2" + minimatch "^3.0.4" + schema-utils "2.7.0" + semver "^7.3.2" + tapable "^1.0.0" + +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + +format@^0.2.0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" + integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww== + +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA== + dependencies: + map-cache "^0.2.2" + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== + +from2@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g== + dependencies: + inherits "^2.0.1" + readable-stream "^2.0.0" + +fs-extra@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" + integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-extra@^9.0.0, fs-extra@^9.0.1: + version "9.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + +fs-minipass@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" + integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== + dependencies: + minipass "^3.0.0" + +fs-monkey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" + integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== + +fs-write-stream-atomic@^1.0.8: + version "1.0.10" + resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA== + dependencies: + graceful-fs "^4.1.2" + iferr "^0.1.5" + imurmurhash "^0.1.4" + readable-stream "1 || 2" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + +function.prototype.name@^1.1.0, function.prototype.name@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" + integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.0" + functions-have-names "^1.2.2" + +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== + +functions-have-names@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" + integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== + +gauge@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395" + integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q== + dependencies: + aproba "^1.0.3 || ^2.0.0" + color-support "^1.1.2" + console-control-strings "^1.0.0" + has-unicode "^2.0.1" + object-assign "^4.1.1" + signal-exit "^3.0.0" + string-width "^4.2.3" + strip-ansi "^6.0.1" + wide-align "^1.1.2" + +gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" + integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-package-type@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" + integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== + +get-stdin@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" + integrity sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw== + +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + +get-stream@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" + integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== + +get-symbol-description@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" + integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== + dependencies: + call-bind "^1.0.2" + get-intrinsic "^1.1.1" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA== + +github-slugger@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e" + integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ== + +glob-parent@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA== + dependencies: + is-glob "^3.1.0" + path-dirname "^1.0.0" + +glob-parent@^5.1.2, glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-parent@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" + integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== + dependencies: + is-glob "^4.0.3" + +glob-promise@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/glob-promise/-/glob-promise-3.4.0.tgz#b6b8f084504216f702dc2ce8c9bc9ac8866fdb20" + integrity sha512-q08RJ6O+eJn+dVanerAndJwIcumgbDdYiUT7zFQl3Wm1xD6fBKtah7H8ZJChj4wP+8C+QfeVy8xautR7rdmKEw== + dependencies: + "@types/glob" "*" + +glob-to-regexp@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" + integrity sha512-Iozmtbqv0noj0uDDqoL0zNq0VBEfK2YFoMAZoxJe4cwphvLR+JskfF30QhXHOR4m3KrE6NLRYw+U9MRXvifyig== + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +glob@^7.0.3, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: + version "7.2.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" + integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.1.1" + once "^1.3.0" + path-is-absolute "^1.0.0" + +global@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/global/-/global-4.4.0.tgz#3e7b105179006a323ed71aafca3e9c57a5cc6406" + integrity sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w== + dependencies: + min-document "^2.19.0" + process "^0.11.10" + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +globals@^13.15.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.15.0.tgz#38113218c907d2f7e98658af246cef8b77e90bac" + integrity sha512-bpzcOlgDhMG070Av0Vy5Owklpv1I6+j96GhUI7Rh7IzDCKLzboflLrrfqMu8NquDbiR4EOQk7XzJwqVJxicxog== + dependencies: + type-fest "^0.20.2" + +globalthis@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" + integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== + dependencies: + define-properties "^1.1.3" + +globby@^11.0.2, globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + integrity sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw== + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +globby@^9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" + integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== + dependencies: + "@types/glob" "^7.1.1" + array-union "^1.0.2" + dir-glob "^2.2.2" + fast-glob "^2.2.6" + glob "^7.1.3" + ignore "^4.0.3" + pify "^4.0.1" + slash "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + +graphql@^16.3.0: + version "16.5.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" + integrity sha512-qbHgh8Ix+j/qY+a/ZcJnFQ+j8ezakqPiHwPiZhV/3PgGlgf96QMBB5/f2rkiC9sgLoy/xvT6TSiaf2nTHJh5iA== + +hamt_plus@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/hamt_plus/-/hamt_plus-1.0.2.tgz#e21c252968c7e33b20f6a1b094cd85787a265601" + integrity sha512-t2JXKaehnMb9paaYA7J0BX8QQAY8lwfQ9Gjf4pg/mk4krt+cmwmU652HOoWonf+7+EQV97ARPMhhVgU1ra2GhA== + +handle-thing@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" + integrity sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg== + +handlebars@^4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + +has-bigints@^1.0.1, has-bigints@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" + integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +has-glob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-glob/-/has-glob-1.0.0.tgz#9aaa9eedbffb1ba3990a7b0010fb678ee0081207" + integrity sha512-D+8A457fBShSEI3tFCj65PAbT++5sKiFtdCdOam0gnfBgw9D277OERk+HM9qYJXmdVLZ/znez10SqHN0BBQ50g== + dependencies: + is-glob "^3.0.0" + +has-property-descriptors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861" + integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ== + dependencies: + get-intrinsic "^1.1.1" + +has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" + integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== + +has-tostringtag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25" + integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ== + dependencies: + has-symbols "^1.0.2" + +has-unicode@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q== + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw== + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ== + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ== + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + +hast-to-hyperscript@^9.0.0: + version "9.0.1" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz#9b67fd188e4c81e8ad66f803855334173920218d" + integrity sha512-zQgLKqF+O2F72S1aa4y2ivxzSlko3MAvxkwG8ehGmNiqd98BIN3JM1rAJPmplEyLmGLO2QZYJtIneOSZ2YbJuA== + dependencies: + "@types/unist" "^2.0.3" + comma-separated-tokens "^1.0.0" + property-information "^5.3.0" + space-separated-tokens "^1.0.0" + style-to-object "^0.3.0" + unist-util-is "^4.0.0" + web-namespaces "^1.0.0" + +hast-util-from-parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" + integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== + dependencies: + "@types/parse5" "^5.0.0" + hastscript "^6.0.0" + property-information "^5.0.0" + vfile "^4.0.0" + vfile-location "^3.2.0" + web-namespaces "^1.0.0" + +hast-util-parse-selector@^2.0.0: + version "2.2.5" + resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" + integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== + +hast-util-raw@6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.0.1.tgz#973b15930b7529a7b66984c98148b46526885977" + integrity sha512-ZMuiYA+UF7BXBtsTBNcLBF5HzXzkyE6MLzJnL605LKE8GJylNjGc4jjxazAHUtcwT5/CEt6afRKViYB4X66dig== + dependencies: + "@types/hast" "^2.0.0" + hast-util-from-parse5 "^6.0.0" + hast-util-to-parse5 "^6.0.0" + html-void-elements "^1.0.0" + parse5 "^6.0.0" + unist-util-position "^3.0.0" + vfile "^4.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hast-util-to-parse5@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" + integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== + dependencies: + hast-to-hyperscript "^9.0.0" + property-information "^5.0.0" + web-namespaces "^1.0.0" + xtend "^4.0.0" + zwitch "^1.0.0" + +hastscript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-6.0.0.tgz#e8768d7eac56c3fdeac8a92830d58e811e5bf640" + integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w== + dependencies: + "@types/hast" "^2.0.0" + comma-separated-tokens "^1.0.0" + hast-util-parse-selector "^2.0.0" + property-information "^5.0.0" + space-separated-tokens "^1.0.0" + +he@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +headers-polyfill@^3.0.4: + version "3.0.7" + resolved "https://registry.yarnpkg.com/headers-polyfill/-/headers-polyfill-3.0.7.tgz#725c4f591e6748f46b036197eae102c92b959ff4" + integrity sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w== + +highlight.js@^10.4.1, highlight.js@~10.7.0: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + +history@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-5.3.0.tgz#1548abaa245ba47992f063a0783db91ef201c73b" + integrity sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ== + dependencies: + "@babel/runtime" "^7.7.6" + +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg== + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + +hpack.js@^2.1.6: + version "2.1.6" + resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" + integrity sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ== + dependencies: + inherits "^2.0.1" + obuf "^1.0.0" + readable-stream "^2.0.1" + wbuf "^1.1.0" + +html-encoding-sniffer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz#2cb1a8cf0db52414776e5b2a7a04d5dd98158de9" + integrity sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA== + dependencies: + whatwg-encoding "^2.0.0" + +html-entities@^2.1.0, html-entities@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46" + integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA== + +html-escaper@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" + integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== + +html-minifier-terser@^5.0.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-5.1.1.tgz#922e96f1f3bb60832c2634b79884096389b1f054" + integrity sha512-ZPr5MNObqnV/T9akshPKbVgyOqLmy+Bxo7juKCfTfnjNniTAMdy4hz21YQqoofMBJD2kdREaqPPdThoR78Tgxg== + dependencies: + camel-case "^4.1.1" + clean-css "^4.2.3" + commander "^4.1.1" + he "^1.2.0" + param-case "^3.0.3" + relateurl "^0.2.7" + terser "^4.6.3" + +html-minifier-terser@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab" + integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw== + dependencies: + camel-case "^4.1.2" + clean-css "^5.2.2" + commander "^8.3.0" + he "^1.2.0" + param-case "^3.0.4" + relateurl "^0.2.7" + terser "^5.10.0" + +html-tags@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.2.0.tgz#dbb3518d20b726524e4dd43de397eb0a95726961" + integrity sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg== + +html-void-elements@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.5.tgz#ce9159494e86d95e45795b166c2021c2cfca4483" + integrity sha512-uE/TxKuyNIcx44cIWnjr/rfIATDH7ZaOMmstu0CwhFG1Dunhlp4OC6/NMbhiwoq5BpW0ubi303qnEk/PZj614w== + +html-webpack-plugin@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-4.5.2.tgz#76fc83fa1a0f12dd5f7da0404a54e2699666bc12" + integrity sha512-q5oYdzjKUIPQVjOosjgvCHQOv9Ett9CYYHlgvJeXG0qQvdSojnBq4vAdQBwn1+yGveAwHCoe/rMR86ozX3+c2A== + dependencies: + "@types/html-minifier-terser" "^5.0.0" + "@types/tapable" "^1.0.5" + "@types/webpack" "^4.41.8" + html-minifier-terser "^5.0.1" + loader-utils "^1.2.3" + lodash "^4.17.20" + pretty-error "^2.1.1" + tapable "^1.1.3" + util.promisify "1.0.0" + +html-webpack-plugin@^5.0.0, html-webpack-plugin@^5.5.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-5.5.0.tgz#c3911936f57681c1f9f4d8b68c158cd9dfe52f50" + integrity sha512-sy88PC2cRTVxvETRgUHFrL4No3UxvcH8G1NepGhqaTT+GXN2kTamqasot0inS5hXeg1cMbFDt27zzo9p35lZVw== + dependencies: + "@types/html-minifier-terser" "^6.0.0" + html-minifier-terser "^6.0.2" + lodash "^4.17.21" + pretty-error "^4.0.0" + tapable "^2.0.0" + +htmlparser2@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" + integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.0.0" + domutils "^2.5.2" + entities "^2.0.0" + +http-deceiver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" + integrity sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw== + +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + +http-errors@~1.6.2: + version "1.6.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" + integrity sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.0" + statuses ">= 1.4.0 < 2" + +http-parser-js@>=0.5.1: + version "0.5.8" + resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.8.tgz#af23090d9ac4e24573de6f6aecc9d84a48bf20e3" + integrity sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q== + +http-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz#5129800203520d434f142bc78ff3c170800f2b43" + integrity sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w== + dependencies: + "@tootallnate/once" "2" + agent-base "6" + debug "4" + +http-proxy-middleware@^2.0.3: + version "2.0.6" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz#e1a4dd6979572c7ab5a4e4b55095d1f32a74963f" + integrity sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw== + dependencies: + "@types/http-proxy" "^1.17.8" + http-proxy "^1.18.1" + is-glob "^4.0.1" + is-plain-obj "^3.0.0" + micromatch "^4.0.2" + +http-proxy@^1.18.1: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg== + +https-proxy-agent@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" + integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== + dependencies: + agent-base "6" + debug "4" + +human-signals@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" + integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +iconv-lite@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + +icss-utils@^4.0.0, icss-utils@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" + integrity sha512-4aFq7wvWyMHKgxsH8QQtGpvbASCf+eM3wPRLI6R+MgAnTCZ6STYsRvttLvRWK0Nfif5piF394St3HeJDaljGPA== + dependencies: + postcss "^7.0.14" + +icss-utils@^5.0.0, icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== + +ieee754@^1.1.13, ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + +iferr@^0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA== + +ignore@^4.0.3: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +ignore@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" + integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== + +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" + integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + +import-local@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4" + integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== + +indent-string@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg== + dependencies: + repeating "^2.0.0" + +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + +infer-owner@^1.0.3, infer-owner@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467" + integrity sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@2.0.4, inherits@^2.0.0, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA== + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw== + +inline-style-parser@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" + integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== + +inquirer@^8.2.0: + version "8.2.4" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" + integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + wrap-ansi "^7.0.0" + +internal-slot@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" + integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== + dependencies: + get-intrinsic "^1.1.0" + has "^1.0.3" + side-channel "^1.0.4" + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + +ipaddr.js@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" + integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== + +is-absolute-url@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698" + integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q== + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A== + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-alphabetical@1.0.4, is-alphabetical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" + integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg== + +is-alphanumerical@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz#7eb9a2431f855f6b1ef1a78e326df515696c4dbf" + integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A== + dependencies: + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + +is-arguments@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" + integrity sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== + +is-bigint@^1.0.1: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" + integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== + dependencies: + has-bigints "^1.0.1" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q== + dependencies: + binary-extensions "^1.0.0" + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-boolean-object@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" + integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-buffer@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" + integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== + +is-callable@^1.1.4, is-callable@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" + integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== + +is-ci@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" + integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== + dependencies: + ci-info "^2.0.0" + +is-core-module@^2.8.1, is-core-module@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" + integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== + dependencies: + has "^1.0.3" + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg== + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-date-object@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" + integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ== + dependencies: + has-tostringtag "^1.0.0" + +is-decimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" + integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== + +is-dom@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-dom/-/is-dom-1.1.0.tgz#af1fced292742443bb59ca3f76ab5e80907b4e8a" + integrity sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ== + dependencies: + is-object "^1.0.1" + is-window "^1.0.2" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw== + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^2.1.0, is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-finite@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" + integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-function@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" + integrity sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ== + +is-generator-fn@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" + integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== + +is-glob@^3.0.0, is-glob@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw== + dependencies: + is-extglob "^2.1.0" + +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-hexadecimal@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" + integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== + +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + +is-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.2.tgz#00922db8c9bf73e81b7a335827bc2a43f2b91127" + integrity sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg== + +is-negative-zero@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" + integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== + +is-node-process@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.0.1.tgz#4fc7ac3a91e8aac58175fe0578abbc56f2831b23" + integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== + +is-number-object@^1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" + integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== + dependencies: + has-tostringtag "^1.0.0" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg== + dependencies: + kind-of "^3.0.2" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-object@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.2.tgz#a56552e1c665c9e950b4a025461da87e72f86fcf" + integrity sha512-2rRIahhZr2UWb45fIOuvZGpFtz0TyOZLf32KxBbSoUCeZR495zCKlWUKKUByk3geS2eAs7ZAABt0Y/Rx0GiQGA== + +is-path-cwd@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" + integrity sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ== + +is-path-in-cwd@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-2.1.0.tgz#bfe2dca26c69f397265a4009963602935a053acb" + integrity sha512-rNocXHgipO+rvnP6dk3zI20RpOtrAM/kzbB258Uw5BWr3TpXi861yzjo16Dn4hUox07iw5AyeMLHWsujkjzvRQ== + dependencies: + is-path-inside "^2.1.0" + +is-path-inside@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-2.1.0.tgz#7c9810587d659a40d27bcdb4d5616eab059494b2" + integrity sha512-wiyhTzfDWsvwAW53OBWF5zuvaOGlZ6PwYxAbPVDhpm+gM09xKQGjBq/8uYN12aDvMxnAnq3dxTyoSoRNmg5YFg== + dependencies: + path-is-inside "^1.0.2" + +is-plain-obj@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-plain-obj@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7" + integrity sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA== + +is-plain-object@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344" + integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q== + +is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + +is-regex@^1.1.2, is-regex@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" + integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== + dependencies: + call-bind "^1.0.2" + has-tostringtag "^1.0.0" + +is-set@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" + integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== + +is-shared-array-buffer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" + integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== + dependencies: + call-bind "^1.0.2" + +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ== + +is-stream@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" + integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== + +is-string@^1.0.5, is-string@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" + integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== + dependencies: + has-tostringtag "^1.0.0" + +is-symbol@^1.0.2, is-symbol@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== + dependencies: + has-symbols "^1.0.2" + +is-typedarray@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + integrity sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q== + +is-weakref@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" + integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== + dependencies: + call-bind "^1.0.2" + +is-whitespace-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz#0858edd94a95594c7c9dd0b5c174ec6e45ee4aa7" + integrity sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w== + +is-window@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-window/-/is-window-1.0.2.tgz#2c896ca53db97de45d3c33133a65d8c9f563480d" + integrity sha512-uj00kdXyZb9t9RcAUAwMZAnkBUwdYGhYlt7djMXhfyhUCzwNba50tIiBKR7q0l7tdoBtFVw/3JmLY6fI3rmZmg== + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +is-word-character@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-word-character/-/is-word-character-1.0.4.tgz#ce0e73216f98599060592f62ff31354ddbeb0230" + integrity sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA== + +is-wsl@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" + integrity sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw== + +is-wsl@^2.1.1, is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== + +isarray@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" + integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA== + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +isobject@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" + integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== + +isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + +istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" + integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== + +istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz#31d18bdd127f825dd02ea7bfdfd906f8ab840e9f" + integrity sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A== + dependencies: + "@babel/core" "^7.12.3" + "@babel/parser" "^7.14.7" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.2.0" + semver "^6.3.0" + +istanbul-lib-report@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" + integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== + dependencies: + istanbul-lib-coverage "^3.0.0" + make-dir "^3.0.0" + supports-color "^7.1.0" + +istanbul-lib-source-maps@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551" + integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw== + dependencies: + debug "^4.1.1" + istanbul-lib-coverage "^3.0.0" + source-map "^0.6.1" + +istanbul-reports@^3.1.3: + version "3.1.5" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae" + integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +istanbul-reports@^3.1.4: + version "3.1.4" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" + integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== + dependencies: + html-escaper "^2.0.0" + istanbul-lib-report "^3.0.0" + +iterate-iterator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/iterate-iterator/-/iterate-iterator-1.0.2.tgz#551b804c9eaa15b847ea6a7cdc2f5bf1ec150f91" + integrity sha512-t91HubM4ZDQ70M9wqp+pcNpu8OyJ9UAtXntT/Bcsvp5tZMnz9vRa+IunKXeI8AnfZMTv0jNuVEmGeLSMjVvfPw== + +iterate-value@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/iterate-value/-/iterate-value-1.0.2.tgz#935115bd37d006a52046535ebc8d07e9c9337f57" + integrity sha512-A6fMAio4D2ot2r/TYzr4yUWrmwNdsN5xL7+HUiyACE4DXm+q8HtPcnFTp+NnW3k4N05tZ7FVYFFb2CR13NxyHQ== + dependencies: + es-get-iterator "^1.0.2" + iterate-iterator "^1.0.1" + +javascript-natural-sort@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== + +jest-changed-files@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-28.1.3.tgz#d9aeee6792be3686c47cb988a8eaf82ff4238831" + integrity sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA== + dependencies: + execa "^5.0.0" + p-limit "^3.1.0" + +jest-circus@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-28.1.3.tgz#d14bd11cf8ee1a03d69902dc47b6bd4634ee00e4" + integrity sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/expect" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + dedent "^0.7.0" + is-generator-fn "^2.0.0" + jest-each "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-runtime "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + p-limit "^3.1.0" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-cli@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-28.1.3.tgz#558b33c577d06de55087b8448d373b9f654e46b2" + integrity sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ== + dependencies: + "@jest/core" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.9" + import-local "^3.0.2" + jest-config "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + prompts "^2.0.1" + yargs "^17.3.1" + +jest-config@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-28.1.3.tgz#e315e1f73df3cac31447eed8b8740a477392ec60" + integrity sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ== + dependencies: + "@babel/core" "^7.11.6" + "@jest/test-sequencer" "^28.1.3" + "@jest/types" "^28.1.3" + babel-jest "^28.1.3" + chalk "^4.0.0" + ci-info "^3.2.0" + deepmerge "^4.2.2" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-circus "^28.1.3" + jest-environment-node "^28.1.3" + jest-get-type "^28.0.2" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-runner "^28.1.3" + jest-util "^28.1.3" + jest-validate "^28.1.3" + micromatch "^4.0.4" + parse-json "^5.2.0" + pretty-format "^28.1.3" + slash "^3.0.0" + strip-json-comments "^3.1.1" + +jest-diff@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-28.1.3.tgz#948a192d86f4e7a64c5264ad4da4877133d8792f" + integrity sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw== + dependencies: + chalk "^4.0.0" + diff-sequences "^28.1.1" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-docblock@^28.1.1: + version "28.1.1" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-28.1.1.tgz#6f515c3bf841516d82ecd57a62eed9204c2f42a8" + integrity sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA== + dependencies: + detect-newline "^3.0.0" + +jest-each@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-28.1.3.tgz#bdd1516edbe2b1f3569cfdad9acd543040028f81" + integrity sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g== + dependencies: + "@jest/types" "^28.1.3" + chalk "^4.0.0" + jest-get-type "^28.0.2" + jest-util "^28.1.3" + pretty-format "^28.1.3" + +jest-environment-jsdom@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-28.1.3.tgz#2d4e5d61b7f1d94c3bddfbb21f0308ee506c09fb" + integrity sha512-HnlGUmZRdxfCByd3GM2F100DgQOajUBzEitjGqIREcb45kGjZvRrKUdlaF6escXBdcXNl0OBh+1ZrfeZT3GnAg== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/jsdom" "^16.2.4" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + jsdom "^19.0.0" + +jest-environment-node@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-28.1.3.tgz#7e74fe40eb645b9d56c0c4b70ca4357faa349be5" + integrity sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + jest-mock "^28.1.3" + jest-util "^28.1.3" + +jest-get-type@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-28.0.2.tgz#34622e628e4fdcd793d46db8a242227901fcf203" + integrity sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA== + +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + +jest-haste-map@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-28.1.3.tgz#abd5451129a38d9841049644f34b034308944e2b" + integrity sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA== + dependencies: + "@jest/types" "^28.1.3" + "@types/graceful-fs" "^4.1.3" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.9" + jest-regex-util "^28.0.2" + jest-util "^28.1.3" + jest-worker "^28.1.3" + micromatch "^4.0.4" + walker "^1.0.8" + optionalDependencies: + fsevents "^2.3.2" + +jest-leak-detector@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz#a6685d9b074be99e3adee816ce84fd30795e654d" + integrity sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA== + dependencies: + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-matcher-utils@^28.0.0, jest-matcher-utils@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz#5a77f1c129dd5ba3b4d7fc20728806c78893146e" + integrity sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw== + dependencies: + chalk "^4.0.0" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + pretty-format "^28.1.3" + +jest-message-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-28.1.3.tgz#232def7f2e333f1eecc90649b5b94b0055e7c43d" + integrity sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g== + dependencies: + "@babel/code-frame" "^7.12.13" + "@jest/types" "^28.1.3" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.9" + micromatch "^4.0.4" + pretty-format "^28.1.3" + slash "^3.0.0" + stack-utils "^2.0.3" + +jest-mock@^27.0.6: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" + integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og== + dependencies: + "@jest/types" "^27.5.1" + "@types/node" "*" + +jest-mock@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-28.1.3.tgz#d4e9b1fc838bea595c77ab73672ebf513ab249da" + integrity sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== + +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + +jest-regex-util@^28.0.2: + version "28.0.2" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-28.0.2.tgz#afdc377a3b25fb6e80825adcf76c854e5bf47ead" + integrity sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw== + +jest-resolve-dependencies@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz#8c65d7583460df7275c6ea2791901fa975c1fe66" + integrity sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA== + dependencies: + jest-regex-util "^28.0.2" + jest-snapshot "^28.1.3" + +jest-resolve@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-28.1.3.tgz#cfb36100341ddbb061ec781426b3c31eb51aa0a8" + integrity sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ== + dependencies: + chalk "^4.0.0" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-pnp-resolver "^1.2.2" + jest-util "^28.1.3" + jest-validate "^28.1.3" + resolve "^1.20.0" + resolve.exports "^1.1.0" + slash "^3.0.0" + +jest-runner@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-28.1.3.tgz#5eee25febd730b4713a2cdfd76bdd5557840f9a1" + integrity sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA== + dependencies: + "@jest/console" "^28.1.3" + "@jest/environment" "^28.1.3" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.10.2" + graceful-fs "^4.2.9" + jest-docblock "^28.1.1" + jest-environment-node "^28.1.3" + jest-haste-map "^28.1.3" + jest-leak-detector "^28.1.3" + jest-message-util "^28.1.3" + jest-resolve "^28.1.3" + jest-runtime "^28.1.3" + jest-util "^28.1.3" + jest-watcher "^28.1.3" + jest-worker "^28.1.3" + p-limit "^3.1.0" + source-map-support "0.5.13" + +jest-runtime@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-28.1.3.tgz#a57643458235aa53e8ec7821949e728960d0605f" + integrity sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw== + dependencies: + "@jest/environment" "^28.1.3" + "@jest/fake-timers" "^28.1.3" + "@jest/globals" "^28.1.3" + "@jest/source-map" "^28.1.2" + "@jest/test-result" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + chalk "^4.0.0" + cjs-module-lexer "^1.0.0" + collect-v8-coverage "^1.0.0" + execa "^5.0.0" + glob "^7.1.3" + graceful-fs "^4.2.9" + jest-haste-map "^28.1.3" + jest-message-util "^28.1.3" + jest-mock "^28.1.3" + jest-regex-util "^28.0.2" + jest-resolve "^28.1.3" + jest-snapshot "^28.1.3" + jest-util "^28.1.3" + slash "^3.0.0" + strip-bom "^4.0.0" + +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + +jest-snapshot@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-28.1.3.tgz#17467b3ab8ddb81e2f605db05583d69388fc0668" + integrity sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg== + dependencies: + "@babel/core" "^7.11.6" + "@babel/generator" "^7.7.2" + "@babel/plugin-syntax-typescript" "^7.7.2" + "@babel/traverse" "^7.7.2" + "@babel/types" "^7.3.3" + "@jest/expect-utils" "^28.1.3" + "@jest/transform" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/babel__traverse" "^7.0.6" + "@types/prettier" "^2.1.5" + babel-preset-current-node-syntax "^1.0.0" + chalk "^4.0.0" + expect "^28.1.3" + graceful-fs "^4.2.9" + jest-diff "^28.1.3" + jest-get-type "^28.0.2" + jest-haste-map "^28.1.3" + jest-matcher-utils "^28.1.3" + jest-message-util "^28.1.3" + jest-util "^28.1.3" + natural-compare "^1.4.0" + pretty-format "^28.1.3" + semver "^7.3.5" + +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + +jest-util@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-28.1.3.tgz#f4f932aa0074f0679943220ff9cbba7e497028b0" + integrity sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ== + dependencies: + "@jest/types" "^28.1.3" + "@types/node" "*" + chalk "^4.0.0" + ci-info "^3.2.0" + graceful-fs "^4.2.9" + picomatch "^2.2.3" + +jest-validate@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-28.1.3.tgz#e322267fd5e7c64cea4629612c357bbda96229df" + integrity sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA== + dependencies: + "@jest/types" "^28.1.3" + camelcase "^6.2.0" + chalk "^4.0.0" + jest-get-type "^28.0.2" + leven "^3.1.0" + pretty-format "^28.1.3" + +jest-watcher@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-28.1.3.tgz#c6023a59ba2255e3b4c57179fc94164b3e73abd4" + integrity sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g== + dependencies: + "@jest/test-result" "^28.1.3" + "@jest/types" "^28.1.3" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + emittery "^0.10.2" + jest-util "^28.1.3" + string-length "^4.0.1" + +jest-worker@^26.5.0, jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + +jest-worker@^27.4.5: + 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== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest-worker@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-28.1.3.tgz#7e3c4ce3fa23d1bb6accb169e7f396f98ed4bb98" + integrity sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +jest@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/jest/-/jest-28.1.3.tgz#e9c6a7eecdebe3548ca2b18894a50f45b36dfc6b" + integrity sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA== + dependencies: + "@jest/core" "^28.1.3" + "@jest/types" "^28.1.3" + import-local "^3.0.2" + jest-cli "^28.1.3" + +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q== + +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg== + +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-yaml@^3.13.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" + integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +jsdom@^19.0.0: + version "19.0.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-19.0.0.tgz#93e67c149fe26816d38a849ea30ac93677e16b6a" + integrity sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A== + dependencies: + abab "^2.0.5" + acorn "^8.5.0" + acorn-globals "^6.0.0" + cssom "^0.5.0" + cssstyle "^2.3.0" + data-urls "^3.0.1" + decimal.js "^10.3.1" + domexception "^4.0.0" + escodegen "^2.0.0" + form-data "^4.0.0" + html-encoding-sniffer "^3.0.0" + http-proxy-agent "^5.0.0" + https-proxy-agent "^5.0.0" + is-potential-custom-element-name "^1.0.1" + nwsapi "^2.2.0" + parse5 "6.0.1" + saxes "^5.0.1" + symbol-tree "^3.2.4" + tough-cookie "^4.0.0" + w3c-hr-time "^1.0.2" + w3c-xmlserializer "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-encoding "^2.0.0" + whatwg-mimetype "^3.0.0" + whatwg-url "^10.0.0" + ws "^8.2.3" + xml-name-validator "^4.0.0" + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-better-errors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== + +json-parse-even-better-errors@^2.3.0, json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== + +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" + +json5@^2.1.2, json5@^2.1.3, json5@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" + integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + +jsonfile@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" + integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + dependencies: + universalify "^2.0.0" + optionalDependencies: + graceful-fs "^4.1.6" + +"jsx-ast-utils@^2.4.1 || ^3.0.0": + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.1.tgz#a3e0f1cb7e230954eab4dcbce9f6288a78f8ba44" + integrity sha512-pxrjmNpeRw5wwVeWyEAk7QJu2GnBO3uzPFmHCKJJFPKK2Cy0cWL23krGtLdnMmbIi6/FjlrQpPyfQI19ByPOhQ== + dependencies: + array-includes "^3.1.5" + object.assign "^4.1.2" + +junk@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" + integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ== + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw== + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +kleur@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" + integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== + +klona@^2.0.4: + version "2.0.5" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc" + integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ== + +lazy-universal-dotenv@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz#a6c8938414bca426ab8c9463940da451a911db38" + integrity sha512-prXSYk799h3GY3iOWnC6ZigYzMPjxN2svgjJ9shk7oMadSNX3wXy0B6F32PMJv7qtMnrIbUxoEHzbutvxR2LBQ== + dependencies: + "@babel/runtime" "^7.5.0" + app-root-dir "^1.0.2" + core-js "^3.0.4" + dotenv "^8.0.0" + dotenv-expand "^5.1.0" + +leven@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" + integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== + +levn@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" + integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== + dependencies: + prelude-ls "^1.2.1" + type-check "~0.4.0" + +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +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" + integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + integrity sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A== + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +loader-runner@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^1.2.3: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + +loader-utils@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129" + integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + integrity sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA== + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== + dependencies: + p-locate "^3.0.0" + path-exists "^3.0.0" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +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: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + +lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +loose-envify@^1.1.0, loose-envify@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +loud-rejection@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" + integrity sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ== + dependencies: + currently-unhandled "^0.4.1" + signal-exit "^3.0.0" + +lower-case@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" + integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg== + dependencies: + tslib "^2.0.3" + +lowlight@^1.17.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888" + integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw== + dependencies: + fault "^1.0.0" + highlight.js "~10.7.0" + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +lz-string@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" + integrity sha512-0ckx7ZHRPqb0oUm8zNr+90mtf9DQB60H1wMCjBtfi62Kl3a7JbHob6gA2bC+xRvZoOL+1hzUK8jeuEIQE8svEQ== + +make-dir@^2.0.0, make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + +make-dir@^3.0.0, make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg== + +map-obj@^1.0.0, map-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" + integrity sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg== + +map-or-similar@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/map-or-similar/-/map-or-similar-1.5.0.tgz#6de2653174adfb5d9edc33c69d3e92a1b76faf08" + integrity sha512-0aF7ZmVon1igznGI4VS30yugpduQW3y3GkcgGJOp7d8x8QrizhigUxjI/m2UojsXXto+jLAH3KSz+xOJTiORjg== + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w== + dependencies: + object-visit "^1.0.0" + +markdown-escapes@^1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.4.tgz#c95415ef451499d7602b91095f3c8e8975f78535" + integrity sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg== + +match-sorter@^6.0.2: + version "6.3.1" + resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda" + integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw== + dependencies: + "@babel/runtime" "^7.12.5" + remove-accents "0.4.2" + +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +mdast-squeeze-paragraphs@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-squeeze-paragraphs/-/mdast-squeeze-paragraphs-4.0.0.tgz#7c4c114679c3bee27ef10b58e2e015be79f1ef97" + integrity sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ== + dependencies: + unist-util-remove "^2.0.0" + +mdast-util-definitions@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz#c5c1a84db799173b4dcf7643cda999e440c24db2" + integrity sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ== + dependencies: + unist-util-visit "^2.0.0" + +mdast-util-to-hast@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-10.0.1.tgz#0cfc82089494c52d46eb0e3edb7a4eb2aea021eb" + integrity sha512-BW3LM9SEMnjf4HXXVApZMt8gLQWVNXc3jryK0nJu/rOXPOnlkUjmdkDlmxMirpbU9ILncGFIwLH/ubnWBbcdgA== + dependencies: + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + mdast-util-definitions "^4.0.0" + mdurl "^1.0.0" + unist-builder "^2.0.0" + unist-util-generated "^1.0.0" + unist-util-position "^3.0.0" + unist-util-visit "^2.0.0" + +mdast-util-to-string@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz#27055500103f51637bd07d01da01eb1967a43527" + integrity sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A== + +mdurl@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" + integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== + +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + +mem@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" + integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.1.0" + +memfs@^3.1.2, memfs@^3.2.2, memfs@^3.4.3: + version "3.4.7" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.7.tgz#e5252ad2242a724f938cb937e3c4f7ceb1f70e5a" + integrity sha512-ygaiUSNalBX85388uskeCyhSAoOSgzBbtVCr9jA2RROssFL9Q19/ZXFqS+2Th2sr1ewNIWgFdLzLC3Yl1Zv+lw== + dependencies: + fs-monkey "^1.0.3" + +memoizerific@^1.11.3: + version "1.11.3" + resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a" + integrity sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog== + dependencies: + map-or-similar "^1.5.0" + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +meow@^3.1.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" + integrity sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA== + dependencies: + camelcase-keys "^2.0.0" + decamelize "^1.1.2" + loud-rejection "^1.0.0" + map-obj "^1.0.1" + minimist "^1.1.3" + normalize-package-data "^2.3.4" + object-assign "^4.0.1" + read-pkg-up "^1.0.1" + redent "^1.0.0" + trim-newlines "^1.0.0" + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" + integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== + +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + +microevent.ts@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" + integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== + +micromatch@^3.1.10, micromatch@^3.1.4: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" + integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + dependencies: + braces "^3.0.2" + picomatch "^2.3.1" + +microseconds@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39" + integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA== + +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + +mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.30, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + +mime@^2.4.4: + version "2.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" + integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== + +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + +mimic-fn@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + +min-document@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" + integrity sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ== + dependencies: + dom-walk "^0.1.0" + +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + +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" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg== + +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + +minipass-collect@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/minipass-collect/-/minipass-collect-1.0.2.tgz#22b813bf745dc6edba2576b940022ad6edc8c617" + integrity sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA== + dependencies: + minipass "^3.0.0" + +minipass-flush@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/minipass-flush/-/minipass-flush-1.0.5.tgz#82e7135d7e89a50ffe64610a787953c4c4cbb373" + integrity sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw== + dependencies: + minipass "^3.0.0" + +minipass-pipeline@^1.2.2: + version "1.2.4" + resolved "https://registry.yarnpkg.com/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz#68472f79711c084657c067c5c6ad93cddea8214c" + integrity sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A== + dependencies: + minipass "^3.0.0" + +minipass@^3.0.0, minipass@^3.1.1: + version "3.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.3.4.tgz#ca99f95dd77c43c7a76bf51e6d200025eee0ffae" + integrity sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw== + dependencies: + yallist "^4.0.0" + +minizlib@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" + integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== + dependencies: + minipass "^3.0.0" + yallist "^4.0.0" + +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== + dependencies: + concat-stream "^1.5.0" + duplexify "^3.4.2" + end-of-stream "^1.1.0" + flush-write-stream "^1.0.0" + from2 "^2.1.0" + parallel-transform "^1.1.0" + pump "^3.0.0" + pumpify "^1.3.3" + stream-each "^1.1.0" + through2 "^2.0.0" + +mixin-deep@^1.2.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" + integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.1, mkdirp@^0.5.3: + version "0.5.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" + integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== + dependencies: + minimist "^1.2.6" + +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +move-concurrently@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ== + dependencies: + aproba "^1.1.1" + copy-concurrently "^1.0.0" + fs-write-stream-atomic "^1.0.8" + mkdirp "^0.5.1" + rimraf "^2.5.4" + run-queue "^1.0.3" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3, ms@^2.1.1: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +msw@^0.43.0: + version "0.43.0" + resolved "https://registry.yarnpkg.com/msw/-/msw-0.43.0.tgz#10c6fc3fb1752c0a144179e5ab04c6a512cc9959" + integrity sha512-XJylZP0qW3D5WUGWh9FFefJEl3MGG4y1I+/8a833d0eedm6B+GaPm6wPVZNcnlS2YVTagvEgShVJ7ZtY66tTRQ== + dependencies: + "@mswjs/cookies" "^0.2.0" + "@mswjs/interceptors" "^0.16.3" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.1" + "@types/js-levenshtein" "^1.1.1" + chalk "4.1.1" + chokidar "^3.4.2" + cookie "^0.4.2" + graphql "^16.3.0" + headers-polyfill "^3.0.4" + inquirer "^8.2.0" + is-node-process "^1.0.1" + js-levenshtein "^1.1.6" + node-fetch "^2.6.7" + outvariant "^1.3.0" + path-to-regexp "^6.2.0" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.2.2" + yargs "^17.3.1" + +multicast-dns@^7.2.5: + version "7.2.5" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" + integrity sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg== + dependencies: + dns-packet "^5.2.2" + thunky "^1.0.2" + +mute-stream@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" + integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + +nan@^2.12.1: + version "2.16.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.16.0.tgz#664f43e45460fb98faf00edca0bb0d7b8dce7916" + integrity sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA== + +nano-time@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef" + integrity sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA== + dependencies: + big-integer "^1.6.16" + +nanoid@^3.3.1, nanoid@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" + integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== + +negotiator@0.6.3: + version "0.6.3" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" + integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== + +neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1, neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +nested-error-stacks@^2.0.0, nested-error-stacks@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nested-error-stacks/-/nested-error-stacks-2.1.1.tgz#26c8a3cee6cc05fbcf1e333cd2fc3e003326c0b5" + integrity sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw== + +nice-try@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== + +no-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d" + integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg== + dependencies: + lower-case "^2.0.2" + tslib "^2.0.3" + +node-dir@^0.1.10: + version "0.1.17" + resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" + integrity sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg== + dependencies: + minimatch "^3.0.2" + +node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +node-forge@^1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw== + +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + +node-releases@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" + integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== + +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w== + dependencies: + remove-trailing-separator "^1.0.1" + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== + +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" + integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw== + dependencies: + path-key "^2.0.0" + +npm-run-path@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" + integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== + dependencies: + path-key "^3.0.0" + +npmlog@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0" + integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw== + dependencies: + are-we-there-yet "^2.0.0" + console-control-strings "^1.1.0" + gauge "^3.0.0" + set-blocking "^2.0.0" + +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + +num2fraction@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" + integrity sha512-Y1wZESM7VUThYY+4W+X4ySH2maqcA+p7UR+w8VWNWVAd6lwuXXWz/w/Cz43J/dI2I+PS6wD5N+bJUF+gjWvIqg== + +nwsapi@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.1.tgz#10a9f268fbf4c461249ebcfe38e359aa36e2577c" + integrity sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg== + +object-assign@^4.0.1, object-assign@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ== + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-inspect@^1.12.0, object-inspect@^1.9.0: + version "1.12.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + +object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA== + dependencies: + isobject "^3.0.0" + +object.assign@^4.1.0, object.assign@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" + integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== + dependencies: + call-bind "^1.0.0" + define-properties "^1.1.3" + has-symbols "^1.0.1" + object-keys "^1.1.1" + +object.entries@^1.1.0, object.entries@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.5.tgz#e1acdd17c4de2cd96d5a08487cfb9db84d881861" + integrity sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +"object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.5.tgz#7b37b205109c21e741e605727fe8b0ad5fa08251" + integrity sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +object.getownpropertydescriptors@^2.0.3, object.getownpropertydescriptors@^2.1.2: + version "2.1.4" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz#7965e6437a57278b587383831a9b829455a4bc37" + integrity sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ== + dependencies: + array.prototype.reduce "^1.0.4" + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.20.1" + +object.hasown@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.1.tgz#ad1eecc60d03f49460600430d97f23882cf592a3" + integrity sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A== + dependencies: + define-properties "^1.1.4" + es-abstract "^1.19.5" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ== + dependencies: + isobject "^3.0.1" + +object.values@^1.1.0, object.values@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.5.tgz#959f63e3ce9ef108720333082131e4a459b716ac" + integrity sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +objectorarray@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/objectorarray/-/objectorarray-1.0.5.tgz#2c05248bbefabd8f43ad13b41085951aac5e68a5" + integrity sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg== + +oblivious-set@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566" + integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw== + +obuf@^1.0.0, obuf@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + +on-finished@2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" + integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== + dependencies: + ee-first "1.1.1" + +on-headers@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" + integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== + +once@^1.3.0, once@^1.3.1, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== + dependencies: + wrappy "1" + +onetime@^5.1.0, onetime@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + +open@^7.0.3: + version "7.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-7.4.2.tgz#b8147e26dcf3e426316c730089fd71edd29c2321" + integrity sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q== + dependencies: + is-docker "^2.0.0" + is-wsl "^2.1.1" + +open@^8.0.9, open@^8.4.0: + version "8.4.0" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.0.tgz#345321ae18f8138f82565a910fdc6b39e8c244f8" + integrity sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q== + dependencies: + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" + +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + +optionator@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" + integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== + dependencies: + deep-is "^0.1.3" + fast-levenshtein "^2.0.6" + levn "^0.4.1" + prelude-ls "^1.2.1" + type-check "^0.4.0" + word-wrap "^1.2.3" + +ora@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A== + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ== + +os-tmpdir@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== + +outvariant@^1.2.1, outvariant@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.3.0.tgz#c39723b1d2cba729c930b74bf962317a81b9b1c9" + integrity sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ== + +p-all@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0" + integrity sha512-HbZxz5FONzz/z2gJfk6bFca0BCiSRF8jU3yCsWOen/vR6lZjfPOu/e7L3uFzTW1i0H8TlC3vqQstEJPQL4/uLA== + dependencies: + p-map "^2.0.0" + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + +p-event@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/p-event/-/p-event-4.2.0.tgz#af4b049c8acd91ae81083ebd1e6f5cae2044c1b5" + integrity sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ== + dependencies: + p-timeout "^3.1.0" + +p-filter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" + integrity sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw== + dependencies: + p-map "^2.0.0" + +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow== + +p-limit@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" + integrity sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q== + dependencies: + p-try "^1.0.0" + +p-limit@^2.0.0, p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-limit@^3.0.2, p-limit@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + integrity sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg== + dependencies: + p-limit "^1.1.0" + +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== + dependencies: + p-limit "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +p-map@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" + integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== + +p-map@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-3.0.0.tgz#d704d9af8a2ba684e2600d9a215983d4141a979d" + integrity sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ== + dependencies: + aggregate-error "^3.0.0" + +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== + dependencies: + "@types/retry" "0.12.0" + retry "^0.13.1" + +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + +p-try@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" + integrity sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww== + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parallel-transform@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.2.0.tgz#9049ca37d6cb2182c3b1d2c720be94d14a5814fc" + integrity sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg== + dependencies: + cyclist "^1.0.1" + inherits "^2.0.3" + readable-stream "^2.1.5" + +param-case@^3.0.3, param-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" + integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + +parent-module@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" + integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== + dependencies: + callsites "^3.0.0" + +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + +parse-entities@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-2.0.0.tgz#53c6eb5b9314a1f4ec99fa0fdf7ce01ecda0cbe8" + integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ== + dependencies: + character-entities "^1.0.0" + character-entities-legacy "^1.0.0" + character-reference-invalid "^1.0.0" + is-alphanumerical "^1.0.0" + is-decimal "^1.0.0" + is-hexadecimal "^1.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + integrity sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ== + dependencies: + error-ex "^1.2.0" + +parse-json@^5.0.0, parse-json@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" + integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== + dependencies: + "@babel/code-frame" "^7.0.0" + error-ex "^1.3.1" + json-parse-even-better-errors "^2.3.0" + lines-and-columns "^1.1.6" + +parse5@6.0.1, parse5@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + +parseurl@~1.3.2, parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + +pascal-case@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb" + integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g== + dependencies: + no-case "^3.0.4" + tslib "^2.0.3" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw== + +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + +path-dirname@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q== + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + integrity sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ== + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== + +path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w== + +path-key@^2.0.0, path-key@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw== + +path-key@^3.0.0, path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + +path-to-regexp@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" + integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + integrity sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg== + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" + integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== + dependencies: + pify "^3.0.0" + +path-type@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" + integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== + +pbkdf2@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + +picocolors@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" + integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== + +picocolors@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" + integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== + +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pify@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg== + +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + integrity sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw== + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + integrity sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg== + +pirates@^4.0.1, pirates@^4.0.4, pirates@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.5.tgz#feec352ea5c3268fb23a37c702ab1699f35a5f3b" + integrity sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ== + +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== + dependencies: + find-up "^3.0.0" + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + +pnp-webpack-plugin@1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" + integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== + dependencies: + ts-pnp "^1.1.6" + +polished@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/polished/-/polished-4.2.2.tgz#2529bb7c3198945373c52e34618c8fe7b1aa84d1" + integrity sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ== + dependencies: + "@babel/runtime" "^7.17.8" + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg== + +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" + integrity sha512-9SiofaZ9CWpQWxOwRh1b/r85KD5y7GgvsNt1056k6OYLvWUun0czCvogfJgylC22uJTwW1KzY3Gz65NZRlvoiQ== + dependencies: + postcss "^7.0.26" + +postcss-loader@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-4.3.0.tgz#2c4de9657cd4f07af5ab42bd60a673004da1b8cc" + integrity sha512-M/dSoIiNDOo8Rk0mUqoj4kpGq91gcxCfb9PoyZVdZ76/AuhxylHDYZblNE8o+EQ9AMSASeMFEKxZf5aU6wlx1Q== + dependencies: + cosmiconfig "^7.0.0" + klona "^2.0.4" + loader-utils "^2.0.0" + schema-utils "^3.0.0" + semver "^7.3.4" + +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" + integrity sha512-LaYLDNS4SG8Q5WAWqIJgdHPJrDDr/Lv775rMBFUbgjTz6j34lUznACHcdRWroPvXANP2Vj7yNK57vp9eFqzLWQ== + dependencies: + postcss "^7.0.5" + +postcss-modules-extract-imports@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz#cda1f047c0ae80c97dbe28c3e76a43b88025741d" + integrity sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw== + +postcss-modules-local-by-default@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-3.0.3.tgz#bb14e0cc78279d504dbdcbfd7e0ca28993ffbbb0" + integrity sha512-e3xDq+LotiGesympRlKNgaJ0PCzoUIdpH0dj47iWAui/kyTgh3CiAr1qP54uodmJhl6p9rN6BoNcdEDVJx9RDw== + dependencies: + icss-utils "^4.1.1" + postcss "^7.0.32" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-local-by-default@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz#ebbb54fae1598eecfdf691a02b3ff3b390a5a51c" + integrity sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ== + dependencies: + icss-utils "^5.0.0" + postcss-selector-parser "^6.0.2" + postcss-value-parser "^4.1.0" + +postcss-modules-scope@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-2.2.0.tgz#385cae013cc7743f5a7d7602d1073a89eaae62ee" + integrity sha512-YyEgsTMRpNd+HmyC7H/mh3y+MeFWevy7V1evVhJWewmMbjDHIbZbOXICC2y+m1xI1UVfIT1HMW/O04Hxyu9oXQ== + dependencies: + postcss "^7.0.6" + postcss-selector-parser "^6.0.0" + +postcss-modules-scope@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz#9ef3151456d3bbfa120ca44898dfca6f2fa01f06" + integrity sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-modules-values@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-3.0.0.tgz#5b5000d6ebae29b4255301b4a3a54574423e7f10" + integrity sha512-1//E5jCBrZ9DmRX+zCtmQtRSV6PV42Ix7Bzj9GbwJceduuf7IqP8MgeTXuRDHOWj2m0VzZD5+roFWDuU8RQjcg== + dependencies: + icss-utils "^4.0.0" + postcss "^7.0.6" + +postcss-modules-values@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz#d7c5e7e68c3bb3c9b27cbf48ca0bb3ffb4602c9c" + integrity sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ== + dependencies: + icss-utils "^5.0.0" + +postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4: + 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== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + +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" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^7.0.14, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.36, postcss@^7.0.5, postcss@^7.0.6: + version "7.0.39" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309" + integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA== + dependencies: + picocolors "^0.2.1" + source-map "^0.6.1" + +postcss@^8.2.15, postcss@^8.4.7: + version "8.4.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" + integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== + dependencies: + nanoid "^3.3.4" + picocolors "^1.0.0" + source-map-js "^1.0.2" + +prelude-ls@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" + integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== + +prettier-linter-helpers@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b" + integrity sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w== + dependencies: + fast-diff "^1.1.2" + +"prettier@>=2.2.1 <=2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.3.0.tgz#b6a5bf1284026ae640f17f7ff5658a7567fc0d18" + integrity sha512-kXtO4s0Lz/DW/IJ9QdWhAf7/NmPWQXkFr/r/WkR3vyI+0v8amTDxiaQSLzs8NBlytfLWX/7uQUMIW677yLKl4w== + +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== + +pretty-error@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.2.tgz#be89f82d81b1c86ec8fdfbc385045882727f93b6" + integrity sha512-EY5oDzmsX5wvuynAByrmY0P0hcp+QpnAKbJng2A2MPjVKXCxrDSUkzghVJ4ZGPIv+JC4gX8fPUWscC0RtjsWGw== + dependencies: + lodash "^4.17.20" + renderkid "^2.0.4" + +pretty-error@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-4.0.0.tgz#90a703f46dd7234adb46d0f84823e9d1cb8f10d6" + integrity sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw== + dependencies: + lodash "^4.17.20" + renderkid "^3.0.0" + +pretty-format@^27.0.2: + version "27.5.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + +pretty-format@^28.0.0, pretty-format@^28.1.3: + version "28.1.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-28.1.3.tgz#c9fba8cedf99ce50963a11b27d982a9ae90970d5" + integrity sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q== + dependencies: + "@jest/schemas" "^28.1.3" + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^18.0.0" + +pretty-hrtime@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" + integrity sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A== + +prismjs@^1.27.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== + +prismjs@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" + integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== + +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== + +promise-inflight@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" + integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g== + +promise.allsettled@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.5.tgz#2443f3d4b2aa8dfa560f6ac2aa6c4ea999d75f53" + integrity sha512-tVDqeZPoBC0SlzJHzWGZ2NKAguVq2oiYj7gbggbiTvH2itHohijTp7njOUA0aQ/nl+0lr/r6egmhoYu63UZ/pQ== + dependencies: + array.prototype.map "^1.0.4" + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + iterate-value "^1.0.2" + +promise.prototype.finally@^3.1.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.3.tgz#d3186e58fcf4df1682a150f934ccc27b7893389c" + integrity sha512-EXRF3fC9/0gz4qkt/f5EP5iW4kj9oFpBICNpCNOb/52+8nlHIX07FPLbi/q4qYBQ1xZqivMzTpNQSnArVASolQ== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +prompts@^2.0.1, prompts@^2.4.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069" + integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q== + dependencies: + kleur "^3.0.3" + sisteransi "^1.0.5" + +prop-types@^15.0.0, prop-types@^15.7.2, prop-types@^15.8.1: + version "15.8.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" + integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== + dependencies: + loose-envify "^1.4.0" + object-assign "^4.1.1" + react-is "^16.13.1" + +property-information@^5.0.0, property-information@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-5.6.0.tgz#61675545fb23002f245c6540ec46077d4da3ed69" + integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA== + dependencies: + xtend "^4.0.0" + +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw== + +psl@^1.1.33: + version "1.9.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7" + integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag== + +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + +pump@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pumpify@^1.3.3: + version "1.5.1" + resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== + dependencies: + duplexify "^3.6.0" + inherits "^2.0.3" + pump "^2.0.0" + +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw== + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ== + +punycode@^2.1.0, punycode@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== + +qs@6.10.3: + version "6.10.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" + integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== + dependencies: + side-channel "^1.0.4" + +qs@^6.10.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA== + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g== + +querystring@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd" + integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg== + +queue-microtask@^1.2.2: + version "1.2.3" + resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" + integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== + +ramda@^0.28.0: + version "0.28.0" + resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.28.0.tgz#acd785690100337e8b063cab3470019be427cc97" + integrity sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA== + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + +range-parser@^1.2.1, range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" + integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +raw-loader@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" + integrity sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +react-docgen-typescript@^2.1.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/react-docgen-typescript/-/react-docgen-typescript-2.2.2.tgz#4611055e569edc071204aadb20e1c93e1ab1659c" + integrity sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg== + +react-docgen@^5.0.0: + version "5.4.3" + resolved "https://registry.yarnpkg.com/react-docgen/-/react-docgen-5.4.3.tgz#7d297f73b977d0c7611402e5fc2a168acf332b26" + integrity sha512-xlLJyOlnfr8lLEEeaDZ+X2J/KJoe6Nr9AzxnkdQWush5hz2ZSu66w6iLMOScMmxoSHWpWMn+k3v5ZiyCfcWsOA== + dependencies: + "@babel/core" "^7.7.5" + "@babel/generator" "^7.12.11" + "@babel/runtime" "^7.7.6" + ast-types "^0.14.2" + commander "^2.19.0" + doctrine "^3.0.0" + estree-to-babel "^3.1.0" + neo-async "^2.6.1" + node-dir "^0.1.10" + strip-indent "^3.0.0" + +react-dom@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" + integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g== + dependencies: + loose-envify "^1.1.0" + scheduler "^0.23.0" + +react-element-to-jsx-string@^14.3.4: + version "14.3.4" + resolved "https://registry.yarnpkg.com/react-element-to-jsx-string/-/react-element-to-jsx-string-14.3.4.tgz#709125bc72f06800b68f9f4db485f2c7d31218a8" + integrity sha512-t4ZwvV6vwNxzujDQ+37bspnLwA4JlgUPWhLjBJWsNIDceAf6ZKUTCjdm08cN6WeZ5pTMKiCJkmAYnpmR4Bm+dg== + dependencies: + "@base2/pretty-print-object" "1.0.1" + is-plain-object "5.0.0" + react-is "17.0.2" + +react-icons@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.4.0.tgz#a13a8a20c254854e1ec9aecef28a95cdf24ef703" + integrity sha512-fSbvHeVYo/B5/L4VhB7sBA1i2tS8MkT0Hb9t2H1AVPkwGfVHLJCqyr2Py9dKMxsyM63Eng1GkdZfbWj+Fmv8Rg== + +react-inspector@^5.1.0: + version "5.1.1" + resolved "https://registry.yarnpkg.com/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8" + integrity sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg== + dependencies: + "@babel/runtime" "^7.0.0" + is-dom "^1.0.0" + prop-types "^15.0.0" + +react-is@17.0.2, react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + +react-is@^16.13.1, react-is@^16.7.0: + version "16.13.1" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" + integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== + +react-is@^18.0.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" + integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== + +react-merge-refs@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-merge-refs/-/react-merge-refs-1.1.0.tgz#73d88b892c6c68cbb7a66e0800faa374f4c38b06" + integrity sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ== + +react-query@^3.39.1: + version "3.39.1" + resolved "https://registry.yarnpkg.com/react-query/-/react-query-3.39.1.tgz#3876c0fdac7a3b5a84e195534e5fa8fbdd628847" + integrity sha512-qYKT1bavdDiQZbngWZyPotlBVzcBjDYEJg5RQLBa++5Ix5jjfbEYJmHSZRZD+USVHUSvl/ey9Hu+QfF1QAK80A== + dependencies: + "@babel/runtime" "^7.5.5" + broadcast-channel "^3.4.1" + match-sorter "^6.0.2" + +react-refresh@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046" + integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A== + +react-router-dom@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-6.3.0.tgz#a0216da813454e521905b5fa55e0e5176123f43d" + integrity sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw== + dependencies: + history "^5.2.0" + react-router "6.3.0" + +react-router@6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-6.3.0.tgz#3970cc64b4cb4eae0c1ea5203a80334fdd175557" + integrity sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ== + dependencies: + history "^5.2.0" + +react-syntax-highlighter@^15.4.5: + version "15.5.0" + resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.5.0.tgz#4b3eccc2325fa2ec8eff1e2d6c18fa4a9e07ab20" + integrity sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg== + dependencies: + "@babel/runtime" "^7.3.1" + highlight.js "^10.4.1" + lowlight "^1.17.0" + prismjs "^1.27.0" + refractor "^3.6.0" + +react@^18.2.0: + version "18.2.0" + resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5" + integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ== + dependencies: + loose-envify "^1.1.0" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + integrity sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A== + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + integrity sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ== + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +recoil@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/recoil/-/recoil-0.7.4.tgz#d6508fa656d9c93e66fdf334e1f723a9e98801cf" + integrity sha512-sCXvQGMfSprkNU4ZRkJV4B0qFQSURJMgsICqY1952WRlg66NMwYqi6n67vhnhn0qw4zHU1gHXJuMvRDaiRNFZw== + dependencies: + hamt_plus "1.0.2" + +redent@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" + integrity sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g== + dependencies: + indent-string "^2.1.0" + strip-indent "^1.0.1" + +refractor@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" + integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA== + dependencies: + hastscript "^6.0.0" + parse-entities "^2.0.0" + prismjs "~1.27.0" + +regenerate-unicode-properties@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56" + integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.13.2, regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.7: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + +regenerator-transform@^0.15.0: + version "0.15.0" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" + integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg== + dependencies: + "@babel/runtime" "^7.8.4" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" + integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + functions-have-names "^1.2.2" + +regexpp@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" + integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== + +regexpu-core@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" + integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.0.1" + regjsgen "^0.6.0" + regjsparser "^0.8.2" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.0.0" + +regjsgen@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d" + integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA== + +regjsparser@^0.8.2: + version "0.8.4" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f" + integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA== + dependencies: + jsesc "~0.5.0" + +relateurl@^0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" + integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog== + +remark-external-links@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/remark-external-links/-/remark-external-links-8.0.0.tgz#308de69482958b5d1cd3692bc9b725ce0240f345" + integrity sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA== + dependencies: + extend "^3.0.0" + is-absolute-url "^3.0.0" + mdast-util-definitions "^4.0.0" + space-separated-tokens "^1.0.0" + unist-util-visit "^2.0.0" + +remark-footnotes@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/remark-footnotes/-/remark-footnotes-2.0.0.tgz#9001c4c2ffebba55695d2dd80ffb8b82f7e6303f" + integrity sha512-3Clt8ZMH75Ayjp9q4CorNeyjwIxHFcTkaektplKGl2A1jNGEUey8cKL0ZC5vJwfcD5GFGsNLImLG/NGzWIzoMQ== + +remark-mdx@1.6.22: + version "1.6.22" + resolved "https://registry.yarnpkg.com/remark-mdx/-/remark-mdx-1.6.22.tgz#06a8dab07dcfdd57f3373af7f86bd0e992108bbd" + integrity sha512-phMHBJgeV76uyFkH4rvzCftLfKCr2RZuF+/gmVcaKrpsihyzmhXjA0BEMDaPTXG5y8qZOKPVo83NAOX01LPnOQ== + dependencies: + "@babel/core" "7.12.9" + "@babel/helper-plugin-utils" "7.10.4" + "@babel/plugin-proposal-object-rest-spread" "7.12.1" + "@babel/plugin-syntax-jsx" "7.12.1" + "@mdx-js/util" "1.6.22" + is-alphabetical "1.0.4" + remark-parse "8.0.3" + unified "9.2.0" + +remark-parse@8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-8.0.3.tgz#9c62aa3b35b79a486454c690472906075f40c7e1" + integrity sha512-E1K9+QLGgggHxCQtLt++uXltxEprmWzNfg+MxpfHsZlrddKzZ/hZyWHDbK3/Ap8HJQqYJRXP+jHczdL6q6i85Q== + dependencies: + ccount "^1.0.0" + collapse-white-space "^1.0.2" + is-alphabetical "^1.0.0" + is-decimal "^1.0.0" + is-whitespace-character "^1.0.0" + is-word-character "^1.0.0" + markdown-escapes "^1.0.0" + parse-entities "^2.0.0" + repeat-string "^1.5.4" + state-toggle "^1.0.0" + trim "0.0.1" + trim-trailing-lines "^1.0.0" + unherit "^1.0.4" + unist-util-remove-position "^2.0.0" + vfile-location "^3.0.0" + xtend "^4.0.1" + +remark-slug@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-6.1.0.tgz#0503268d5f0c4ecb1f33315c00465ccdd97923ce" + integrity sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ== + dependencies: + github-slugger "^1.0.0" + mdast-util-to-string "^1.0.0" + unist-util-visit "^2.0.0" + +remark-squeeze-paragraphs@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-squeeze-paragraphs/-/remark-squeeze-paragraphs-4.0.0.tgz#76eb0e085295131c84748c8e43810159c5653ead" + integrity sha512-8qRqmL9F4nuLPIgl92XUuxI3pFxize+F1H0e/W3llTk0UsjJaj01+RrirkMw7P21RKe4X6goQhYRSvNWX+70Rw== + dependencies: + mdast-squeeze-paragraphs "^4.0.0" + +remove-accents@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5" + integrity sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA== + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw== + +renderkid@^2.0.4: + version "2.0.7" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-2.0.7.tgz#464f276a6bdcee606f4a15993f9b29fc74ca8609" + integrity sha512-oCcFyxaMrKsKcTY59qnCAtmDVSLfPbrv6A3tVbPdFMMrv5jaK10V6m40cKsoPNhAqN6rmHW9sswW4o3ruSrwUQ== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^3.0.1" + +renderkid@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/renderkid/-/renderkid-3.0.0.tgz#5fd823e4d6951d37358ecc9a58b1f06836b6268a" + integrity sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg== + dependencies: + css-select "^4.1.3" + dom-converter "^0.2.0" + htmlparser2 "^6.1.0" + lodash "^4.17.21" + strip-ansi "^6.0.1" + +repeat-element@^1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" + integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== + +repeat-string@^1.5.4, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w== + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A== + dependencies: + is-finite "^1.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== + +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ== + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg== + +resolve.exports@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.0.tgz#5ce842b94b05146c0e03076985d1d0e7e48c90c9" + integrity sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ== + +resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.3.2, resolve@^1.9.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +resolve@^2.0.0-next.3: + version "2.0.0-next.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.4.tgz#3d37a113d6429f496ec4752d2a2e58efb1fd4660" + integrity sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +retry@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" + integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg== + +reusify@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" + integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + +rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" + integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== + dependencies: + glob "^7.1.3" + +rimraf@^2.5.4, rimraf@^2.6.3: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + +run-async@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" + integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== + +run-parallel@^1.1.9: + version "1.2.0" + resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" + integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== + dependencies: + queue-microtask "^1.2.2" + +run-queue@^1.0.0, run-queue@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg== + dependencies: + aproba "^1.1.1" + +rxjs@^7.5.5: + version "7.5.5" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + dependencies: + tslib "^2.1.0" + +safe-buffer@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== + +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg== + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.1.0: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + +scheduler@^0.23.0: + version "0.23.0" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" + integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw== + dependencies: + loose-envify "^1.1.0" + +schema-utils@2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" + integrity sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A== + dependencies: + "@types/json-schema" "^7.0.4" + ajv "^6.12.2" + ajv-keywords "^3.4.1" + +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + +schema-utils@^2.6.5, schema-utils@^2.7.0: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.0, schema-utils@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" + integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +schema-utils@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" + integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== + dependencies: + "@types/json-schema" "^7.0.9" + ajv "^8.8.0" + ajv-formats "^2.1.1" + ajv-keywords "^5.0.0" + +select-hose@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" + integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== + +selfsigned@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.0.1.tgz#8b2df7fa56bf014d19b6007655fff209c0ef0a56" + integrity sha512-LmME957M1zOsUhG+67rAjKfiWFox3SBxE/yymatMZsAx+oMrJ0YQ8AToOnyCm7xbeg2ep37IHLxdu0o2MavQOQ== + dependencies: + node-forge "^1" + +"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + +semver@7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" + integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== + +semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" + integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== + +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7: + version "7.3.7" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" + integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== + dependencies: + lru-cache "^6.0.0" + +send@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" + integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + +serialize-javascript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +serve-favicon@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" + integrity sha512-FMW2RvqNr03x+C0WxTyu6sOv21oOjkq5j8tjquWccwa6ScNyGFOGJVpuS1NmTVGBAHS07xnSKotgf2ehQmf9iA== + dependencies: + etag "~1.8.1" + fresh "0.5.2" + ms "2.1.1" + parseurl "~1.3.2" + safe-buffer "5.1.1" + +serve-index@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" + integrity sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw== + dependencies: + accepts "~1.3.4" + batch "0.6.1" + debug "2.6.9" + escape-html "~1.0.3" + http-errors "~1.6.2" + mime-types "~2.1.17" + parseurl "~1.3.2" + +serve-static@1.15.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" + integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.18.0" + +set-blocking@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== + +set-cookie-parser@^2.4.6: + version "2.5.0" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.0.tgz#96b59525e1362c94335c3c761100bb6e8f2da4b0" + integrity sha512-cHMAtSXilfyBePduZEBVPTCftTQWz6ehWJD5YNUg4mqvRosrrjKbo4WS8JkB0/RxonMoohHm7cOGH60mDkRQ9w== + +set-value@^2.0.0, set-value@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" + integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +setimmediate@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + +setprototypeof@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== + +setprototypeof@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" + integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== + +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg== + dependencies: + shebang-regex "^1.0.0" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ== + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +side-channel@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" + integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== + dependencies: + call-bind "^1.0.0" + get-intrinsic "^1.0.2" + object-inspect "^1.9.0" + +signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + +sisteransi@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" + integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== + +slash@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" + integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== + +slash@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" + integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +sockjs@^0.3.24: + version "0.3.24" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.24.tgz#c9bc8995f33a111bea0395ec30aa3206bdb5ccce" + integrity sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ== + dependencies: + faye-websocket "^0.11.3" + uuid "^8.3.2" + websocket-driver "^0.7.4" + +source-list-map@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== + +source-map-js@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" + integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== + +source-map-resolve@^0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@0.5.13: + version "0.5.13" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" + integrity sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-support@^0.5.16, source-map-support@~0.5.12, source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== + +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + +space-separated-tokens@^1.0.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" + integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA== + +spdx-correct@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" + integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" + integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.11" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" + integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== + +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== + dependencies: + debug "^4.1.0" + detect-node "^2.0.4" + hpack.js "^2.1.6" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" + +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== + dependencies: + debug "^4.1.0" + handle-thing "^2.0.0" + http-deceiver "^1.2.7" + select-hose "^2.0.0" + spdy-transport "^3.0.0" + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== + +ssri@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== + dependencies: + figgy-pudding "^3.5.1" + +ssri@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-8.0.1.tgz#638e4e439e2ffbd2cd289776d5ca457c4f51a2af" + integrity sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ== + dependencies: + minipass "^3.1.1" + +stable@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" + integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== + +stack-utils@^2.0.3: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" + integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== + dependencies: + escape-string-regexp "^2.0.0" + +stackframe@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.3.4.tgz#b881a004c8c149a5e8efef37d51b16e412943310" + integrity sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw== + +state-toggle@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" + integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g== + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +statuses@2.0.1, statuses@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" + integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== + +"statuses@>= 1.4.0 < 2": + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + +store2@^2.12.0: + version "2.13.2" + resolved "https://registry.yarnpkg.com/store2/-/store2-2.13.2.tgz#01ad8802ca5b445b9c316b55e72645c13a3cd7e3" + integrity sha512-CMtO2Uneg3SAz/d6fZ/6qbqqQHi2ynq6/KzMD/26gTkiEShCcpqFfTHgOxsE0egAq6SX3FmN4CeSqn8BzXQkJg== + +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + +stream-each@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== + dependencies: + end-of-stream "^1.1.0" + stream-shift "^1.0.0" + +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + +strict-event-emitter@^0.2.0, strict-event-emitter@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz#365714f0c95f059db31064ca745d5b33e5b30f6e" + integrity sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ== + dependencies: + events "^3.3.0" + +string-length@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" + integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== + dependencies: + char-regex "^1.0.2" + strip-ansi "^6.0.0" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string.prototype.matchall@^4.0.0 || ^3.0.1", string.prototype.matchall@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" + integrity sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + get-intrinsic "^1.1.1" + has-symbols "^1.0.3" + internal-slot "^1.0.3" + regexp.prototype.flags "^1.4.1" + side-channel "^1.0.4" + +string.prototype.padend@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.3.tgz#997a6de12c92c7cb34dc8a201a6c53d9bd88a5f1" + integrity sha512-jNIIeokznm8SD/TZISQsZKYu7RJyheFNt84DUPrh482GC8RVp2MKqm2O5oBRdGxbDQoXrhhWtPIWQOiy20svUg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +string.prototype.padstart@^3.0.0: + version "3.1.3" + resolved "https://registry.yarnpkg.com/string.prototype.padstart/-/string.prototype.padstart-3.1.3.tgz#4551d0117d9501692ec6000b15056ac3f816cfa5" + integrity sha512-NZydyOMtYxpTjGqp0VN5PYUF/tsU15yDMZnUdj16qRUIUiMJkHHSDElYyQFrMu+/WloTpA7MQSiADhBicDfaoA== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.19.1" + +string.prototype.trimend@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" + integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string.prototype.trimstart@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz#5466d93ba58cfa2134839f81d7f42437e8c01fef" + integrity sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg== + dependencies: + call-bind "^1.0.2" + define-properties "^1.1.4" + es-abstract "^1.19.5" + +string_decoder@^1.0.0, string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg== + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + integrity sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g== + dependencies: + is-utf8 "^0.2.0" + +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q== + +strip-final-newline@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" + integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== + +strip-indent@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" + integrity sha512-I5iQq6aFMM62fBEAIB/hXzwJD6EEZ0xEGCX2t7oXqaKPIRgt4WruAQ285BISgdkP+HLGWyeGmNJcpIwFeRYRUA== + dependencies: + get-stdin "^4.0.1" + +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + +strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +style-loader@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" + integrity sha512-V7TCORko8rs9rIqkSrlMfkqA63DfoGBBJmK1kKGCcSi+BWb4cqz0SRsnp4l6rU5iwOEd0/2ePv68SV22VXon4Q== + dependencies: + loader-utils "^2.0.0" + schema-utils "^2.7.0" + +style-loader@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-2.0.0.tgz#9669602fd4690740eaaec137799a03addbbc393c" + integrity sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +style-loader@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" + integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== + +style-to-object@0.3.0, style-to-object@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" + integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA== + dependencies: + inline-style-parser "0.1.1" + +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^7.0.0, supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-hyperlinks@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" + integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== + dependencies: + has-flag "^4.0.0" + supports-color "^7.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "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== + +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +symbol.prototype.description@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/symbol.prototype.description/-/symbol.prototype.description-1.0.5.tgz#d30e01263b6020fbbd2d2884a6276ce4d49ab568" + integrity sha512-x738iXRYsrAt9WBhRCVG5BtIC3B7CUkFwbHW2zOvGtwM33s7JjrCDyq8V0zgMYVb5ymsL8+qkzzpANH63CPQaQ== + dependencies: + call-bind "^1.0.2" + get-symbol-description "^1.0.0" + has-symbols "^1.0.2" + object.getownpropertydescriptors "^2.1.2" + +synchronous-promise@^2.0.15: + version "2.0.15" + resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.15.tgz#07ca1822b9de0001f5ff73595f3d08c4f720eb8e" + integrity sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg== + +tapable@^1.0.0, tapable@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.0.0, tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +tar@^6.0.2: + version "6.1.11" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" + integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^3.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +telejson@^6.0.8: + version "6.0.8" + resolved "https://registry.yarnpkg.com/telejson/-/telejson-6.0.8.tgz#1c432db7e7a9212c1fbd941c3e5174ec385148f7" + integrity sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg== + dependencies: + "@types/is-function" "^1.0.0" + global "^4.4.0" + is-function "^1.0.2" + is-regex "^1.1.2" + is-symbol "^1.0.3" + isobject "^4.0.0" + lodash "^4.17.21" + memoizerific "^1.11.3" + +terminal-link@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" + integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== + dependencies: + ansi-escapes "^4.2.1" + supports-hyperlinks "^2.0.0" + +terser-webpack-plugin@^1.4.3: + version "1.4.5" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz#a217aefaea330e734ffacb6120ec1fa312d6040b" + integrity sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw== + dependencies: + cacache "^12.0.2" + find-cache-dir "^2.1.0" + is-wsl "^1.1.0" + schema-utils "^1.0.0" + serialize-javascript "^4.0.0" + source-map "^0.6.1" + terser "^4.1.2" + webpack-sources "^1.4.0" + worker-farm "^1.7.0" + +terser-webpack-plugin@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz#28daef4a83bd17c1db0297070adc07fc8cfc6a9a" + integrity sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ== + dependencies: + cacache "^15.0.5" + find-cache-dir "^3.3.1" + jest-worker "^26.5.0" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.4" + webpack-sources "^1.4.3" + +terser-webpack-plugin@^5.0.3, terser-webpack-plugin@^5.1.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz#8033db876dd5875487213e87c627bca323e5ed90" + integrity sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ== + dependencies: + "@jridgewell/trace-mapping" "^0.3.7" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.0" + terser "^5.7.2" + +terser@^4.1.2, terser@^4.6.3: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +terser@^5.10.0, terser@^5.3.4, terser@^5.7.2: + version "5.14.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.1.tgz#7c95eec36436cb11cf1902cc79ac564741d19eca" + integrity sha512-+ahUAE+iheqBTDxXhTisdA8hgvbEG1hHOQ9xmNjeUJSoi6DU/gMrKNcfZjHkyY6Alnuyc+ikYJaxxfHkT3+WuQ== + dependencies: + "@jridgewell/source-map" "^0.3.2" + acorn "^8.5.0" + commander "^2.20.0" + source-map-support "~0.5.20" + +test-exclude@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" + integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== + dependencies: + "@istanbuljs/schema" "^0.1.2" + glob "^7.1.4" + minimatch "^3.0.4" + +text-table@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== + +through2@^2.0.0: + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + dependencies: + readable-stream "~2.3.6" + xtend "~4.0.1" + +through@^2.3.6: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== + +thunky@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" + integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== + +timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + +tmp@^0.0.33: + version "0.0.33" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" + integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== + dependencies: + os-tmpdir "~1.0.2" + +tmpl@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== + +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha512-okFlQcoGTi4LQBG/PgSYblw9VOyptsz2KJZqc6qtgGdes8VktzUQkj4BI2blit072iS8VODNcMA+tvnS9dnuMA== + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg== + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg== + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +toidentifier@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" + integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== + +tough-cookie@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" + integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== + dependencies: + psl "^1.1.33" + punycode "^2.1.1" + universalify "^0.1.2" + +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +trim-newlines@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" + integrity sha512-Nm4cF79FhSTzrLKGDMi3I4utBtFv8qKy4sq1enftf2gMdpqI8oVQTAfySkTz5r49giVzDj88SVZXP4CeYQwjaw== + +trim-trailing-lines@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-1.1.4.tgz#bd4abbec7cc880462f10b2c8b5ce1d8d1ec7c2c0" + integrity sha512-rjUWSqnfTNrjbB9NQWfPMH/xRK1deHeGsHoVfpxJ++XeYXE0d6B1En37AHfw3jtfTU7dzMzZL2jjpe8Qb5gLIQ== + +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== + +trough@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406" + integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA== + +ts-dedent@^2.0.0, ts-dedent@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/ts-dedent/-/ts-dedent-2.2.0.tgz#39e4bd297cd036292ae2394eb3412be63f563bb5" + integrity sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ== + +ts-loader@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.1.tgz#fe25cca56e3e71c1087fe48dc67f4df8c59b22d4" + integrity sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw== + dependencies: + chalk "^4.1.0" + enhanced-resolve "^5.0.0" + micromatch "^4.0.0" + semver "^7.3.4" + +ts-pnp@^1.1.6: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" + integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== + +tsconfig-paths@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" + integrity sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.6" + strip-bom "^3.0.0" + +tslib@^1.8.1, tslib@^1.9.3: + version "1.14.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + +tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha512-JVa5ijo+j/sOoHGjw0sxw734b1LhBkQ3bvUGNdxnVXDCX81Yx7TFgnZygxrIIWn23hbfTaMYLwRmAxFyDuFmIw== + +type-check@^0.4.0, type-check@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" + integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== + dependencies: + prelude-ls "^1.2.1" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== + dependencies: + prelude-ls "~1.1.2" + +type-detect@4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + +type-fest@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" + integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== + +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-fest@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" + integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== + +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + +typedarray-to-buffer@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" + integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== + +typescript@^4.7.4: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + +uglify-js@^3.1.4: + version "3.16.2" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.16.2.tgz#0481e1dbeed343ad1c2ddf3c6d42e89b7a6d4def" + integrity sha512-AaQNokTNgExWrkEYA24BTNMSjyqEXPSfhqoS0AxmHkCJ4U+Dyy5AvbGV/sqxuxficEfGGoX3zWw9R7QpLFfEsg== + +unbox-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" + integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== + dependencies: + call-bind "^1.0.2" + has-bigints "^1.0.2" + has-symbols "^1.0.3" + which-boxed-primitive "^1.0.2" + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + +unherit@^1.0.4: + version "1.1.3" + resolved "https://registry.yarnpkg.com/unherit/-/unherit-1.1.3.tgz#6c9b503f2b41b262330c80e91c8614abdaa69c22" + integrity sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ== + dependencies: + inherits "^2.0.0" + xtend "^4.0.0" + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714" + integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8" + integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ== + +unified@9.2.0: + version "9.2.0" + resolved "https://registry.yarnpkg.com/unified/-/unified-9.2.0.tgz#67a62c627c40589edebbf60f53edfd4d822027f8" + integrity sha512-vx2Z0vY+a3YoTj8+pttM3tiJHCwY5UFbYdiWrwBEbHmK8pvsPj2rtAX2BFfgXen8T39CJWblWRDT4L5WGXtDdg== + dependencies: + bail "^1.0.0" + extend "^3.0.0" + is-buffer "^2.0.0" + is-plain-obj "^2.0.0" + trough "^1.0.0" + vfile "^4.0.0" + +union-value@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" + integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^2.0.1" + +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== + dependencies: + unique-slug "^2.0.0" + +unique-slug@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.2.tgz#baabce91083fc64e945b0f3ad613e264f7cd4e6c" + integrity sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w== + dependencies: + imurmurhash "^0.1.4" + +unist-builder@2.0.3, unist-builder@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-2.0.3.tgz#77648711b5d86af0942f334397a33c5e91516436" + integrity sha512-f98yt5pnlMWlzP539tPc4grGMsFaQQlP/vM396b00jngsiINumNmsY8rkXjfoi1c6QaM8nQ3vaGDuoKWbe/1Uw== + +unist-util-generated@^1.0.0: + version "1.1.6" + resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.6.tgz#5ab51f689e2992a472beb1b35f2ce7ff2f324d4b" + integrity sha512-cln2Mm1/CZzN5ttGK7vkoGw+RZ8VcUH6BtGbq98DDtRGquAAOXig1mrBQYelOwMXYS8rK+vZDyyojSjp7JX+Lg== + +unist-util-is@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" + integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== + +unist-util-position@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.1.0.tgz#1c42ee6301f8d52f47d14f62bbdb796571fa2d47" + integrity sha512-w+PkwCbYSFw8vpgWD0v7zRCl1FpY3fjDSQ3/N/wNd9Ffa4gPi8+4keqt99N3XW6F99t/mUzp2xAhNmfKWp95QA== + +unist-util-remove-position@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-2.0.1.tgz#5d19ca79fdba712301999b2b73553ca8f3b352cc" + integrity sha512-fDZsLYIe2uT+oGFnuZmy73K6ZxOPG/Qcm+w7jbEjaFcJgbQ6cqjs/eSPzXhsmGpAsWPkqZM9pYjww5QTn3LHMA== + dependencies: + unist-util-visit "^2.0.0" + +unist-util-remove@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unist-util-remove/-/unist-util-remove-2.1.0.tgz#b0b4738aa7ee445c402fda9328d604a02d010588" + integrity sha512-J8NYPyBm4baYLdCbjmf1bhPu45Cr1MWTm77qd9istEkzWpnN6O9tMsEbB2JhNnBCqGENRqEWomQ+He6au0B27Q== + dependencies: + unist-util-is "^4.0.0" + +unist-util-stringify-position@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz#cce3bfa1cdf85ba7375d1d5b17bdc4cada9bd9da" + integrity sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g== + dependencies: + "@types/unist" "^2.0.2" + +unist-util-visit-parents@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" + integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + +unist-util-visit@2.0.3, unist-util-visit@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" + integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== + dependencies: + "@types/unist" "^2.0.0" + unist-util-is "^4.0.0" + unist-util-visit-parents "^3.0.0" + +universalify@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== + +universalify@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" + integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== + +unload@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7" + integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA== + dependencies: + "@babel/runtime" "^7.6.2" + detect-node "^2.0.4" + +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ== + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +untildify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/untildify/-/untildify-2.1.0.tgz#17eb2807987f76952e9c0485fc311d06a826a2e0" + integrity sha512-sJjbDp2GodvkB0FZZcn7k6afVisqX5BZD7Yq3xp4nN2O15BBK0cLm3Vwn2vQaF7UDS0UUsrQMkkplmDI5fskig== + dependencies: + os-homedir "^1.0.0" + +upath@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" + integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== + +update-browserslist-db@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz#dbfc5a789caa26b1db8990796c2c8ebbce304824" + integrity sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA== + 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" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg== + +url-loader@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-4.1.1.tgz#28505e905cae158cf07c92ca622d7f237e70a4e2" + integrity sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA== + dependencies: + loader-utils "^2.0.0" + mime-types "^2.1.27" + schema-utils "^3.0.0" + +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha512-kbailJa29QrtXnxgq+DdCEGlbTeYM2eJUxsz6vjZavrCYPMIFHMKQmSKYAIuUK2i7hgPm28a8piX5NTUtM/LKQ== + dependencies: + punycode "1.3.2" + querystring "0.2.0" + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + +util.promisify@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha512-5KiHfsmkqacuKjkRkdV7SsfDJ2EGiPsK92s2MhNSY0craxjTdKTtqKsJaCWp4LW33ZZ0OPUv1WO/TFvNQRiQxQ== + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + +utila@~0.4: + version "0.4.0" + resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" + integrity sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA== + +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== + +uuid-browser@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" + integrity sha512-dsNgbLaTrd6l3MMxTtouOCFw4CBFc/3a+GgYA2YyrJvyQ1u6q4pcu3ktLoUZ/VN/Aw9WsauazbgsgdfVWgAKQg== + +uuid@^3.3.2: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== + +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + +v8-compile-cache@^2.0.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" + integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== + +v8-to-istanbul@^9.0.0, v8-to-istanbul@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz#b6f994b0b5d4ef255e17a0d17dc444a9f5132fa4" + integrity sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.12" + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== + +vfile-location@^3.0.0, vfile-location@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" + integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== + +vfile-message@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.4.tgz#5b43b88171d409eae58477d13f23dd41d52c371a" + integrity sha512-DjssxRGkMvifUOJre00juHoP9DPWuzjxKuMDrhNbk2TdaYYBNMStsNhEOt3idrtI12VQYM/1+iM0KOzXi4pxwQ== + dependencies: + "@types/unist" "^2.0.0" + unist-util-stringify-position "^2.0.0" + +vfile@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.2.1.tgz#03f1dce28fc625c625bc6514350fbdb00fa9e624" + integrity sha512-O6AE4OskCG5S1emQ/4gl8zK586RqA3srz3nfK/Viy0UPToBc5Trp9BVFb1u0CjsKrAWwnpr4ifM/KBXPWwJbCA== + dependencies: + "@types/unist" "^2.0.0" + is-buffer "^2.0.0" + unist-util-stringify-position "^2.0.0" + vfile-message "^2.0.0" + +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + +w3c-hr-time@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" + integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== + dependencies: + browser-process-hrtime "^1.0.0" + +w3c-xmlserializer@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz#06cdc3eefb7e4d0b20a560a5a3aeb0d2d9a65923" + integrity sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg== + dependencies: + xml-name-validator "^4.0.0" + +walker@^1.0.7, walker@^1.0.8, walker@~1.0.5: + version "1.0.8" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + +watchpack-chokidar2@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz#38500072ee6ece66f3769936950ea1771be1c957" + integrity sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww== + dependencies: + chokidar "^2.1.8" + +watchpack@^1.7.4: + version "1.7.5" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.7.5.tgz#1267e6c55e0b9b5be44c2023aed5437a2c26c453" + integrity sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ== + dependencies: + graceful-fs "^4.1.2" + neo-async "^2.5.0" + optionalDependencies: + chokidar "^3.4.1" + watchpack-chokidar2 "^2.0.1" + +watchpack@^2.2.0, watchpack@^2.3.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" + integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +wbuf@^1.1.0, wbuf@^1.7.3: + version "1.7.3" + resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" + integrity sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA== + dependencies: + minimalistic-assert "^1.0.0" + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg== + dependencies: + defaults "^1.0.3" + +web-namespaces@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" + integrity sha512-wYxSGajtmoP4WxfejAPIr4l0fVh+jeMXZb08wNc0tMg6xsfZXj3cECqIK0G7ZAqUq0PP8WlMDtaOGVBTAWztNw== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +webidl-conversions@^7.0.0: + version "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-cli@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" + integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.2.0" + "@webpack-cli/info" "^1.5.0" + "@webpack-cli/serve" "^1.7.0" + colorette "^2.0.14" + commander "^7.0.0" + cross-spawn "^7.0.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-dev-middleware@^3.7.3: + version "3.7.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.3.tgz#0639372b143262e2b84ab95d3b91a7597061c2c5" + integrity sha512-djelc/zGiz9nZj/U7PTBi2ViorGJXEWo/3ltkPbDyxCXhhEXkW0ce99falaok4TPj+AsxLiXJR0EBOb0zh9fKQ== + dependencies: + memory-fs "^0.4.1" + mime "^2.4.4" + mkdirp "^0.5.1" + range-parser "^1.2.1" + webpack-log "^2.0.0" + +webpack-dev-middleware@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-4.3.0.tgz#179cc40795882cae510b1aa7f3710cbe93c9333e" + integrity sha512-PjwyVY95/bhBh6VUqt6z4THplYcsvQ8YNNBTBM873xLVmw8FLeALn0qurHbs9EmcfhzQis/eoqypSnZeuUz26w== + dependencies: + colorette "^1.2.2" + mem "^8.1.1" + memfs "^3.2.2" + mime-types "^2.1.30" + range-parser "^1.2.1" + schema-utils "^3.0.0" + +webpack-dev-middleware@^5.3.1: + version "5.3.3" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" + integrity sha512-hj5CYrY0bZLB+eTO+x/j67Pkrquiy7kWepMHmUMoPsmcUaeEnQJqFzHJOyxgWlq746/wUuA64p9ta34Kyb01pA== + dependencies: + colorette "^2.0.10" + memfs "^3.4.3" + mime-types "^2.1.31" + range-parser "^1.2.1" + schema-utils "^4.0.0" + +webpack-dev-server@^4.9.3: + version "4.9.3" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz#2360a5d6d532acb5410a668417ad549ee3b8a3c9" + integrity sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.1" + ansi-html-community "^0.0.8" + bonjour-service "^1.0.11" + chokidar "^3.5.3" + colorette "^2.0.10" + compression "^1.7.4" + connect-history-api-fallback "^2.0.0" + default-gateway "^6.0.3" + express "^4.17.3" + graceful-fs "^4.2.6" + html-entities "^2.3.2" + http-proxy-middleware "^2.0.3" + ipaddr.js "^2.0.1" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.0.1" + serve-index "^1.9.1" + sockjs "^0.3.24" + spdy "^4.0.2" + webpack-dev-middleware "^5.3.1" + ws "^8.4.2" + +webpack-filter-warnings-plugin@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/webpack-filter-warnings-plugin/-/webpack-filter-warnings-plugin-1.2.1.tgz#dc61521cf4f9b4a336fbc89108a75ae1da951cdb" + integrity sha512-Ez6ytc9IseDMLPo0qCuNNYzgtUl8NovOqjIq4uAU8LTD4uoa1w1KpZyyzFtLTEMZpkkOkLfL9eN+KGYdk1Qtwg== + +webpack-hot-middleware@^2.25.1: + version "2.25.1" + resolved "https://registry.yarnpkg.com/webpack-hot-middleware/-/webpack-hot-middleware-2.25.1.tgz#581f59edf0781743f4ca4c200fd32c9266c6cf7c" + integrity sha512-Koh0KyU/RPYwel/khxbsDz9ibDivmUbrRuKSSQvW42KSDdO4w23WI3SkHpSUKHE76LrFnnM/L7JCrpBwu8AXYw== + dependencies: + ansi-html-community "0.0.8" + html-entities "^2.1.0" + querystring "^0.2.0" + strip-ansi "^6.0.0" + +webpack-log@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/webpack-log/-/webpack-log-2.0.0.tgz#5b7928e0637593f119d32f6227c1e0ac31e1b47f" + integrity sha512-cX8G2vR/85UYG59FgkoMamwHUIkSSlV3bBMRsbxVXVUk2j6NleCKjQ/WE9eYg9WY4w25O9w8wKP4rzNZFmUcUg== + dependencies: + ansi-colors "^3.0.0" + uuid "^3.3.2" + +webpack-merge@^5.7.3: + version "5.8.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" + integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== + dependencies: + clone-deep "^4.0.1" + wildcard "^2.0.0" + +webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" + integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== + dependencies: + source-list-map "^2.0.0" + source-map "~0.6.1" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-virtual-modules@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.2.2.tgz#20863dc3cb6bb2104729fff951fbe14b18bd0299" + integrity sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA== + dependencies: + debug "^3.0.0" + +webpack-virtual-modules@^0.4.1: + version "0.4.4" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.4.4.tgz#a19fcf371923c59c4712d63d7d194b1e4d8262cc" + integrity sha512-h9atBP/bsZohWpHnr+2sic8Iecb60GxftXsWNLLLSqewgIsGzByd2gcIID4nXcG+3tNe4GQG3dLcff3kXupdRA== + +webpack@4: + version "4.46.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" + integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== + dependencies: + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^6.4.1" + ajv "^6.10.2" + ajv-keywords "^3.4.1" + chrome-trace-event "^1.0.2" + enhanced-resolve "^4.5.0" + eslint-scope "^4.0.3" + json-parse-better-errors "^1.0.2" + loader-runner "^2.4.0" + loader-utils "^1.2.3" + memory-fs "^0.4.1" + micromatch "^3.1.10" + mkdirp "^0.5.3" + neo-async "^2.6.1" + node-libs-browser "^2.2.1" + schema-utils "^1.0.0" + tapable "^1.1.3" + terser-webpack-plugin "^1.4.3" + watchpack "^1.7.4" + webpack-sources "^1.4.1" + +"webpack@>=4.43.0 <6.0.0", webpack@^5.73.0, webpack@^5.9.0: + version "5.73.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.73.0.tgz#bbd17738f8a53ee5760ea2f59dce7f3431d35d38" + integrity sha512-svjudQRPPa0YiOYa2lM/Gacw0r6PvxptHj4FuEKQ2kX05ZLkjbVc5MnPs6its5j7IZljnIqSVo/OsY2X0IpHGA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^0.0.51" + "@webassemblyjs/ast" "1.11.1" + "@webassemblyjs/wasm-edit" "1.11.1" + "@webassemblyjs/wasm-parser" "1.11.1" + acorn "^8.4.1" + acorn-import-assertions "^1.7.6" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.9.3" + es-module-lexer "^0.9.0" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.9" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.1.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.1.3" + watchpack "^2.3.1" + webpack-sources "^3.2.3" + +websocket-driver@>=0.5.1, websocket-driver@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760" + integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg== + dependencies: + http-parser-js ">=0.5.1" + safe-buffer ">=5.1.0" + websocket-extensions ">=0.1.1" + +websocket-extensions@>=0.1.1: + version "0.1.4" + resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.4.tgz#7f8473bc839dfd87608adb95d7eb075211578a42" + integrity sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg== + +whatwg-encoding@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53" + integrity sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg== + dependencies: + iconv-lite "0.6.3" + +whatwg-mimetype@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz#5fa1a7623867ff1af6ca3dc72ad6b8a4208beba7" + integrity sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q== + +whatwg-url@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-10.0.0.tgz#37264f720b575b4a311bd4094ed8c760caaa05da" + integrity sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-boxed-primitive@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" + integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== + dependencies: + is-bigint "^1.0.1" + is-boolean-object "^1.1.0" + is-number-object "^1.0.4" + is-string "^1.0.5" + is-symbol "^1.0.3" + +which@^1.2.9: + version "1.3.1" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== + dependencies: + isexe "^2.0.0" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3" + integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg== + dependencies: + string-width "^1.0.2 || 2 || 3 || 4" + +widest-line@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca" + integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg== + dependencies: + string-width "^4.0.0" + +wildcard@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" + integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== + +word-wrap@^1.2.3, word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + +worker-farm@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.7.0.tgz#26a94c5391bbca926152002f69b84a4bf772e5a8" + integrity sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw== + dependencies: + errno "~0.1.7" + +worker-rpc@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/worker-rpc/-/worker-rpc-0.1.1.tgz#cb565bd6d7071a8f16660686051e969ad32f54d5" + integrity sha512-P1WjMrUB3qgJNI9jfmpZ/htmBEjFh//6l/5y8SD9hg1Ef5zTTVVoRjTrTEzPrNBQvmhMxkoTsjOXN10GWU7aCg== + dependencies: + microevent.ts "~0.1.1" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== + +write-file-atomic@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" + integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== + dependencies: + imurmurhash "^0.1.4" + is-typedarray "^1.0.0" + signal-exit "^3.0.2" + typedarray-to-buffer "^3.1.5" + +write-file-atomic@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.1.tgz#9faa33a964c1c85ff6f849b80b42a88c2c537c8f" + integrity sha512-nSKUxgAbyioruk6hU87QzVbY279oYT6uiwgDoujth2ju4mJ+TZau7SQBhtbTmUyuNYTuXnSyRn66FV0+eCgcrQ== + dependencies: + imurmurhash "^0.1.4" + signal-exit "^3.0.7" + +ws@^8.2.3, ws@^8.4.2: + version "8.8.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.0.tgz#8e71c75e2f6348dbf8d78005107297056cb77769" + integrity sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ== + +x-default-browser@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/x-default-browser/-/x-default-browser-0.4.0.tgz#70cf0da85da7c0ab5cb0f15a897f2322a6bdd481" + integrity sha512-7LKo7RtWfoFN/rHx1UELv/2zHGMx8MkZKDq1xENmOCTkfIqZJ0zZ26NEJX8czhnPXVcqS0ARjjfJB+eJ0/5Cvw== + optionalDependencies: + default-browser-id "^1.0.4" + +xml-name-validator@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835" + integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + +xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + +y18n@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" + integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +yallist@^4.0.0: + version "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: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + +yargs-parser@^20.2.2, yargs-parser@^20.2.9: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-parser@^21.0.0: + version "21.0.1" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.1.tgz#0267f286c877a4f0f728fceb6f8a3e4cb95c6e35" + integrity sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg== + +yargs@^16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1: + version "17.5.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" + integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zwitch@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" + integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw== diff --git a/jenkins/backend-dev.jenkinsfile b/jenkins/backend-dev.jenkinsfile new file mode 100644 index 00000000..19967532 --- /dev/null +++ b/jenkins/backend-dev.jenkinsfile @@ -0,0 +1,64 @@ +pipeline { + agent any + stages { + stage('Github') { + steps { + git branch: 'develop', url: 'https://github.com/woowacourse-teams/2022-dallog.git' + withCredentials([GitUsernamePassword(credentialsId: 'github-access-token', gitToolName: 'Default')]) { + sh 'git submodule update --init --recursive' + } + } + } + stage('SonarQube analysis') { + steps { + dir('backend') { + withSonarQubeEnv('SonarServer') { + sh './gradlew test sonarqube' + } + } + } + } + stage("Quality Gate") { + steps { + timeout(time: 1, unit: 'HOURS') { + waitForQualityGate abortPipeline: true + } + } + } + stage('Build') { + steps { + dir('backend') { + sh "./gradlew bootJar" + } + } + } + stage('Deploy') { + steps { + dir('backend/build/libs') { + sshagent(credentials: ['key-dallog']) { + sh "scp -o StrictHostKeyChecking=no backend-0.0.1-SNAPSHOT.jar ubuntu@${env.BACKEND_DEV_IP}:/home/ubuntu" + sh "ssh -o StrictHostKeyChecking=no ubuntu@${env.BACKEND_DEV_IP} 'sh run.sh' &" + } + } + } + } + } + post { + success { + discordSend title: "백엔드 개발 서버 배포에 성공하였습니다 ✨", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_BACKEND_DEV_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + failure { + discordSend title: "백엔드 개발 서버 배포에 실패하였습니다 ❌", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_BACKEND_DEV_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + } +} diff --git a/jenkins/backend-prod.jenkinsfile b/jenkins/backend-prod.jenkinsfile new file mode 100644 index 00000000..dc36e0b7 --- /dev/null +++ b/jenkins/backend-prod.jenkinsfile @@ -0,0 +1,48 @@ +pipeline { + agent any + stages { + stage('Github') { + steps { + git branch: 'main', url: 'https://github.com/woowacourse-teams/2022-dallog.git' + withCredentials([GitUsernamePassword(credentialsId: 'github-access-token', gitToolName: 'Default')]) { + sh 'git submodule update --init --recursive' + } + } + } + stage('Build') { + steps { + dir('backend') { + sh "./gradlew bootJar" + } + } + } + stage('Deploy') { + steps { + dir('backend/build/libs') { + sshagent(credentials: ['key-dallog']) { + sh "scp -o StrictHostKeyChecking=no backend-0.0.1-SNAPSHOT.jar ubuntu@${env.BACKEND_PROD_IP}:/home/ubuntu" + sh "ssh -o StrictHostKeyChecking=no ubuntu@${env.BACKEND_PROD_IP} 'sh run.sh' &" + } + } + } + } + } + post { + success { + discordSend title: "백엔드 프로덕션 서버 배포에 성공하였습니다 ✨", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_BACKEND_PROD_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + failure { + discordSend title: "백엔드 프로덕션 서버 배포에 실패하였습니다 ❌", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_BACKEND_PROD_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + } +} diff --git a/jenkins/frontend-dev.jenkinsfile b/jenkins/frontend-dev.jenkinsfile new file mode 100644 index 00000000..aae120ed --- /dev/null +++ b/jenkins/frontend-dev.jenkinsfile @@ -0,0 +1,53 @@ +API_URL = "https://dev-api.dallog.me" + +pipeline { + agent any + + stages { + stage('Github') { + steps { + git branch: 'develop', url: 'https://github.com/woowacourse-teams/2022-dallog.git' + } + } + stage('Build') { + steps { + dir('frontend') { + sh "echo 'API_URL = ${API_URL}' > .env" + nodejs(nodeJSInstallationName: 'NodeJS 16.14.0') { + sh "npm install -g yarn" + sh "yarn" + sh "yarn prod-build" + } + } + } + } + stage('Deploy') { + steps { + dir('frontend/dist') { + sshagent(credentials: ['key-dallog']) { + sh "scp -o StrictHostKeyChecking=no -r ./* ubuntu@${env.FRONTEND_DEV_IP}:/home/ubuntu/" + sh "ssh -o StrictHostKeyChecking=no ubuntu@${env.FRONTEND_DEV_IP} 'sudo cp -r ./* /usr/share/nginx/html/ && sudo rm -rf ./*'" + } + } + } + } + } + post { + success { + discordSend title: "프론트엔드 개발 서버 배포에 성공하였습니다 ✨", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_FRONTEND_DEV_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + failure { + discordSend title: "프론트엔드 개발 서버 배포에 실패하였습니다 ❌", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_FRONTEND_DEV_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + } +} diff --git a/jenkins/frontend-prod.jenkinsfile b/jenkins/frontend-prod.jenkinsfile new file mode 100644 index 00000000..5504ac05 --- /dev/null +++ b/jenkins/frontend-prod.jenkinsfile @@ -0,0 +1,53 @@ +API_URL = "https://api.dallog.me" + +pipeline { + agent any + + stages { + stage('Github') { + steps { + git branch: 'main', url: 'https://github.com/woowacourse-teams/2022-dallog.git' + } + } + stage('Build') { + steps { + dir('frontend') { + sh "echo 'API_URL = ${API_URL}' > .env" + nodejs(nodeJSInstallationName: 'NodeJS 16.14.0') { + sh "npm install -g yarn" + sh "yarn" + sh "yarn prod-build" + } + } + } + } + stage('Deploy') { + steps { + dir('frontend/dist') { + sshagent(credentials: ['key-dallog']) { + sh "scp -o StrictHostKeyChecking=no -r ./* ubuntu@${env.FRONTEND_PROD_IP}:/home/ubuntu/" + sh "ssh -o StrictHostKeyChecking=no ubuntu@${env.FRONTEND_PROD_IP} 'sudo cp -r ./* /usr/share/nginx/html/ && sudo rm -rf ./*'" + } + } + } + } + } + post { + success { + discordSend title: "프론트엔드 프로덕션 서버 배포에 성공하였습니다 ✨", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_FRONTEND_PROD_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + failure { + discordSend title: "프론트엔드 프로덕션 서버 배포에 실패하였습니다 ❌", + description: "빌드 번호 #${env.BUILD_NUMBER}", + link: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}", + result: currentBuild.currentResult, + webhookURL: env.DISCORD_FRONTEND_PROD_WEBHOOK, + footer: "http://jenkins.dallog.me:8080/job/${env.JOB_NAME}/${env.BUILD_NUMBER}" + } + } +}