diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..964b19db --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +## 📋 ì´ìŠˆ ë‚´ìš© + + +## ✅ ì²´í¬ë¦¬ìŠ¤íŠ¸ +- [ ] + +## 📠레í¼ëŸ°ìŠ¤ \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..c5c07b8c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +## #ï¸âƒ£ì—°ê´€ëœ ì´ìŠˆ + +## 📋 PR 개요 + + +## ✅ PR 유형 +- [ ] 새로운 기능 추가 +- [ ] 버그 수정 +- [ ] CSS 등 ì‚¬ìš©ìž UI ë””ìžì¸ 변경 +- [ ] ì½”ë“œì— ì˜í–¥ì„ 주지 않는 변경사항(오타 수정, 탭 사ì´ì¦ˆ 변경, 변수명 변경) +- [ ] 코드 ë¦¬íŒ©í† ë§ +- [ ] ì£¼ì„ ì¶”ê°€ ë° ìˆ˜ì • +- [ ] 문서 수정 +- [ ] 테스트 추가, 테스트 ë¦¬íŒ©í† ë§ +- [ ] 빌드 부분 í˜¹ì€ íŒ¨í‚¤ì§€ 매니저 수정 +- [ ] íŒŒì¼ í˜¹ì€ í´ë”명 수정 +- [ ] íŒŒì¼ í˜¹ì€ í´ë” ì‚­ì œ + +## âœï¸ 변경 사항 +- ë‚´ìš©ì„ ì ì–´ì£¼ì„¸ìš”. \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..2b6ae04b --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,59 @@ +name: Build and Deploy to EC2 + +on: + push: + branches: [ "dev_backend" ] + pull_request: + branches: [ "dev_backend" ] + +defaults: + run: + shell: bash + +env: + AWS_S3_BUCKET: howabouttrip-backend-s3 + AWS_CODE_DEPLOY_APPLICATION: HowAboutTrip-Backend-CD + AWS_CODE_DEPLOY_GROUP: HowAboutTrip-Backend-CD-Group + +jobs: + deploy: + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'corretto' + + - name: Grant execute permission for gradlew + working-directory: ./backend + run: chmod +x ./gradlew + shell: bash + + - name: Build Project and Test + working-directory: ./backend + run: ./gradlew build test + + - name: Setup MySQL + uses: mirromutth/mysql-action@v1.1 + with: + mysql database: 'trip' + mysql user: ${DB_USERNAME} + mysql password: ${DB_PASSWORD} + + - name: Configure AWS credential + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-region: ap-northeast-2 + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + + - name: Upload to S3 + working-directory: ./backend + run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://${{ env.AWS_S3_BUCKET }}/HowAboutTrip-Backend-EC2/$GITHUB_SHA.zip --source . + + - name: Code Deploy to EC2 + working-directory: ./backend + run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=${{ env.AWS_S3_BUCKET }},key=HowAboutTrip-Backend-EC2/$GITHUB_SHA.zip,bundleType=zip diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..bdce34a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,294 @@ +# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,gradle,intellij+all,java,intellij,sonar +# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,gradle,intellij+all,java,intellij,sonar + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff + +# AWS User-specific + +# Generated files + +# Sensitive or high-churn files + +# Gradle + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake + +# Mongo Explorer plugin + +# File-based project format + +# IntelliJ + +# mpeltonen/sbt-idea plugin + +# JIRA plugin + +# Cursive Clojure plugin + +# SonarLint plugin + +# Crashlytics plugin (for Android Studio and IntelliJ) + +# Editor-based Rest Client + +# Android studio 3.1+ serialized cache file + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +replay_pid* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Sonar ### +#Sonar generated dir +/.sonar/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +**/build/ +!src/**/build/ +.idea/sonarlint + + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Avoid ignore Gradle wrappper properties +!gradle-wrapper.properties + +# Cache of project +.gradletasknamecache + +# Eclipse Gradle plugin generated files +# Eclipse Core +.project +# JDT-specific (Eclipse Java Development Tools) +.classpath + +### Gradle Patch ### +# Java heap dump +*.hprof + +# End of https://www.toptal.com/developers/gitignore/api/macos,windows,gradle,intellij+all,java,intellij,sonar +*.pptx diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..92d600d2 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +.idea/sonarlint + diff --git a/.idea/ISP.iml b/.idea/ISP.iml new file mode 100644 index 00000000..55a758cf --- /dev/null +++ b/.idea/ISP.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..6b7d6740 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..f9030907 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..fdc392fe --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jpa-buddy.xml b/.idea/jpa-buddy.xml new file mode 100644 index 00000000..898e07a6 --- /dev/null +++ b/.idea/jpa-buddy.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..a449ab52 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..1900631b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/backend.iml b/.idea/modules/backend.iml new file mode 100644 index 00000000..1bd5cb3e --- /dev/null +++ b/.idea/modules/backend.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules/backend.main.iml b/.idea/modules/backend.main.iml new file mode 100644 index 00000000..6bd375ab --- /dev/null +++ b/.idea/modules/backend.main.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules/backend.test.iml b/.idea/modules/backend.test.iml new file mode 100644 index 00000000..10c8c277 --- /dev/null +++ b/.idea/modules/backend.test.iml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/6/065e95c874597c49590257f1b37ccd1a809185ce b/.idea/sonarlint/issuestore/0/6/065e95c874597c49590257f1b37ccd1a809185ce new file mode 100644 index 00000000..4d440a06 --- /dev/null +++ b/.idea/sonarlint/issuestore/0/6/065e95c874597c49590257f1b37ccd1a809185ce @@ -0,0 +1,3 @@ + +{ +java:S1128"`Remove this unused import 'com.isp.backend.global.exception.member.MemberNotActivatedException'.(ÙŸ‘žûÿÿÿÿ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/2/1/21d4b1ec4e3be89735ba8533a58eee07a372667a b/.idea/sonarlint/issuestore/2/1/21d4b1ec4e3be89735ba8533a58eee07a372667a new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/2/3/2363d464b7abe433f93c6cab2d693ce0bd00ed52 b/.idea/sonarlint/issuestore/2/3/2363d464b7abe433f93c6cab2d693ce0bd00ed52 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/2/3/23e463b2e953a590c1c20f64075baaa7a9b12eb7 b/.idea/sonarlint/issuestore/2/3/23e463b2e953a590c1c20f64075baaa7a9b12eb7 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/2/5/25e7bc2e784c14c74b34d15b334f63726e22b891 b/.idea/sonarlint/issuestore/2/5/25e7bc2e784c14c74b34d15b334f63726e22b891 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/3/3/330813660e7d0a96999bd35a5cebf19a0e34105b b/.idea/sonarlint/issuestore/3/3/330813660e7d0a96999bd35a5cebf19a0e34105b new file mode 100644 index 00000000..3b3481d4 --- /dev/null +++ b/.idea/sonarlint/issuestore/3/3/330813660e7d0a96999bd35a5cebf19a0e34105b @@ -0,0 +1,2 @@ + +o java:S120"ZRename this package name to match the regular expression '^[a-z_]+(\.[a-z_][a-z0-9_]*)*$'.(ª¥¿ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/3/d/3de99115bf130d9ac74cb84cf73160d47a7d6f22 b/.idea/sonarlint/issuestore/3/d/3de99115bf130d9ac74cb84cf73160d47a7d6f22 new file mode 100644 index 00000000..3b525412 --- /dev/null +++ b/.idea/sonarlint/issuestore/3/d/3de99115bf130d9ac74cb84cf73160d47a7d6f22 @@ -0,0 +1,2 @@ + +t java:S120"ZRename this package name to match the regular expression '^[a-z_]+(\.[a-z_][a-z0-9_]*)*$'.(¯žÄüÿÿÿÿ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/3/e/3e7baf5788b82805f2e8de304ea856c7f50e4463 b/.idea/sonarlint/issuestore/3/e/3e7baf5788b82805f2e8de304ea856c7f50e4463 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/4/5/45fb1e19d829b0c832b3cac40fc3f67fd0df95a2 b/.idea/sonarlint/issuestore/4/5/45fb1e19d829b0c832b3cac40fc3f67fd0df95a2 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/4/7/4782521f7af0b5e4af513038bfb522ed4dc91c53 b/.idea/sonarlint/issuestore/4/7/4782521f7af0b5e4af513038bfb522ed4dc91c53 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/4/b/4bea8a271179cc56b85f48f234ab6200a9668cc5 b/.idea/sonarlint/issuestore/4/b/4bea8a271179cc56b85f48f234ab6200a9668cc5 new file mode 100644 index 00000000..39e0c846 --- /dev/null +++ b/.idea/sonarlint/issuestore/4/b/4bea8a271179cc56b85f48f234ab6200a9668cc5 @@ -0,0 +1,7 @@ + +f +java:S11925"KDefine a constant instead of duplicating this literal "token_type" 4 times.(«¿‹€ûÿÿÿÿ +~ +java:S14889"cImmediately return this expression instead of assigning it to the temporary variable "accessToken".(Ì글üÿÿÿÿ +z +java:S1488M"dImmediately return this expression instead of assigning it to the temporary variable "refreshToken".(Ж¬À \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/5/4/5427e158efe231e7f47bb4a5853deac93cbcae0f b/.idea/sonarlint/issuestore/5/4/5427e158efe231e7f47bb4a5853deac93cbcae0f new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/6/4/64208b6155f2d4e2e3fe0106eb90088af7d3b2e4 b/.idea/sonarlint/issuestore/6/4/64208b6155f2d4e2e3fe0106eb90088af7d3b2e4 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/6/a/6a2dd50a3f6743dd249b62f3b60675b35f1da4c3 b/.idea/sonarlint/issuestore/6/a/6a2dd50a3f6743dd249b62f3b60675b35f1da4c3 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/3/739b9a34c92bd055e28df022d452c330a2c0c3c2 b/.idea/sonarlint/issuestore/7/3/739b9a34c92bd055e28df022d452c330a2c0c3c2 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/b/7b3a79f800e85c8b44fcfc250f8be64bf945f627 b/.idea/sonarlint/issuestore/7/b/7b3a79f800e85c8b44fcfc250f8be64bf945f627 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/d/7d88f08545b388a8671b7bf33741a819d744615e b/.idea/sonarlint/issuestore/7/d/7d88f08545b388a8671b7bf33741a819d744615e new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/e/7e16691a9fc60084a157565ec422c8094d3df67c b/.idea/sonarlint/issuestore/7/e/7e16691a9fc60084a157565ec422c8094d3df67c new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/e/7e30ae1cb84f6db862cf23385cd180d2ee224023 b/.idea/sonarlint/issuestore/7/e/7e30ae1cb84f6db862cf23385cd180d2ee224023 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/7/f/7f150bb1711df2425db4160648cf05d67e224656 b/.idea/sonarlint/issuestore/7/f/7f150bb1711df2425db4160648cf05d67e224656 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/8/6/86da04119b555391860b194466103596afaf9d52 b/.idea/sonarlint/issuestore/8/6/86da04119b555391860b194466103596afaf9d52 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/8/a/8acaf61470b8ba9aa6049283e1007d390bfaa9be b/.idea/sonarlint/issuestore/8/a/8acaf61470b8ba9aa6049283e1007d390bfaa9be new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/8/e/8eb84e93349d79436a7fe24fb4d9f0c36b25c479 b/.idea/sonarlint/issuestore/8/e/8eb84e93349d79436a7fe24fb4d9f0c36b25c479 new file mode 100644 index 00000000..aad5046c --- /dev/null +++ b/.idea/sonarlint/issuestore/8/e/8eb84e93349d79436a7fe24fb4d9f0c36b25c479 @@ -0,0 +1,3 @@ + +H +java:S2699 "-Add at least one assertion to this test case.(áùêÌùÿÿÿÿ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/9/5/95352dc9683fc5665e000eb792c2e520ec020fa5 b/.idea/sonarlint/issuestore/9/5/95352dc9683fc5665e000eb792c2e520ec020fa5 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/9/5/954cd358297ecfdc47b7f4515dcf34dfb67f86aa b/.idea/sonarlint/issuestore/9/5/954cd358297ecfdc47b7f4515dcf34dfb67f86aa new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/9/6/9691797aa04d85d3238a4a45d4fa240168a3d92d b/.idea/sonarlint/issuestore/9/6/9691797aa04d85d3238a4a45d4fa240168a3d92d new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/9/8/982d85ea34e066de5118505e73079ae8c0bbd2f1 b/.idea/sonarlint/issuestore/9/8/982d85ea34e066de5118505e73079ae8c0bbd2f1 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/9/8/988ee194fffff9c0b19490c3f17aec47381ae2a0 b/.idea/sonarlint/issuestore/9/8/988ee194fffff9c0b19490c3f17aec47381ae2a0 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/a/4/a4d45d939048b362dfb05cd73baff88946c380da b/.idea/sonarlint/issuestore/a/4/a4d45d939048b362dfb05cd73baff88946c380da new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/a/5/a5cc2925ca8258af241be7e5b0381edf30266302 b/.idea/sonarlint/issuestore/a/5/a5cc2925ca8258af241be7e5b0381edf30266302 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/a/9/a9e70078b70425918411b78b3c53d5603c3ce5c7 b/.idea/sonarlint/issuestore/a/9/a9e70078b70425918411b78b3c53d5603c3ce5c7 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/a/d/ad8639f749dc49476f473a3e26d54daae5f52c58 b/.idea/sonarlint/issuestore/a/d/ad8639f749dc49476f473a3e26d54daae5f52c58 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/a/d/ade58d0c2cdff8bd319d05c89afd49ae61d4c888 b/.idea/sonarlint/issuestore/a/d/ade58d0c2cdff8bd319d05c89afd49ae61d4c888 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/b/3/b329b210b93dd6d96e8198d55bd14bd3361ac708 b/.idea/sonarlint/issuestore/b/3/b329b210b93dd6d96e8198d55bd14bd3361ac708 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/b/5/b556167afb84bda333b2fab7fc28934e32b459d9 b/.idea/sonarlint/issuestore/b/5/b556167afb84bda333b2fab7fc28934e32b459d9 new file mode 100644 index 00000000..17cc0b43 --- /dev/null +++ b/.idea/sonarlint/issuestore/b/5/b556167afb84bda333b2fab7fc28934e32b459d9 @@ -0,0 +1,7 @@ + +x +java:S1128"]Remove this unused import 'org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder'.(åÅÚ…ûÿÿÿÿ +s +java:S1128"]Remove this unused import 'org.springframework.security.crypto.password.NoOpPasswordEncoder'.(ƒ»ÅÅ +t +java:S1128"YRemove this unused import 'org.springframework.security.crypto.password.PasswordEncoder'.(‰¡ßáÿÿÿÿÿ \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/b/d/bd7108983578f649cb6893456714cdace9161089 b/.idea/sonarlint/issuestore/b/d/bd7108983578f649cb6893456714cdace9161089 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/b/f/bf8a91d94ba6d87e3accbb8ff83cce215d706910 b/.idea/sonarlint/issuestore/b/f/bf8a91d94ba6d87e3accbb8ff83cce215d706910 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/c/2/c2919d98ae33730c77963d99ccd650551213ef68 b/.idea/sonarlint/issuestore/c/2/c2919d98ae33730c77963d99ccd650551213ef68 new file mode 100644 index 00000000..e69de29b diff --git a/.idea/sonarlint/issuestore/c/5/c50f0cf17624e9670cc09596eb1c566241c04477 b/.idea/sonarlint/issuestore/c/5/c50f0cf17624e9670cc09596eb1c566241c04477 new file mode 100644 index 00000000..292236ec --- /dev/null +++ b/.idea/sonarlint/issuestore/c/5/c50f0cf17624e9670cc09596eb1c566241c04477 @@ -0,0 +1,8 @@ + +c +java:S11921"MDefine a constant instead of duplicating this literal "Access-Token" 3 times.(¢ìäÈ +i +java:S11922"NDefine a constant instead of duplicating this literal "Refresh-Token" 3 times.(óŸãáûÿÿÿÿ +] java:S125" + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..56ab882d --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +

+ + +# 여행어때 +

+ +``` +âœˆï¸ ì—¬í–‰ ì¼ì •ì´ 필요해? 우리가 만들어줄게! +``` +
+ +## CONTRIBUTORS +| 김수현
([@ksh-g001](https://github.com/ksh-g001)) | í™ë‹¤ì—°
([@Dayeon-Hong](https://github.com/Dayon-Hong)) | 김지윤
([@yo0oni](https://github.com/yo0oni)) | +|:---------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:| +| | | +| `Android` | `Backend` | `Backend`
`DevOps` | +
+ +## TECH STACK +### Android +- Android App Architecture +- Retrofit2, OkHttp3 +- Coroutine & Flow +- Data Binding & View Binding +- Google OAuth +- Lottie +- Google Map SDK & Google Place SDK + +### Backend +- Spring Boot 3.2.2 with Java 17 +- MySQL & JPA +- AWS EC2, CodeDeploy for deploy +- AWS S3 +- GPT-4o API +- Amadeus API + +
+ +## [ERD](https://dbdiagram.io/d/HowAboutTrip-6634f0ee5b24a634d071a4d7) +![HowAboutTrip](https://github.com/tukcomCD2024/ISP/assets/95288297/6eb6e6a6-7fde-413e-adfd-3df8f934214d) + + +
+
+ +## SCREENSHOTS +| 뷰 | 1 | 2 | 3 | 4 | +|:-------------:|:---------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:|:---------------------------------------------------------------------------------------------------------------------------:| +| 스플래쉬
ë¡œê·¸ì¸ | | | | | +| ì¼ì •
ìƒì„±í•˜ê¸° | 1 | | | | +| ìƒì„±ëœ
ì¼ì •
수정하기 | | | | | +| ì¼ì •
확ì¸í•˜ê¸° | | | | | +| 항공권
ë° ìˆ™ë°•
예약하기
(작업 중) | | | | | + + +
diff --git a/android/HowAboutTrip/.gitignore b/android/HowAboutTrip/.gitignore new file mode 100644 index 00000000..aa724b77 --- /dev/null +++ b/android/HowAboutTrip/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/android/HowAboutTrip/.idea/.gitignore b/android/HowAboutTrip/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/android/HowAboutTrip/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/android/HowAboutTrip/.idea/appInsightsSettings.xml b/android/HowAboutTrip/.idea/appInsightsSettings.xml new file mode 100644 index 00000000..23b2e1fe --- /dev/null +++ b/android/HowAboutTrip/.idea/appInsightsSettings.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/compiler.xml b/android/HowAboutTrip/.idea/compiler.xml new file mode 100644 index 00000000..b589d56e --- /dev/null +++ b/android/HowAboutTrip/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/deploymentTargetDropDown.xml b/android/HowAboutTrip/.idea/deploymentTargetDropDown.xml new file mode 100644 index 00000000..0c0c3383 --- /dev/null +++ b/android/HowAboutTrip/.idea/deploymentTargetDropDown.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/gradle.xml b/android/HowAboutTrip/.idea/gradle.xml new file mode 100644 index 00000000..0897082f --- /dev/null +++ b/android/HowAboutTrip/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/kotlinc.xml b/android/HowAboutTrip/.idea/kotlinc.xml new file mode 100644 index 00000000..8d81632f --- /dev/null +++ b/android/HowAboutTrip/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/migrations.xml b/android/HowAboutTrip/.idea/migrations.xml new file mode 100644 index 00000000..f8051a6f --- /dev/null +++ b/android/HowAboutTrip/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/misc.xml b/android/HowAboutTrip/.idea/misc.xml new file mode 100644 index 00000000..8978d23d --- /dev/null +++ b/android/HowAboutTrip/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/.idea/render.experimental.xml b/android/HowAboutTrip/.idea/render.experimental.xml new file mode 100644 index 00000000..8ec256a5 --- /dev/null +++ b/android/HowAboutTrip/.idea/render.experimental.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/.gitignore b/android/HowAboutTrip/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/android/HowAboutTrip/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/android/HowAboutTrip/app/build.gradle.kts b/android/HowAboutTrip/app/build.gradle.kts new file mode 100644 index 00000000..b3331632 --- /dev/null +++ b/android/HowAboutTrip/app/build.gradle.kts @@ -0,0 +1,86 @@ +import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties + +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("com.google.gms.google-services") + id("kotlin-kapt") +} + +android { + namespace = "com.project.how" + compileSdk = 34 + + defaultConfig { + applicationId = "com.project.how" + minSdk = 29 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + buildFeatures{ + dataBinding = true + buildConfig = true + } + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + + resValue("string", "API_SERVER", getApiKey("api_server")) + + buildConfigField("String", "GOOGLE_OAUTH_ID", getApiKey("google_oauth_id")) + buildConfigField("String", "GOOGLE_MAP_API_KEY", getApiKey("google_map_api_key")) + buildConfigField("String", "GOOGLE_SERVER_ID", getApiKey("google_server_id")) + buildConfigField("String", "API_SERVER", getApiKey("api_server")) + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +fun getApiKey(propertyKey: String):String = gradleLocalProperties(rootDir).getProperty(propertyKey) + +dependencies { + + implementation("androidx.core:core-ktx:1.12.0") + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("com.google.android.material:material:1.11.0") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") + implementation("com.airbnb.android:lottie:6.3.0") + + implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.7.0") + implementation("androidx.fragment:fragment-ktx:1.7.0-alpha08") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") + implementation("androidx.datastore:datastore-preferences:1.0.0") + + implementation(platform("com.google.firebase:firebase-bom:32.7.0")) + implementation("com.google.firebase:firebase-analytics") + implementation("com.google.firebase:firebase-auth:22.3.1") + implementation("com.google.android.gms:play-services-auth:20.7.0") + + implementation("com.squareup.retrofit:retrofit:2.0.0-beta2") + implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("com.squareup.okhttp3:okhttp:5.0.0-alpha.12") + implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") + implementation("com.squareup.retrofit2:converter-scalars:2.9.0") + + implementation("com.github.bumptech.glide:glide:5.0.0-rc01") + + + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/google-services.json b/android/HowAboutTrip/app/google-services.json new file mode 100644 index 00000000..1d353f25 --- /dev/null +++ b/android/HowAboutTrip/app/google-services.json @@ -0,0 +1,47 @@ +{ + "project_info": { + "project_number": "740845966307", + "project_id": "howabouttrip", + "storage_bucket": "howabouttrip.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:740845966307:android:71aff1bfe733b688ad46d6", + "android_client_info": { + "package_name": "com.project.how" + } + }, + "oauth_client": [ + { + "client_id": "740845966307-oulmuvm5mv595vlu9hf1a3j5n4ouvskg.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.project.how", + "certificate_hash": "8f29dbe96504294182411cc8b5af35fee8e57de8" + } + }, + { + "client_id": "740845966307-vhqequ0l999eglm9p87su14da4bjreid.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyDg0_7_UQD4gNj9XmKnQZpBGREl0r9CZ_Q" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "740845966307-vhqequ0l999eglm9p87su14da4bjreid.apps.googleusercontent.com", + "client_type": 3 + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/proguard-rules.pro b/android/HowAboutTrip/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/android/HowAboutTrip/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/androidTest/java/com/project/how/ExampleInstrumentedTest.kt b/android/HowAboutTrip/app/src/androidTest/java/com/project/how/ExampleInstrumentedTest.kt new file mode 100644 index 00000000..4e1dac40 --- /dev/null +++ b/android/HowAboutTrip/app/src/androidTest/java/com/project/how/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.project.how + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.project.how", appContext.packageName) + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/AndroidManifest.xml b/android/HowAboutTrip/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..af2348be --- /dev/null +++ b/android/HowAboutTrip/app/src/main/AndroidManifest.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/adapter/CalendarAdapter.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/adapter/CalendarAdapter.kt new file mode 100644 index 00000000..1811f8be --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/adapter/CalendarAdapter.kt @@ -0,0 +1,154 @@ +package com.project.how.adapter + +import android.graphics.Color +import android.util.Log +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.project.how.R +import com.project.how.databinding.CalendarDayItemBinding +import java.time.LocalDate + +class CalendarAdapter( + repositoryDays : List, + selectedDate : LocalDate, + private val onItemClickListener : OnItemClickListener) + : RecyclerView.Adapter() { + private var sd = selectedDate + private var selected : MutableList = mutableListOf() + private val selectedMonthDays : MutableMap> = mutableMapOf() + private val selectedTwoDay : MutableMap = mutableMapOf() + private var days = repositoryDays + private var emptyCount = 0 + private var dayCount = 1 + private var selectedCount = 0 + + init { + Log.d("CalendarAdapter", "init repositoryDays.size : ${repositoryDays.size}\n${days.size}") + initSelected() + } + + inner class ViewHolder(val binding : CalendarDayItemBinding) : RecyclerView.ViewHolder(binding.root){ + fun bind(data: Int, position: Int){ + if(data != EMPTY){ + binding.day.text = dayCount++.toString() + val firstSelected = selectedTwoDay[FIRST] + val secondSelected = selectedTwoDay[SECOND] + if(firstSelected != null && secondSelected != null){ + if(firstSelected.month.value < secondSelected.month.value){ + if((position - emptyCount) < secondSelected.dayOfMonth){ + binding.day.setTextColor(Color.parseColor("#00000000")) + binding.background.setBackgroundResource(R.color.black) + } + }else if(firstSelected.month.value > secondSelected.month.value){ + if((position - emptyCount) < firstSelected.dayOfMonth){ + binding.day.setTextColor(Color.parseColor("#00000000")) + binding.background.setBackgroundResource(R.color.black) + } + }else{ + if(firstSelected.dayOfMonth < secondSelected.dayOfMonth){ + if((firstSelected.dayOfMonth < (position - emptyCount)) && (secondSelected.dayOfMonth > (position - emptyCount))) { + binding.day.setTextColor(Color.parseColor("#00000000")) + binding.background.setBackgroundResource(R.color.black) + } + }else if(firstSelected.dayOfMonth > secondSelected.dayOfMonth){ + if((firstSelected.dayOfMonth > (position - emptyCount)) && (secondSelected.dayOfMonth < (position - emptyCount))) { + binding.day.setTextColor(Color.parseColor("#00000000")) + binding.background.setBackgroundResource(R.color.black) + } + } + } + } + binding.day.setOnClickListener { +// selected[position] = !selected[position] + selectedTwoDay[selectedCount++%2] = sd.withDayOfMonth(position-emptyCount) + val sd = sd.withDayOfMonth(position-emptyCount) + onItemClickListener.onItemClickListener(data, selected, position, sd) + } + + changeColor(binding, position) + }else{ + binding.day.text = " " + emptyCount++ + } + + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder( + CalendarDayItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + + override fun getItemCount(): Int = days.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val data = days[position] + holder.bind(data, position) + } + + fun update(newDays : List, sd: LocalDate){ + days = newDays + dayCount = 1 + emptyCount = 0 + observeMonthChange(sd) + notifyDataSetChanged() + } + + fun observeMonthChange(sd : LocalDate){ + selectedMonthDays[this.sd.month.value] = selected + this.sd = sd + initSelected() + } + + private fun initSelected(){ + Log.d("CalendarAdapter", "Before ${selected.size}\n${days.size}") + selected = mutableListOf() + for(i in days.indices){ + selected.add(false) + } + Log.d("CalendarAdapter", "After ${selected.size}\n${days.size}") + } + + fun resetSelected(){ + for(i in days.indices) + selected[i] = false + dayCount = 1 + emptyCount = 0 + notifyDataSetChanged() + } + + private fun changeColor(binding: CalendarDayItemBinding, position: Int){ + if(selected[position]){ + binding.day.setTextColor(Color.parseColor("#FFFFFFFF")) + binding.background.setBackgroundResource(R.drawable.black_oval_day_background) + }else{ + binding.day.setTextColor(Color.parseColor("#FF000000")) + binding.background.setBackgroundResource(R.drawable.white_oval_day_background) + } + } + + fun getSelectedDays() : MutableMap?{ + if (selectedTwoDay[FIRST] != null || selectedTwoDay[SECOND] != null) + return selectedTwoDay + return null + } + + fun getSelectedDay() : LocalDate?{ + if (selectedTwoDay[FIRST] != null) + return selectedTwoDay[FIRST] + return null + } + + interface OnItemClickListener{ + fun onItemClickListener(data : Int, selected : MutableList, position: Int, sd: LocalDate){} + } + companion object{ + private const val EMPTY = 99 + private const val FIRST = 0 + private const val SECOND = 1 + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/MemberInfo.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/MemberInfo.kt new file mode 100644 index 00000000..2c2958fe --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/MemberInfo.kt @@ -0,0 +1,7 @@ +package com.project.how.data_class + +data class MemberInfo( + val name : String, + val birth : String, + val phone : String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/Tokens.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/Tokens.kt new file mode 100644 index 00000000..07f922ef --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/Tokens.kt @@ -0,0 +1,6 @@ +package com.project.how.data_class + +data class Tokens( + val accessToken : String, + val refreshToken: String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/AuthRecreateRequest.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/AuthRecreateRequest.kt new file mode 100644 index 00000000..0b3365f4 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/AuthRecreateRequest.kt @@ -0,0 +1,7 @@ +package com.project.how.data_class.dto + +import com.google.gson.annotations.SerializedName + +data class AuthRecreateRequest( + @SerializedName("refreshToken") val refreshToken : String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/EmptyResponse.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/EmptyResponse.kt new file mode 100644 index 00000000..6be866f3 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/EmptyResponse.kt @@ -0,0 +1,3 @@ +package com.project.how.data_class.dto + +class EmptyResponse() diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/GetInfoResponse.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/GetInfoResponse.kt new file mode 100644 index 00000000..2359be4c --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/GetInfoResponse.kt @@ -0,0 +1,10 @@ +package com.project.how.data_class.dto + +import com.google.gson.annotations.SerializedName + +data class GetInfoResponse( + @SerializedName("uid") val uid : String, + @SerializedName("name") val name : String, + @SerializedName("birth") val birth : String, + @SerializedName("phoneNumber") val phone : String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/LoginRequest.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/LoginRequest.kt new file mode 100644 index 00000000..d890cb60 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/LoginRequest.kt @@ -0,0 +1,6 @@ +package com.project.how.data_class.dto +import com.google.gson.annotations.SerializedName + +data class LoginRequest ( + @SerializedName("uid") val uid : String +) \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpInfo.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpInfo.kt new file mode 100644 index 00000000..3e9f7aaa --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpInfo.kt @@ -0,0 +1,7 @@ +package com.project.how.data_class.dto + +data class SignUpInfo( + val name : String, + val birth : String, + val phone : String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpRequest.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpRequest.kt new file mode 100644 index 00000000..0a76791c --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/data_class/dto/SignUpRequest.kt @@ -0,0 +1,9 @@ +package com.project.how.data_class.dto + +import com.google.gson.annotations.SerializedName + +data class SignUpRequest( + @SerializedName("name") val name : String, + @SerializedName("birth") val birth : String, + @SerializedName("phoneNumber") val phone : String +) diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/datastore/TokenDataStore.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/datastore/TokenDataStore.kt new file mode 100644 index 00000000..71b74980 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/datastore/TokenDataStore.kt @@ -0,0 +1,37 @@ +package com.project.how.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.project.how.data_class.Tokens +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +object TokenDataStore { + val Context.dataStore: DataStore by preferencesDataStore(name = "tokens") + + val ACCESS_TOKEN = stringPreferencesKey("access_token") + val REFRESH_TOKEN = stringPreferencesKey("refresh_token") + + suspend fun saveTokens(context: Context, tokens : Tokens){ + context.dataStore.edit { + it[ACCESS_TOKEN] = tokens.accessToken + it[REFRESH_TOKEN] = tokens.refreshToken + } + } + fun getTokens(context: Context): Flow = flow { + context.dataStore.data.collect { + val accessToken = it[ACCESS_TOKEN] + val refreshToken = it[REFRESH_TOKEN] + if(accessToken.isNullOrEmpty() || refreshToken.isNullOrEmpty()){ + this.emit(null) + }else{ + this.emit(Tokens(accessToken, refreshToken)) + } + } + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/model/CalendarRepository.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/model/CalendarRepository.kt new file mode 100644 index 00000000..4e32fcf6 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/model/CalendarRepository.kt @@ -0,0 +1,76 @@ +package com.project.how.model + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import java.time.LocalDate + +class CalendarRepository { + private val nowDate = LocalDate.now() + private val _selectedDate = MutableLiveData() + val selectedDate : LiveData + get() = _selectedDate + private lateinit var monthOfFirstDate : LocalDate + private lateinit var sd : LocalDate + private var monthOfFirstDayOfWeek = Companion.EMPTY + private var lastDay = 0 + + private fun getMonthInform(fdw : Int) : Flow> = flow { + val monthInfo = mutableListOf() + lastDay = sd.lengthOfMonth() + var week = fdw + for (i in 0 until fdw) + monthInfo.add(Companion.EMPTY) + for(i in 1..lastDay){ + monthInfo.add(week) + week = (week+1) % 7 + } + this.emit(monthInfo) + } + + private fun getMonthOfFirstDayOfWeekChangeInt() : Int { + when(monthOfFirstDate.dayOfWeek.toString()){ + "MONDAY" -> return 1 + "TUESDAY" -> return 2 + "WEDNESDAY" -> return 3 + "THURSDAY" -> return 4 + "FRIDAY" -> return 5 + "SATURDAY" -> return 6 + "SUNDAY" -> return 0 + else -> { + Log.e("getMonthOfFirstDayOfWeek", "monthOfFirstDate.dayOfWeek.toString() go to else case\n${monthOfFirstDate.dayOfWeek}") + return 99} + } + } + + private fun getFirstDate(): Flow> { + monthOfFirstDate = sd.withDayOfMonth(1)!! + monthOfFirstDayOfWeek = getMonthOfFirstDayOfWeekChangeInt() + return getMonthInform(monthOfFirstDayOfWeek) + } + + fun plusSelectedDate(): Flow> { + sd = sd.plusMonths(1) + _selectedDate.postValue(sd) + return getFirstDate() + } + + fun minusSelectedDate(): Flow> { + sd = sd.minusMonths(1) + _selectedDate.postValue(sd) + return getFirstDate() + } + + fun init() : Flow>{ + sd = nowDate + _selectedDate.postValue(nowDate) + return getFirstDate() + } + + companion object { + private const val EMPTY = 99 + } + +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/model/MemberRepository.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/model/MemberRepository.kt new file mode 100644 index 00000000..5e6f12d9 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/model/MemberRepository.kt @@ -0,0 +1,79 @@ +package com.project.how.model + +import android.content.Context +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseUser +import com.google.firebase.auth.GoogleAuthProvider +import com.project.how.data_class.MemberInfo +import com.project.how.data_class.Tokens +import com.project.how.datastore.TokenDataStore + +class MemberRepository { + private val firebaseAuth = FirebaseAuth.getInstance() + private val _currentUserLiveData = MutableLiveData() + private val _userLiveData = MutableLiveData() + private val _tokensLiveData = MutableLiveData() + private val _tokensSaveLiveData = MutableLiveData() + private val _Member_infoLiveData = MutableLiveData() + val currentUserLiveData : LiveData + get() = _currentUserLiveData + val userLiveData: LiveData + get() = _userLiveData + val tokensLiveData : LiveData + get() = _tokensLiveData + val tokensSaveLiveData : LiveData + get() = _tokensSaveLiveData + val memberInfoLiveData : LiveData + get() = _Member_infoLiveData + + fun getUser(idToken: String) { + val credential = GoogleAuthProvider.getCredential(idToken, null) + firebaseAuth.signInWithCredential(credential).addOnCompleteListener{ + if(it.isSuccessful){ + _userLiveData.postValue(firebaseAuth.currentUser) + } else{ + Log.e("GoogleAuthRepository", "getUser is failed\n${it.exception}") + } + } + } + + suspend fun init(context: Context) { + Log.d("init", "LoginRepository init start") + TokenDataStore.getTokens(context).collect{ + Log.d("init", "LoginRepository init getTokens() \tToken is null : ${it == null}") + if (it != null){ + Log.d("init", "LoginRepository init getTokens()\naccessToken : ${it.accessToken}\nrefreshToken : ${it.refreshToken}") + _tokensLiveData.postValue(it) + _tokensSaveLiveData.postValue(true) + }else{ + Log.d("init", "LoginRepository init getTokens() _tokensSaveLiveData.postValue(false)") + _tokensSaveLiveData.postValue(false) + } + } + Log.d("init", "LoginRepository init end") + } + + fun checkCurrentUser() { + val currentUser = firebaseAuth.currentUser + if(currentUser != null){ + Log.d("checkCurrentUser", "firebaseAuth.current != null\nemail : ${currentUser.email}\ndisplayName : ${currentUser.displayName}") + _currentUserLiveData.postValue(currentUser!!) + Log.d("checkCurrentUser", "currentUserLiveData.value : ${currentUserLiveData.value}") + }else{ + Log.d("checkCurrentUser", "firebaseAuth.current == null") + } + } + + suspend fun getTokens(context: Context, accessToken : String, refreshToken: String) { + val tokens = Tokens(accessToken, refreshToken) + _tokensLiveData.postValue(tokens) + TokenDataStore.saveTokens(context, Tokens(accessToken, refreshToken)) + } + + fun getInfo(memberInfo : MemberInfo){ + _Member_infoLiveData.postValue(memberInfo) + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/network/api_interface/MemberService.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/network/api_interface/MemberService.kt new file mode 100644 index 00000000..8d546d8e --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/network/api_interface/MemberService.kt @@ -0,0 +1,37 @@ +package com.project.how.network.api_interface + +import com.project.how.data_class.dto.EmptyResponse +import com.project.how.data_class.dto.LoginRequest +import com.project.how.data_class.dto.SignUpRequest +import com.project.how.data_class.dto.AuthRecreateRequest +import com.project.how.data_class.dto.GetInfoResponse +import retrofit2.Call +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.Header +import retrofit2.http.Headers +import retrofit2.http.POST +import retrofit2.http.PUT + +interface MemberService { + @POST("members/login") + fun login( + @Body login: LoginRequest + ) : Call + + @PUT("members/signup") + fun signUp( + @Header("Authorization") accessToken : String, + @Body signUp: SignUpRequest + ) : Call + + @POST("members/refresh") + fun authRecreate( + @Body authRecreate : AuthRecreateRequest + ) : Call + + @GET("members/info") + fun getInfo( + @Header("Authorization") accessToken: String + ) : Call +} diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/network/client/MemberRetrofit.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/network/client/MemberRetrofit.kt new file mode 100644 index 00000000..475f5f0e --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/network/client/MemberRetrofit.kt @@ -0,0 +1,41 @@ +package com.project.how.network.client + +import com.google.gson.GsonBuilder +import com.project.how.BuildConfig +import com.project.how.network.api_interface.MemberService +import com.project.how.network.converter_factory.NullOnEmptyConverterFactory +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor + +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import retrofit2.converter.scalars.ScalarsConverterFactory +import java.util.concurrent.TimeUnit + +object MemberRetrofit { + private const val BASE_URL = BuildConfig.API_SERVER + + fun getApiService() : MemberService? = getInstance()?.create(MemberService::class.java) + + private fun getInstance() : Retrofit? { + val gson = GsonBuilder().setLenient().create() + + val httpClient = OkHttpClient.Builder() + .connectTimeout(2, TimeUnit.MINUTES) + .readTimeout(1, TimeUnit.MINUTES) + .writeTimeout(1, TimeUnit.MINUTES) + + val loggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.BODY + } + httpClient.addInterceptor(loggingInterceptor) + + return Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(ScalarsConverterFactory.create()) + .addConverterFactory(NullOnEmptyConverterFactory()) + .addConverterFactory(GsonConverterFactory.create(gson)) + .client(httpClient.build()) + .build() + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/network/converter_factory/NullOnEmptyConverterFactory.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/network/converter_factory/NullOnEmptyConverterFactory.kt new file mode 100644 index 00000000..c10c38c7 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/network/converter_factory/NullOnEmptyConverterFactory.kt @@ -0,0 +1,19 @@ +package com.project.how.network.converter_factory + + +import com.project.how.data_class.dto.EmptyResponse +import okhttp3.ResponseBody +import retrofit2.Converter +import retrofit2.Retrofit +import java.lang.reflect.Type + +class NullOnEmptyConverterFactory : Converter.Factory() { + override fun responseBodyConverter(type: Type?, annotations: Array?, retrofit: Retrofit?): Converter? { + val delegate = retrofit!!.nextResponseBodyConverter(this, type!!, annotations!!) + return Converter { + if (it.contentLength() == 0L) return@Converter EmptyResponse() + delegate.convert(it) + } + } + +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/LoginActivity.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/LoginActivity.kt new file mode 100644 index 00000000..27587970 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/LoginActivity.kt @@ -0,0 +1,129 @@ +package com.project.how.view.activity + +import android.app.Activity +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.lifecycleScope +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInClient +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.project.how.BuildConfig +import com.project.how.R +import com.project.how.data_class.dto.LoginRequest +import com.project.how.databinding.ActivityLoginBinding +import com.project.how.view_model.MemberViewModel +import kotlinx.coroutines.launch + +class LoginActivity : AppCompatActivity() { + private lateinit var binding : ActivityLoginBinding + private val viewModel: MemberViewModel by viewModels() + private lateinit var activityResultLauncher : ActivityResultLauncher + private lateinit var googleSignInRequest: GoogleSignInClient + private lateinit var gso : GoogleSignInOptions + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + binding.login = this + binding.lifecycleOwner = this + + gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(BuildConfig.GOOGLE_SERVER_ID) + .requestEmail() + .build() + googleSignInRequest = GoogleSignIn.getClient(this, gso) + activityResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ + if(it.resultCode == Activity.RESULT_OK) { + val task = GoogleSignIn.getSignedInAccountFromIntent(it.data) + try { + val account = task.result + viewModel.getUser(account.idToken!!) + viewModel.userLiveData.observe(this){user-> + Log.d("activityResultLauncher", "Login Success\nidToken : ${account.idToken}\nid : ${account.id}\nemail : ${account.email}\nuid : ${user.uid}") + sendUid(user.uid) + } + } catch (e: Exception){ + Log.e("activityResultLauncher", "Login Failed\nError : ${e.stackTrace}\n${e.message}") + } + } + } + } + + override fun onStart() { + super.onStart() + viewModel.currentUserLiveData.observe(this) { user -> + if (user != null) { + Log.d("onStart", "viewModel.currentUserLiveData.value != null") + autoLogin() + } else { + Log.d("onStart", "viewModel.currentUserLiveData.value == null") + } + } + } + + private fun sendUid(uid : String){ + lifecycleScope.launch { + val loginRequest = LoginRequest(uid) + val code = viewModel.getTokens(this@LoginActivity, loginRequest) + if(code == EXISTING_MEMBER){ + Log.d("sendUid", "Existing member") + moveMain() + }else if(code == NEW_MEMBER){ + Log.d("sendUid", "New member") + moveSignUp() + }else{ + Log.e("sendUid", "Login failed") + Toast.makeText(this@LoginActivity, "[ë¡œê·¸ì¸ ì‹¤íŒ¨] HowAboutTrip 로그ì¸ì— 실패했습니다.\n해당 오류가 지ì†ë  경우 문ì˜í•˜ì‹œê¸°ë¥¼ ë°”ëžë‹ˆë‹¤.", Toast.LENGTH_SHORT).show() + } + } + } + + private fun autoLogin() { + googleSignInRequest.silentSignIn().addOnCompleteListener(this) { task -> + if (task.isSuccessful){ + try { + val account = task.result + viewModel.getUser(account.idToken!!) + viewModel.userLiveData.observe(this){user-> + Log.d("autoLogin", "Auto Login Success\nidToken : ${account.idToken}\nid : ${account.id}\nemail : ${account.email}\nuid : ${user.uid}") + sendUid(user.uid) + } + } catch (e: Exception) { + Log.e("autoLogin", "Auto Login Failed\nError : ${e.stackTrace}\n${e.message}") + } + } else{ + Log.e("autoLogin", "Auto Login Failed\nError : ${task.exception}") + } + } + } + fun login(){ + activityResultLauncher.launch(googleSignInRequest.signInIntent) + binding.lottie.pauseAnimation() + } + + private fun moveSignUp(){ + viewModel.tokensLiveData.observe(this){ + startActivity(Intent(this, SignUpActivity::class.java)) + finish() + } + } + + private fun moveMain(){ + viewModel.tokensLiveData.observe(this){ + startActivity(Intent(this, MainActivity::class.java)) + finish() + } + } + + companion object{ + const val EXISTING_MEMBER = 200 + const val NEW_MEMBER = 201 + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/MainActivity.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/MainActivity.kt new file mode 100644 index 00000000..c2664fbc --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/MainActivity.kt @@ -0,0 +1,46 @@ +package com.project.how.view.activity + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import androidx.databinding.DataBindingUtil +import com.project.how.R +import com.project.how.databinding.ActivityMainBinding +import com.project.how.view.fragment.CalendarFragment +import com.project.how.view.fragment.MypageFragment +import com.project.how.view.fragment.PictureFragment +import com.project.how.view.fragment.TicketFragment + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityMainBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding= DataBindingUtil.setContentView(this, R.layout.activity_main) + binding.main = this + binding.lifecycleOwner = this + + supportFragmentManager.beginTransaction().add(R.id.fragment, CalendarFragment()).commitAllowingStateLoss() + binding.menu.menu.getItem(2).isEnabled = false + binding.menu.selectedItemId = R.id.menu_calendar + binding.menu.setOnItemSelectedListener { + when(it.itemId){ + R.id.menu_ticket->{ + supportFragmentManager.beginTransaction().replace(R.id.fragment, TicketFragment()) + .commitAllowingStateLoss() + } + R.id.menu_calendar->{ + supportFragmentManager.beginTransaction().replace(R.id.fragment, CalendarFragment()) + .commitAllowingStateLoss() + } + R.id.menu_picture->{ + supportFragmentManager.beginTransaction().replace(R.id.fragment, PictureFragment()) + .commitAllowingStateLoss() + } + R.id.menu_mypage->{ + supportFragmentManager.beginTransaction().replace(R.id.fragment, MypageFragment()) + .commitAllowingStateLoss() + } + } + true + } + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SignUpActivity.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SignUpActivity.kt new file mode 100644 index 00000000..d296d9d0 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SignUpActivity.kt @@ -0,0 +1,157 @@ +package com.project.how.view.activity + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.telephony.PhoneNumberFormattingTextWatcher +import android.util.Log +import android.widget.Toast +import androidx.activity.viewModels +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.GridLayoutManager +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.project.how.R +import com.project.how.adapter.CalendarAdapter +import com.project.how.data_class.dto.AuthRecreateRequest +import com.project.how.databinding.ActivitySignUpBinding +import com.project.how.databinding.CalendarBottomSheetBinding +import com.project.how.view_model.CalendarViewModel +import com.project.how.view_model.MemberViewModel +import kotlinx.coroutines.launch +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class SignUpActivity : AppCompatActivity(), CalendarAdapter.OnItemClickListener { + private lateinit var binding : ActivitySignUpBinding + private lateinit var calendarBinding : CalendarBottomSheetBinding + private val calenderViewModel : CalendarViewModel by viewModels() + private val memberViewModel : MemberViewModel by viewModels() + private lateinit var days : List + private lateinit var adapter : CalendarAdapter + private var selectedDay : LocalDate? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_sign_up) + calendarBinding = CalendarBottomSheetBinding.inflate(layoutInflater) + binding.signUp = this + binding.lifecycleOwner = this + + lifecycleScope.launch { + memberViewModel.init(this@SignUpActivity) + } + + binding.phone.addTextChangedListener(PhoneNumberFormattingTextWatcher()) + } + + fun sendInfo(){ + val name = binding.name.text.toString() + val phone = binding.phone.text.toString() + val birth = binding.birth.text.toString() + + Log.d("sendInfo", "IF Before\nname : $name\t${name.length}\nphone : $phone\t${phone.length}\nbirth : $birth\t${birth.length}") + + if(name.isNotEmpty() && phone.length == 13 && birth.length == 8){ + val n = name.trim() + val p = phone + val bDate = LocalDate.parse(birth, DateTimeFormatter.ofPattern("yyyyMMdd")) + val b = bDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + Log.d("sendInfo", "IF After\nname : $n\t${n.length}\nphone : $p\t${p.length}\nbirth : $b\t${b .length}") + memberViewModel.tokensLiveData.value?.let { memberViewModel.getInfoSignUp(this, it.accessToken, n, p, b) } + memberViewModel.memberInfoLiveData.observe(this){ info -> + moveMainActivity() + } + }else{ + Toast.makeText(this, "[필수] 모든 í•­ëª©ì€ í•„ìˆ˜ë¡œ 입력하셔야 합니다.\n모든 í•­ëª©ì´ ì •í•´ì§„ 형ì‹ì— 맞춰서 작성ë˜ì—ˆëŠ”지 확ì¸í•˜ì„¸ìš”.", Toast.LENGTH_SHORT).show() + } + } + + private fun moveMainActivity() { + val intent = Intent(this, MainActivity::class.java) + startActivity(intent) + finish() + } + +// testìš© 코드 +// fun authRecreate(){ +// memberViewModel.tokensLiveData.value?.let { +// memberViewModel.authRecreate(this, AuthRecreateRequest(it.refreshToken)) +// } +// } + +// ìº˜ë¦°ë” í…ŒìŠ¤íŠ¸ìš© 코드 +// fun setCalenderBottomSheetView() { +// initializeCalendarBottomSheet() +// observeCalendarData() +// } +// +// private fun initializeCalendarBottomSheet() { +// calendarBinding = CalendarBottomSheetBinding.inflate(layoutInflater) +// val bottomSheetDialog = BottomSheetDialog(this) +// bottomSheetDialog.setContentView(calendarBinding.root) +// bottomSheetDialog.show() +// +// +// calenderViewModel.selectedDate.observe(this@SignUpActivity) { sd -> +// Log.d("selectedData observe", "initializeCalendarBottom() 시작") +// days = listOf() +// adapter = CalendarAdapter(days, sd, this) +// calendarBinding.days.layoutManager = GridLayoutManager(this, 7) +// calendarBinding.days.adapter = adapter +// } +// +// calendarBinding.left.setOnClickListener{ minusMonth() } +// calendarBinding.right.setOnClickListener { plusMonth() } +// calendarBinding.reset.setOnClickListener { resetDay() } +// calendarBinding.confirm.setOnClickListener { } +// } +// +// private fun observeCalendarData() { +// lifecycleScope.launch { +// calenderViewModel.init().collect { updatedDays -> +// calenderViewModel.selectedDate.observe(this@SignUpActivity) { sd -> +// Log.d("selectedData observe", "observeCalendarData() 시작") +// adapter.update(updatedDays, sd) +// calendarBinding.month.text = sd.month.value.toString() +// calendarBinding.year.text = sd.year.toString() +// } +// } +// } +// } +// +// private fun minusMonth(){ +// lifecycleScope.launch { +// calenderViewModel.minusSelectedDate().collect { updatedDays -> +// calenderViewModel.selectedDate.observe(this@SignUpActivity) { sd -> +// Log.d("selectedData observe", "left.setOnClickListener() 시작") +// adapter.update(updatedDays, sd) +// } +// } +// } +// } +// +// +// private fun resetDay() { +// selectedDay = null +// adapter.resetSelected() +// } +// +// private fun plusMonth(){ +// lifecycleScope.launch { +// calenderViewModel.plusSelectedDate().collect { updatedDays -> +// calenderViewModel.selectedDate.observe(this@SignUpActivity) { sd -> +// Log.d("selectedData observe", "right.setOnClickListener() 시작") +// adapter.update(updatedDays, sd) +// } +// } +// } +// } +// +// override fun onItemClickListener(data: Int, selected: MutableList, position: Int, sd : LocalDate) { +// adapter.resetSelected() +// selected[position] = !selected[position] +// selectedDay = sd +// } + +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SplashActivity.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SplashActivity.kt new file mode 100644 index 00000000..fda55d68 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/activity/SplashActivity.kt @@ -0,0 +1,57 @@ +package com.project.how.view.activity + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.util.Log +import androidx.activity.viewModels +import androidx.databinding.DataBindingUtil +import androidx.lifecycle.lifecycleScope +import com.project.how.R +import com.project.how.databinding.ActivitySplashBinding +import com.project.how.view_model.MemberViewModel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class SplashActivity : AppCompatActivity() { + private lateinit var binding : ActivitySplashBinding + private val memberViewModel: MemberViewModel by viewModels() + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_splash) + binding.splash = this + + lifecycleScope.launch { + memberViewModel.init(this@SplashActivity) + } + + memberViewModel.tokensSaveLiveData.observe(this@SplashActivity){saveCheck-> + Log.d("tokensSaveLiveData observe", "start\nsaveCheck : $saveCheck") + if (saveCheck){ + getMemberInfo() + memberViewModel.memberInfoLiveData.observe(this){ + Log.d("tokenSaveLiveData observe", "memberInfoLiveData\nname : ${it.name}\nphone : ${it.phone}\nbirth : ${it.birth}") + moveMain() + } + }else{ + moveLogin() + } + } + } + + private fun getMemberInfo() { + memberViewModel.tokensLiveData.value?.let { memberViewModel.getInfo(this, it.accessToken) } + } + + private fun moveLogin(){ + val intent = Intent(this@SplashActivity, LoginActivity::class.java) + startActivity(intent) + finish() + } + + private fun moveMain(){ + val intent = Intent(this@SplashActivity, MainActivity::class.java) + startActivity(intent) + finish() + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/CalendarFragment.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/CalendarFragment.kt new file mode 100644 index 00000000..b0182f6c --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/CalendarFragment.kt @@ -0,0 +1,35 @@ +package com.project.how.view.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.project.how.R +import com.project.how.databinding.FragmentCalendarBinding + +class CalendarFragment : Fragment() { + private var _binding : FragmentCalendarBinding? = null + private val binding : FragmentCalendarBinding + get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_calendar, container, false) + binding.calendar = this + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/MypageFragment.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/MypageFragment.kt new file mode 100644 index 00000000..094eb9f4 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/MypageFragment.kt @@ -0,0 +1,36 @@ +package com.project.how.view.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.project.how.R +import com.project.how.databinding.FragmentMypageBinding + +class MypageFragment : Fragment() { + private var _binding : FragmentMypageBinding? = null + private val binding : FragmentMypageBinding + get() = _binding!! + var name = "닉네임 예시" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_mypage, container, false) + binding.mypage = this + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/PictureFragment.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/PictureFragment.kt new file mode 100644 index 00000000..f240796f --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/PictureFragment.kt @@ -0,0 +1,35 @@ +package com.project.how.view.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.project.how.R +import com.project.how.databinding.FragmentPictureBinding + +class PictureFragment : Fragment() { + private var _binding : FragmentPictureBinding? = null + private val binding : FragmentPictureBinding + get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_picture, container, false) + binding.picture = this + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/TicketFragment.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/TicketFragment.kt new file mode 100644 index 00000000..a7d23145 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view/fragment/TicketFragment.kt @@ -0,0 +1,35 @@ +package com.project.how.view.fragment + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.databinding.DataBindingUtil +import com.project.how.R +import com.project.how.databinding.FragmentTicketBinding + +class TicketFragment : Fragment() { + private var _binding : FragmentTicketBinding? = null + private val binding : FragmentTicketBinding + get() = _binding!! + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + _binding = DataBindingUtil.inflate(inflater, R.layout.fragment_ticket, container, false) + binding.ticket = this + binding.lifecycleOwner = this + return binding.root + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/CalendarViewModel.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/CalendarViewModel.kt new file mode 100644 index 00000000..fd367f46 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/CalendarViewModel.kt @@ -0,0 +1,20 @@ +package com.project.how.view_model + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import com.project.how.model.CalendarRepository +import kotlinx.coroutines.flow.Flow +import java.time.LocalDate + +class CalendarViewModel : ViewModel() { + private var calendarRepository : CalendarRepository = CalendarRepository() + private val _selectedDate = calendarRepository.selectedDate + val selectedDate : LiveData + get() = _selectedDate + + fun init() : Flow> = calendarRepository.init() + + fun plusSelectedDate() : Flow> = calendarRepository.plusSelectedDate() + + fun minusSelectedDate() : Flow> = calendarRepository.minusSelectedDate() +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/MemberViewModel.kt b/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/MemberViewModel.kt new file mode 100644 index 00000000..aea4a5aa --- /dev/null +++ b/android/HowAboutTrip/app/src/main/java/com/project/how/view_model/MemberViewModel.kt @@ -0,0 +1,190 @@ +package com.project.how.view_model + +import android.content.Context +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.auth.FirebaseUser +import com.project.how.R +import com.project.how.data_class.MemberInfo +import com.project.how.data_class.dto.LoginRequest +import com.project.how.data_class.Tokens +import com.project.how.data_class.dto.AuthRecreateRequest +import com.project.how.data_class.dto.EmptyResponse +import com.project.how.data_class.dto.GetInfoResponse +import com.project.how.data_class.dto.SignUpRequest +import com.project.how.model.MemberRepository +import com.project.how.network.client.MemberRetrofit +import kotlinx.coroutines.launch +import retrofit2.Call +import retrofit2.Callback +import retrofit2.HttpException +import retrofit2.Response +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException +import kotlin.coroutines.suspendCoroutine + +class MemberViewModel : ViewModel() { + private var memberRepository : MemberRepository = MemberRepository() + private val _currentUserLiveData = memberRepository.currentUserLiveData + private val _userLiveData = memberRepository.userLiveData + private val _tokensLiveData = memberRepository.tokensLiveData + private val _tokensSaveLiveData = memberRepository.tokensSaveLiveData + private val _infoLiveData = memberRepository.memberInfoLiveData + val currentUserLiveData : LiveData + get() = _currentUserLiveData + val userLiveData: LiveData + get() = _userLiveData + val tokensLiveData : LiveData + get() = _tokensLiveData + val tokensSaveLiveData : LiveData + get() = _tokensSaveLiveData + val memberInfoLiveData : LiveData + get() = _infoLiveData + + private var authRecreateCount = 0 + + init { + Log.d("LoginViewModel init", "checkCurrentUser start") + memberRepository.checkCurrentUser() + Log.d("LoginViewModel init", "checkCurrentUser end") + authRecreateCount = 0 + } + + fun getUser(idToken: String) { + memberRepository.getUser(idToken) + } + + suspend fun init(context: Context){ + Log.d("init", "LoginViewModel init start") + memberRepository.init(context) + } + + fun authRecreate(context: Context, arr: AuthRecreateRequest) { + MemberRetrofit.getApiService()!! + .authRecreate(arr) + .enqueue(object : Callback{ + override fun onResponse( + call: Call, + response: Response + ) { + viewModelScope.launch { + val result = response.body().toString() + val accessToken = response.headers()[ACCESS_TOKEN] + val refreshToken = response.headers()[REFRESH_TOKEN] + Log.d( + "authRecreate success", + "code : ${response.code()}\nresult : ${result}\naccessToken : ${accessToken}\nrefreshToken : $refreshToken" + ) + memberRepository.getTokens(context, accessToken!!, refreshToken!!) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("authRecreate onFailure", "${t.message}") + } + + }) + } + + suspend fun getTokens(context : Context, lr: LoginRequest) : Int = suspendCoroutine{ continuation -> + Log.d("getTokens", "loginRequest : ${lr.uid}") + MemberRetrofit.getApiService()!! + .login(lr) + .enqueue(object : Callback{ + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful) { + viewModelScope.launch { + val result = response.body().toString() + val accessToken = response.headers()[ACCESS_TOKEN] + val refreshToken = response.headers()[REFRESH_TOKEN] + Log.d("getTokens success", "code : ${response.code()}\nresult : ${result}\naccessToken : ${accessToken}\nrefreshToken : $refreshToken") + memberRepository.getTokens(context, accessToken!!, refreshToken!!) + continuation.resume(response.code()) + } + } else { + Log.d("getTokens response is not success", "code : ${response.code()}\nError : ${response.code()}") + continuation.resumeWithException(HttpException(response)) + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("getTokens onFailure", "${t.message}") + continuation.resume(ON_FAILURE) + } + }) + } + + fun getInfoSignUp(context : Context, accessToken : String, name : String, birth : String, phone : String){ + val si = SignUpRequest(name, birth, phone) + MemberRetrofit.getApiService()!! + .signUp(context.getString(R.string.bearer_token, accessToken), si) + .enqueue(object : Callback{ + override fun onResponse( + call: Call, + response: Response + ) { + if (response.isSuccessful){ + memberRepository.getInfo(MemberInfo(name, birth, phone)) + Log.d("getInfoSignUp success", "code : ${response.code()}") + }else{ + Log.d("getInfoSignUp response is not success", "code : ${response.code()}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("getInfoSignUp onFailure", "${t.message}") + } + }) + } + + fun getInfo(context: Context, accessToken: String){ + MemberRetrofit.getApiService()!! + .getInfo(context.resources.getString(R.string.bearer_token, accessToken)) + .enqueue(object : Callback{ + override fun onResponse( + call: Call, + response: Response + ) { + if(response.isSuccessful){ + val result = response.body() + if(result != null){ + val name = result.name + val birth = result.birth + val phone = result.phone + memberRepository.getInfo(MemberInfo(name, birth, phone)) + Log.d("getInfo success", "code : ${response.code()}\nname : ${name}\nbirth : ${birth}\nphone : $phone") + }else{ + Log.d("getInfo result is null", "code : ${response.code()}\n message : ${response.message()}") + } + }else if(response.code() == BAD_REQUEST){ + if (authRecreateCount<2){ + tokensLiveData.value + ?.let { AuthRecreateRequest(it.refreshToken) } + ?.let { refreshToken-> + authRecreate(context, refreshToken) + authRecreateCount++} + Log.d("getInfo Bad Request", "code : ${response.code()}\nExecute authRecreate $authRecreateCount") + } + }else{ + Log.d("getInfo response is not success", "code : ${response.code()}") + } + } + + override fun onFailure(call: Call, t: Throwable) { + Log.d("getInfo onFailure", "${t.message}") + } + }) + } + + companion object{ + private const val ON_FAILURE = 99999 + private const val ACCESS_TOKEN = "Access-Token" + private const val REFRESH_TOKEN = "Refresh-Token" + private const val BAD_REQUEST = 400 + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/arrow_left.png b/android/HowAboutTrip/app/src/main/res/drawable/arrow_left.png new file mode 100644 index 00000000..ee9a8bb1 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/arrow_left.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/arrow_right.png b/android/HowAboutTrip/app/src/main/res/drawable/arrow_right.png new file mode 100644 index 00000000..e5c71245 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/arrow_right.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/black_oval_day_background.xml b/android/HowAboutTrip/app/src/main/res/drawable/black_oval_day_background.xml new file mode 100644 index 00000000..01daec93 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/black_oval_day_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/black_oval_fab.xml b/android/HowAboutTrip/app/src/main/res/drawable/black_oval_fab.xml new file mode 100644 index 00000000..9408d3c4 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/black_oval_fab.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/black_round_rectangle.xml b/android/HowAboutTrip/app/src/main/res/drawable/black_round_rectangle.xml new file mode 100644 index 00000000..06d4a793 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/black_round_rectangle.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/google_login.png b/android/HowAboutTrip/app/src/main/res/drawable/google_login.png new file mode 100644 index 00000000..27e88b87 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/google_login.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/gray_round_rectangle.xml b/android/HowAboutTrip/app/src/main/res/drawable/gray_round_rectangle.xml new file mode 100644 index 00000000..76ef59ea --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/gray_round_rectangle.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_background.xml b/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..07d5da9c --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_foreground.xml b/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar.png new file mode 100644 index 00000000..5163f51c Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar_size_fixed.xml b/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar_size_fixed.xml new file mode 100644 index 00000000..bb7c30e1 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/icon_ai_calendar_size_fixed.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_new.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_new.png new file mode 100644 index 00000000..a774821c Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_new.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_none.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_none.png new file mode 100644 index 00000000..805347d6 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_alarm_none.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar.xml b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar.xml new file mode 100644 index 00000000..52667557 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_bold.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_bold.png new file mode 100644 index 00000000..127f5634 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_bold.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_linear.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_linear.png new file mode 100644 index 00000000..d4ef6fdb Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_calendar_linear.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage.xml b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage.xml new file mode 100644 index 00000000..e875b68d --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_bold.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_bold.png new file mode 100644 index 00000000..942837af Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_bold.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_linear.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_linear.png new file mode 100644 index 00000000..d8523603 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_mypage_linear.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_picture.xml b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture.xml new file mode 100644 index 00000000..3f9c43d6 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_bold.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_bold.png new file mode 100644 index 00000000..190b3164 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_bold.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_linear.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_linear.png new file mode 100644 index 00000000..92a3b7c7 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_picture_linear.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket.xml b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket.xml new file mode 100644 index 00000000..80be4f3a --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_bold.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_bold.png new file mode 100644 index 00000000..1e28c77d Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_bold.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_linear.png b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_linear.png new file mode 100644 index 00000000..b105e752 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/icon_ticket_linear.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/logo.png b/android/HowAboutTrip/app/src/main/res/drawable/logo.png new file mode 100644 index 00000000..62d14b26 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/drawable/logo.png differ diff --git a/android/HowAboutTrip/app/src/main/res/drawable/white_oval_day_background.xml b/android/HowAboutTrip/app/src/main/res/drawable/white_oval_day_background.xml new file mode 100644 index 00000000..26913ec2 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/white_oval_day_background.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/white_rectangle_top_shadow.xml b/android/HowAboutTrip/app/src/main/res/drawable/white_rectangle_top_shadow.xml new file mode 100644 index 00000000..eef7a6a7 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/white_rectangle_top_shadow.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/drawable/white_round_bottom_sheet_backgroud.xml b/android/HowAboutTrip/app/src/main/res/drawable/white_round_bottom_sheet_backgroud.xml new file mode 100644 index 00000000..bb7f3008 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/drawable/white_round_bottom_sheet_backgroud.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/font/blinker_semibold.ttf b/android/HowAboutTrip/app/src/main/res/font/blinker_semibold.ttf new file mode 100644 index 00000000..408a9cf0 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/blinker_semibold.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/font/bm_hanna.ttf b/android/HowAboutTrip/app/src/main/res/font/bm_hanna.ttf new file mode 100644 index 00000000..cc8cf496 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/bm_hanna.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_bold.ttf b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_bold.ttf new file mode 100644 index 00000000..6cf639eb Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_bold.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_medium.ttf b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_medium.ttf new file mode 100644 index 00000000..5311c8a3 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_medium.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_regular.ttf b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_regular.ttf new file mode 100644 index 00000000..1b14d324 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_regular.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_semi_bold.ttf b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_semi_bold.ttf new file mode 100644 index 00000000..616bb095 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/font/noto_sans_kr_semi_bold.ttf differ diff --git a/android/HowAboutTrip/app/src/main/res/layout/activity_login.xml b/android/HowAboutTrip/app/src/main/res/layout/activity_login.xml new file mode 100644 index 00000000..038709f2 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/activity_main.xml b/android/HowAboutTrip/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..cc7b6ff2 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/activity_sign_up.xml b/android/HowAboutTrip/app/src/main/res/layout/activity_sign_up.xml new file mode 100644 index 00000000..68eea381 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/activity_sign_up.xml @@ -0,0 +1,174 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/activity_splash.xml b/android/HowAboutTrip/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 00000000..d6b7f38c --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/calendar_bottom_sheet.xml b/android/HowAboutTrip/app/src/main/res/layout/calendar_bottom_sheet.xml new file mode 100644 index 00000000..c21aebda --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/calendar_bottom_sheet.xml @@ -0,0 +1,222 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/calendar_day_item.xml b/android/HowAboutTrip/app/src/main/res/layout/calendar_day_item.xml new file mode 100644 index 00000000..aa09fa0e --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/calendar_day_item.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/fragment_calendar.xml b/android/HowAboutTrip/app/src/main/res/layout/fragment_calendar.xml new file mode 100644 index 00000000..615abe6f --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/fragment_calendar.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/fragment_mypage.xml b/android/HowAboutTrip/app/src/main/res/layout/fragment_mypage.xml new file mode 100644 index 00000000..07702150 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/fragment_mypage.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/fragment_picture.xml b/android/HowAboutTrip/app/src/main/res/layout/fragment_picture.xml new file mode 100644 index 00000000..2fc16171 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/fragment_picture.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/layout/fragment_ticket.xml b/android/HowAboutTrip/app/src/main/res/layout/fragment_ticket.xml new file mode 100644 index 00000000..d40a6e23 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/layout/fragment_ticket.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/menu/main_bottom_navigation_menu.xml b/android/HowAboutTrip/app/src/main/res/menu/main_bottom_navigation_menu.xml new file mode 100644 index 00000000..4148ef88 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/menu/main_bottom_navigation_menu.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher.xml b/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml b/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml new file mode 100644 index 00000000..6f3b755b --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/mipmap-anydpi/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..c209e78e Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..b2dfe3d1 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4f0f1d64 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..62b611da Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..948a3070 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..1b9a6956 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..28d4b77f Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9287f508 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..aa7d6427 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..9126ae37 Binary files /dev/null and b/android/HowAboutTrip/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android/HowAboutTrip/app/src/main/res/raw/login_animation.json b/android/HowAboutTrip/app/src/main/res/raw/login_animation.json new file mode 100644 index 00000000..5e60cb5b --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/raw/login_animation.json @@ -0,0 +1 @@ +{"v":"5.6.9","fr":30,"ip":0,"op":120,"w":1920,"h":1080,"nm":"Scene - 3","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"F_hand_1","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.792],"y":[-0.042]},"o":{"x":[0.622],"y":[0]},"t":0,"s":[-41]},{"i":{"x":[0.379],"y":[1]},"o":{"x":[0.174],"y":[0.325]},"t":12.409,"s":[-31.005]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26.728,"s":[0]},{"i":{"x":[0.797],"y":[-0.791]},"o":{"x":[0.615],"y":[0]},"t":70.136,"s":[0]},{"i":{"x":[0.359],"y":[1]},"o":{"x":[0.17],"y":[0.298]},"t":80.636,"s":[-5.538]},{"t":94,"s":[-41]}],"ix":10},"p":{"a":0,"k":[455.912,193.405,0],"ix":2},"a":{"a":0,"k":[305.733,61.809,0],"ix":1},"s":{"a":0,"k":[520,520,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.628,-2.676],[-5.109,-7.881],[-0.02,2.767]],"o":[[-3.225,1.302],[5.109,7.879],[0.022,-2.768]],"v":[[-8.81,-17.279],[-4.319,12.076],[12.013,15.522]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.944999964097,0.957000014361,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[313.494,77.481],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 101","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"F_hand_2","parent":2,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.769],"y":[0.241]},"o":{"x":[0.461],"y":[0]},"t":0.954,"s":[11]},{"i":{"x":[0.6],"y":[0.822]},"o":{"x":[0.44],"y":[0.298]},"t":8.591,"s":[4.26]},{"i":{"x":[0.484],"y":[1]},"o":{"x":[0.198],"y":[-0.456]},"t":16.228,"s":[-28.468]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27.682,"s":[-19]},{"i":{"x":[0.685],"y":[0.749]},"o":{"x":[0.671],"y":[0]},"t":71.091,"s":[-19]},{"i":{"x":[0.482],"y":[1]},"o":{"x":[0.195],"y":[-0.797]},"t":84.454,"s":[16.441]},{"t":94.9541015625,"s":[11]}],"ix":10},"p":{"a":0,"k":[325.302,93.385,0],"ix":2},"a":{"a":0,"k":[557.672,357.602,0],"ix":1},"s":{"a":0,"k":[19.231,19.231,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.884,0],[0.017,0.002],[-0.01,0.069],[-0.069,-0.014],[-2.946,0.43],[-0.009,-0.068],[0.068,-0.011]],"o":[[-1.588,0],[-0.068,-0.011],[0.01,-0.067],[0.027,0.004],[0.072,-0.01],[0.011,0.068],[-1.068,0.155]],"v":[[-0.131,0.231],[-2.854,0.068],[-2.959,-0.075],[-2.818,-0.179],[2.817,-0.221],[2.958,-0.115],[2.853,0.027]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[301.769,100.383],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 95","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[435.296,394.012],"ix":2},"a":{"a":0,"k":[301.768,100.387],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 95","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.005,0],[0.008,0.063],[-0.069,0.009],[-0.745,0.566],[-0.043,-0.057],[0.054,-0.042],[0.083,-0.01]],"o":[[-0.062,0],[-0.008,-0.069],[0.019,-0.003],[0.055,-0.043],[0.041,0.055],[-0.799,0.607],[-0.005,0.001]],"v":[[-1.389,0.553],[-1.513,0.444],[-1.404,0.304],[1.304,-0.51],[1.48,-0.486],[1.456,-0.311],[-1.374,0.552]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[299.974,99.29],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 96","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[425.94,388.349],"ix":2},"a":{"a":0,"k":[299.969,99.298],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 96","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.039,0],[0.022,0.015],[-0.039,0.056],[-1.767,0.632],[-0.023,-0.066],[0.065,-0.023],[0.015,-0.022]],"o":[[-0.025,0],[-0.056,-0.04],[0.067,-0.095],[0.062,-0.023],[0.024,0.065],[-1.692,0.604],[-0.025,0.035]],"v":[[-1.675,1.589],[-1.747,1.568],[-1.778,1.394],[1.634,-1.566],[1.793,-1.49],[1.718,-1.33],[-1.572,1.536]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[299.991,97.702],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 97","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[426.053,380.093],"ix":2},"a":{"a":0,"k":[299.991,97.71],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 97","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.053,0],[0.013,0.004],[-0.021,0.066],[-1.876,1.155],[-0.035,-0.059],[0.059,-0.036],[0.013,-0.04]],"o":[[-0.013,0],[-0.065,-0.021],[0.055,-0.166],[0.06,-0.036],[0.036,0.058],[-1.798,1.107],[-0.017,0.053]],"v":[[-1.587,2.722],[-1.626,2.717],[-1.707,2.559],[1.52,-2.687],[1.691,-2.645],[1.65,-2.473],[-1.469,2.636]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[299.597,95.759],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 98","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[424,369.991],"ix":2},"a":{"a":0,"k":[299.596,95.768],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 98","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.016,1.674],[-4.814,2.018],[0.805,-1.548],[9.112,5.318]],"o":[[-0.071,-7.786],[4.814,-2.019],[-0.806,1.547],[0,0]],"v":[[-14.231,2.898],[3.784,-9.759],[13.497,-0.812],[-12.848,6.46]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[311.729,94.457],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 100","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[485.38,357.602],"ix":2},"a":{"a":0,"k":[311.4,93.385],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 100","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"F_arm","parent":3,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.792],"y":[3.888]},"o":{"x":[0.622],"y":[0]},"t":1.909,"s":[25]},{"i":{"x":[0.5],"y":[0.756]},"o":{"x":[0.237],"y":[0.142]},"t":14.318,"s":[27.198]},{"i":{"x":[0.574],"y":[1]},"o":{"x":[0.253],"y":[-12.833]},"t":21,"s":[-0.302]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28.637,"s":[0]},{"i":{"x":[0.806],"y":[1.229]},"o":{"x":[0.576],"y":[0]},"t":72.045,"s":[0]},{"i":{"x":[0.502],"y":[0.859]},"o":{"x":[0.273],"y":[0.083]},"t":81.591,"s":[-21.76]},{"i":{"x":[0.538],"y":[1]},"o":{"x":[0.227],"y":[-1.132]},"t":87.318,"s":[29.343]},{"t":95.9091796875,"s":[25]}],"ix":10},"p":{"a":0,"k":[428.089,381.24,0],"ix":2},"a":{"a":0,"k":[428.089,381.24,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.05,0],[0.015,0.006],[-0.026,0.064],[-1.168,0.91],[-0.043,-0.054],[0.055,-0.042],[0.009,-0.022]],"o":[[-0.016,0],[-0.064,-0.025],[0.036,-0.092],[0.053,-0.043],[0.042,0.055],[-1.114,0.868],[-0.019,0.049]],"v":[[-1.013,1.683],[-1.059,1.674],[-1.129,1.512],[0.938,-1.64],[1.113,-1.619],[1.091,-1.443],[-0.897,1.604]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[292.173,103.529],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 102","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[385.402,409.892],"ix":2},"a":{"a":0,"k":[292.173,103.441],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 102","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.052,0],[0.014,0.005],[-0.022,0.065],[-1.577,1.613],[-0.049,-0.049],[0.048,-0.05],[0.006,-0.02]],"o":[[-0.013,0],[-0.065,-0.021],[0.008,-0.023],[0.049,-0.048],[0.049,0.048],[-1.537,1.571],[-0.018,0.053]],"v":[[-1.143,2.01],[-1.184,2.004],[-1.263,1.847],[1.057,-1.96],[1.234,-1.961],[1.236,-1.784],[-1.025,1.924]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[291.396,103.154],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 103","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[381.365,408.437],"ix":2},"a":{"a":0,"k":[291.397,103.161],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 103","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.048,0],[0.016,0.008],[-0.027,0.064],[-0.04,0.043],[-0.05,-0.048],[0.047,-0.051],[0.134,-0.307]],"o":[[-0.017,0],[-0.063,-0.027],[0.179,-0.409],[0.048,-0.047],[0.05,0.047],[-0.266,0.283],[-0.021,0.047]],"v":[[-0.569,0.846],[-0.619,0.836],[-0.684,0.672],[0.483,-0.793],[0.66,-0.798],[0.665,-0.621],[-0.454,0.772]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[291.098,101.531],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 104","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[379.817,400.001],"ix":2},"a":{"a":0,"k":[291.099,101.539],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 104","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.269,0.469],[-0.19,0.456],[-0.002,0.006],[-0.212,0.482],[-0.967,0.637],[-0.907,0.207],[-0.721,0.328],[-2.418,1.528],[0.264,0.192],[1.256,0.918],[0.168,-0.111],[0.061,-0.063],[3.018,-1.062],[1.365,-1.851],[-0.383,0.519],[-0.914,0.54],[-0.009,-0.029],[0.505,-1.412]],"o":[[0.147,0.361],[0.002,-0.004],[0.086,0.413],[0.439,-0.996],[0.804,-0.529],[0.777,-0.177],[2.605,-1.186],[0.255,-0.163],[-1.257,-0.919],[-0.205,-0.15],[-0.069,0.024],[-2.263,2.282],[-2.151,0.758],[-0.38,0.514],[0.756,-1.024],[0.006,0.03],[-1.028,1.08],[-0.202,0.567]],"v":[[-7.156,5.588],[-6.256,5.65],[-6.248,5.635],[-5.325,5.779],[-3.476,3.073],[-0.667,2.285],[1.507,1.56],[9.079,-2.562],[9.135,-3.356],[5.365,-6.111],[4.767,-6.119],[4.57,-5.994],[-3.844,-0.843],[-9.019,2.867],[-8.268,3.527],[-5.75,1.284],[-5.737,1.372],[-8.005,5.066]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999982834,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 4')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[297.408,99.452],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 105","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[413.089,388.74],"ix":2},"a":{"a":0,"k":[297.498,99.373],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 105","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Backpack_front","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-3.812,"ix":10},"p":{"a":0,"k":[495.657,240.788,0],"ix":2},"a":{"a":0,"k":[137.842,71.746,0],"ix":1},"s":{"a":0,"k":[-515,515,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.739,4.767],[-1.363,-1.371],[4.375,-4.377],[5.244,-1.252],[-1.88,0.448],[-4.014,6.689]],"o":[[-1.363,-1.371],[4.87,4.899],[-3.911,3.913],[-1.877,0.449],[7.233,-1.727],[3.318,-5.529]],"v":[[7.841,-13.998],[5.719,-11.877],[3.943,5.092],[-10.703,12.027],[-9.906,14.92],[8.977,3.52]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.246999987434,0.258999992819,0.380000005984,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[138.01,71.483],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 72","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":3,"nm":"NULL CONTROL","sr":1,"ks":{"o":{"a":0,"k":0,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[369.84,456.109,0],"ix":2},"a":{"a":0,"k":[60,60,0],"ix":1},"s":{"a":0,"k":[95,95,100],"ix":6}},"ao":0,"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Body","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.792],"y":[1.308]},"o":{"x":[0.622],"y":[0]},"t":0,"s":[-8]},{"i":{"x":[0.379],"y":[1]},"o":{"x":[0.174],"y":[0.072]},"t":12,"s":[-13.903]},{"i":{"x":[0.47],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26.728,"s":[12]},{"i":{"x":[0.3],"y":[1]},"o":{"x":[0.526],"y":[0]},"t":71.091,"s":[12]},{"t":94.9541015625,"s":[-8]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[68.691,-56.785,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.468,"y":0.468},"o":{"x":0.47,"y":0.47},"t":27,"s":[121.691,-56.785,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[121.691,-56.785,0],"to":[0,0,0],"ti":[0,0,0]},{"t":95,"s":[68.691,-56.785,0]}],"ix":2},"a":{"a":0,"k":[449.149,359.824,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[10.296,6.959],[0.731,-11.342],[0,0],[-17.135,4.666]],"o":[[0,0],[-0.732,11.343],[0,0],[0,0]],"v":[[-2.682,-21.688],[-6.98,1.607],[-18.142,16.305],[18.142,17.022]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.141000002623,0.513999998569,0.885999977589,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[307.366,81.891],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 106","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[464.405,290.649],"ix":2},"a":{"a":0,"k":[307.366,80.509],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 106","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-17.355,5.01],[9.024,2.914],[2.236,-0.507],[0.334,-0.161],[0.326,-3.88],[1.074,-3.578],[0,0]],"o":[[0,0],[-2.752,-0.889],[-0.327,0.074],[-2.682,1.292],[-0.592,7.043],[-1.074,3.579],[0,0]],"v":[[20.691,19.263],[0.578,-21.892],[-7.03,-22.464],[-9.028,-21.774],[-13.857,-14.014],[-16.344,7.098],[-20.691,17.296]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 8')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[304.817,79.649],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 117","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[451.149,281.842],"ix":2},"a":{"a":0,"k":[304.817,78.816],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 117","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Legs","parent":6,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[51.309,176.785,0],"ix":2},"a":{"a":0,"k":[466.532,593.394,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0.015,0],[0.022,0.08],[-0.096,0.026],[-5,5.777],[-0.075,-0.063],[0.065,-0.075],[0.139,-0.038]],"o":[[-0.079,0],[-0.026,-0.096],[0.139,-0.039],[0.065,-0.075],[0.075,0.066],[-5.071,5.861],[-0.016,0.004]],"v":[[-9.505,5.042],[-9.679,4.909],[-9.553,4.688],[9.368,-4.96],[9.622,-4.979],[9.64,-4.724],[-9.457,5.035]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0.012,-0.01],[0.068,0.048],[-0.057,0.081],[-0.166,7.638],[-0.098,-0.001],[0.002,-0.099],[0.083,-0.118]],"o":[[-0.061,0.05],[-0.081,-0.057],[0.082,-0.119],[0.002,-0.099],[0.1,0.003],[-0.167,7.748],[-0.01,0.013]],"v":[[-1.903,2.374],[-2.122,2.382],[-2.166,2.132],[6.252,-17.368],[6.436,-17.544],[6.612,-17.359],[-1.87,2.338]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0.012,-0.01],[0.068,0.048],[-0.057,0.081],[-0.166,7.638],[-0.098,-0.001],[0.002,-0.099],[0.083,-0.118]],"o":[[-0.061,0.05],[-0.081,-0.057],[0.082,-0.119],[0.002,-0.099],[0.1,0.003],[-0.167,7.748],[-0.01,0.013]],"v":[[-1.903,2.374],[-2.122,2.382],[-2.166,2.132],[6.252,-17.368],[6.436,-17.544],[6.612,-17.359],[-1.87,2.338]],"c":true}]},{"t":95,"s":[{"i":[[0.015,0],[0.022,0.08],[-0.096,0.026],[-5,5.777],[-0.075,-0.063],[0.065,-0.075],[0.139,-0.038]],"o":[[-0.079,0],[-0.026,-0.096],[0.139,-0.039],[0.065,-0.075],[0.075,0.066],[-5.071,5.861],[-0.016,0.004]],"v":[[-9.505,5.042],[-9.679,4.909],[-9.553,4.688],[9.368,-4.96],[9.622,-4.979],[9.64,-4.724],[-9.457,5.035]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[321.386,159.659],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 87","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[537.304,702.28],"ix":2},"a":{"a":0,"k":[321.385,159.669],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 87","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0.012,0],[0.017,0.085],[-0.097,0.02],[-7.889,5.404],[-0.057,-0.084],[0.082,-0.055],[0.122,-0.025]],"o":[[-0.084,0],[-0.021,-0.097],[0.121,-0.026],[0.082,-0.057],[0.056,0.082],[-7.949,5.447],[-0.013,0.002]],"v":[[-10.092,4.205],[-10.268,4.062],[-10.129,3.848],[9.982,-4.148],[10.233,-4.102],[10.186,-3.852],[-10.055,4.201]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0.009,-0.008],[0.067,0.055],[-0.062,0.077],[-2.629,9.194],[-0.098,-0.028],[0.027,-0.095],[0.078,-0.097]],"o":[[-0.065,0.054],[-0.078,-0.061],[0.077,-0.097],[0.027,-0.096],[0.095,0.027],[-2.648,9.265],[-0.009,0.01]],"v":[[0.125,-1.385],[-0.102,-1.384],[-0.131,-1.636],[10.257,-20.623],[10.479,-20.748],[10.602,-20.525],[0.151,-1.412]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0.009,-0.008],[0.067,0.055],[-0.062,0.077],[-2.629,9.194],[-0.098,-0.028],[0.027,-0.095],[0.078,-0.097]],"o":[[-0.065,0.054],[-0.078,-0.061],[0.077,-0.097],[0.027,-0.096],[0.095,0.027],[-2.648,9.265],[-0.009,0.01]],"v":[[0.125,-1.385],[-0.102,-1.384],[-0.131,-1.636],[10.257,-20.623],[10.479,-20.748],[10.602,-20.525],[0.151,-1.412]],"c":true}]},{"t":95,"s":[{"i":[[0.012,0],[0.017,0.085],[-0.097,0.02],[-7.889,5.404],[-0.057,-0.084],[0.082,-0.055],[0.122,-0.025]],"o":[[-0.084,0],[-0.021,-0.097],[0.121,-0.026],[0.082,-0.057],[0.056,0.082],[-7.949,5.447],[-0.013,0.002]],"v":[[-10.092,4.205],[-10.268,4.062],[-10.129,3.848],[9.982,-4.148],[10.233,-4.102],[10.186,-3.852],[-10.055,4.201]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[324.721,165.585],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 88","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[554.628,733.107],"ix":2},"a":{"a":0,"k":[324.717,165.598],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 88","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0.047,0],[0.035,0.034],[3.909,0.39],[0.035,-0.004],[0.01,0.098],[-0.098,0.01],[-4.287,-0.427],[-3.445,-3.375],[0.069,-0.071]],"o":[[-0.045,0],[-3.37,-3.3],[-4.25,-0.425],[-0.106,-0.004],[-0.011,-0.1],[0.036,-0.003],[3.969,0.397],[0.071,0.07],[-0.036,0.036]],"v":[[10.281,2.844],[10.155,2.793],[-2.49,-2.056],[-10.271,-2.112],[-10.469,-2.272],[-10.31,-2.471],[-2.461,-2.417],[10.407,2.535],[10.41,2.79]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0.047,-0.004],[0.037,0.031],[3.928,0.075],[0.035,-0.007],[0.018,0.097],[-0.097,0.018],[-4.307,-0.082],[-3.706,-3.086],[0.063,-0.076]],"o":[[-0.045,0.004],[-3.625,-3.018],[-4.27,-0.081],[-0.106,0.005],[-0.019,-0.099],[0.036,-0.006],[3.988,0.076],[0.076,0.064],[-0.033,0.039]],"v":[[10.035,3.839],[9.905,3.797],[-3.089,-0.017],[-10.85,0.554],[-11.06,0.411],[-10.917,0.199],[-3.09,-0.379],[10.135,3.52],[10.159,3.774]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0.047,-0.004],[0.037,0.031],[3.928,0.075],[0.035,-0.007],[0.018,0.097],[-0.097,0.018],[-4.307,-0.082],[-3.706,-3.086],[0.063,-0.076]],"o":[[-0.045,0.004],[-3.625,-3.018],[-4.27,-0.081],[-0.106,0.005],[-0.019,-0.099],[0.036,-0.006],[3.988,0.076],[0.076,0.064],[-0.033,0.039]],"v":[[10.035,3.839],[9.905,3.797],[-3.089,-0.017],[-10.85,0.554],[-11.06,0.411],[-10.917,0.199],[-3.09,-0.379],[10.135,3.52],[10.159,3.774]],"c":true}]},{"t":95,"s":[{"i":[[0.047,0],[0.035,0.034],[3.909,0.39],[0.035,-0.004],[0.01,0.098],[-0.098,0.01],[-4.287,-0.427],[-3.445,-3.375],[0.069,-0.071]],"o":[[-0.045,0],[-3.37,-3.3],[-4.25,-0.425],[-0.106,-0.004],[-0.011,-0.1],[0.036,-0.003],[3.969,0.397],[0.071,0.07],[-0.036,0.036]],"v":[[10.281,2.844],[10.155,2.793],[-2.49,-2.056],[-10.271,-2.112],[-10.469,-2.272],[-10.31,-2.471],[-2.461,-2.417],[10.407,2.535],[10.41,2.79]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[293.104,162.888],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 89","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[390.22,719.592],"ix":2},"a":{"a":0,"k":[293.1,162.998],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 89","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0.046,0],[0.035,0.035],[0.124,-0.024],[0.018,0.097],[-0.097,0.018],[-8.167,-8.211],[0.071,-0.07]],"o":[[-0.046,0],[-8.03,-8.072],[-0.097,0.023],[-0.019,-0.098],[0.127,-0.024],[0.07,0.071],[-0.035,0.035]],"v":[[10.374,4.259],[10.245,4.206],[-10.342,-1.548],[-10.553,-1.69],[-10.411,-1.901],[10.501,3.952],[10.5,4.207]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0.046,-0.004],[0.038,0.032],[0.122,-0.034],[0.026,0.095],[-0.095,0.026],[-8.802,-7.526],[0.065,-0.076]],"o":[[-0.046,0.004],[-8.654,-7.399],[-0.095,0.031],[-0.027,-0.096],[0.125,-0.034],[0.076,0.065],[-0.032,0.038]],"v":[[10.246,5.304],[10.114,5.26],[-10.87,1.184],[-11.092,1.059],[-10.967,0.837],[10.349,4.986],[10.368,5.241]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0.046,-0.004],[0.038,0.032],[0.122,-0.034],[0.026,0.095],[-0.095,0.026],[-8.802,-7.526],[0.065,-0.076]],"o":[[-0.046,0.004],[-8.654,-7.399],[-0.095,0.031],[-0.027,-0.096],[0.125,-0.034],[0.076,0.065],[-0.032,0.038]],"v":[[10.246,5.304],[10.114,5.26],[-10.87,1.184],[-11.092,1.059],[-10.967,0.837],[10.349,4.986],[10.368,5.241]],"c":true}]},{"t":95,"s":[{"i":[[0.046,0],[0.035,0.035],[0.124,-0.024],[0.018,0.097],[-0.097,0.018],[-8.167,-8.211],[0.071,-0.07]],"o":[[-0.046,0],[-8.03,-8.072],[-0.097,0.023],[-0.019,-0.098],[0.127,-0.024],[0.07,0.071],[-0.035,0.035]],"v":[[10.374,4.259],[10.245,4.206],[-10.342,-1.548],[-10.553,-1.69],[-10.411,-1.901],[10.501,3.952],[10.5,4.207]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[291.833,167.612],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 90","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[383.625,748.872],"ix":2},"a":{"a":0,"k":[291.832,168.629],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 90","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.887,-3.219],[-1.75,2.278]],"o":[[-1.098,1.873],[1.124,-1.461]],"v":[[-8.123,-1.565],[8.097,2.506]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.231000006199,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 9')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[282.892,186.975],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 118","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[337.08,845.875],"ix":2},"a":{"a":0,"k":[282.881,187.284],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 118","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.034,-0.619],[0,0],[-1.195,1.13],[-0.019,0.325],[0,0],[0,0]],"o":[[0,0],[0,0],[1.123,-1.057],[0.012,-0.018],[-2.443,-2.776],[-2.009,1.434]],"v":[[-8.228,-1.986],[-2.984,0.682],[6.845,2.82],[8.216,-1.143],[8.224,-1.174],[-0.215,-3.663]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[284.142,186.668],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 119","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[343.631,841.075],"ix":2},"a":{"a":0,"k":[284.141,186.361],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 119","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.882,2.313],[-0.32,1.809],[0,0],[-0.533,2.348],[1.604,0.16],[0.761,-0.779],[0.154,-0.106],[2.034,-0.619],[0.19,-0.347]],"o":[[0.775,-0.969],[0.011,-0.017],[0.244,-1.406],[-3.589,-1.394],[-0.568,1.819],[-0.12,0.128],[-2.008,1.433],[-0.554,0.147],[-0.989,1.76]],"v":[[6.866,5.905],[8.238,1.944],[8.246,1.913],[10.345,-6.549],[3.12,-8.219],[0.215,-0.926],[-0.194,-0.577],[-8.207,1.1],[-9.356,1.827]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 10')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[284.121,183.582],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 120","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[345.982,822.258],"ix":2},"a":{"a":0,"k":[284.593,182.742],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 120","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.043,-1.212],[-1.087,2.568],[5.741,1.431]],"o":[[0.09,2.568],[0.792,-1.874],[-6.884,-1.716]],"v":[[-10.285,-2.369],[9.536,2.86],[1.108,-3.712]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.090000003576,0.090000003576,0.11400000006,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 11')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[290.299,179.732],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 121","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[373.997,804.376],"ix":2},"a":{"a":0,"k":[289.98,179.303],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 121","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[-5.944,-7.796],[-2.14,7.763],[-2.483,6.991],[-2.061,7.547],[2.74,5.953],[0.304,-0.119],[4.908,-5.236],[0.72,-3.567],[1.114,-5.026],[3.45,-9.558],[0.177,-9.283]],"o":[[0.249,0.549],[1.973,-7.162],[2.619,-7.373],[1.671,-6.116],[-0.176,-0.385],[-5.03,-4.089],[-2.608,2.781],[-1.019,5.046],[-2.204,9.935],[-3.126,8.66],[4.158,-0.972]],"v":[[-1.566,45.583],[3.806,21.418],[11.699,0.588],[18.684,-21.863],[18.648,-40.26],[17.806,-40.585],[0.308,-40.896],[-3.416,-30.497],[-6.536,-15.372],[-15.288,13.626],[-21.388,40.355]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[-5.944,-7.796],[-0.777,8.015],[-1.249,7.313],[-0.739,7.788],[3.719,5.396],[0.279,-0.169],[3.94,-5.998],[0.099,-3.638],[0.238,-5.143],[1.762,-10.008],[0.177,-9.283]],"o":[[0.249,0.549],[0.717,-7.394],[1.318,-7.713],[0.599,-6.312],[-0.239,-0.349],[-5.656,-3.167],[-2.093,3.187],[-0.14,5.146],[-0.47,10.166],[-1.597,9.067],[4.158,-0.972]],"v":[[-1.566,45.583],[2.922,22.486],[7.131,0.612],[10.167,-22.704],[6.981,-40.823],[6.096,-40.999],[-11.197,-38.308],[-13.085,-27.425],[-13.568,-11.989],[-17.225,18.079],[-21.388,40.355]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[-5.944,-7.796],[-0.777,8.015],[-1.249,7.313],[-0.739,7.788],[3.719,5.396],[0.279,-0.169],[3.94,-5.998],[0.099,-3.638],[0.238,-5.143],[1.762,-10.008],[0.177,-9.283]],"o":[[0.249,0.549],[0.717,-7.394],[1.318,-7.713],[0.599,-6.312],[-0.239,-0.349],[-5.656,-3.167],[-2.093,3.187],[-0.14,5.146],[-0.47,10.166],[-1.597,9.067],[4.158,-0.972]],"v":[[-1.566,45.583],[2.922,22.486],[7.131,0.612],[10.167,-22.704],[6.981,-40.823],[6.096,-40.999],[-11.197,-38.308],[-13.085,-27.425],[-13.568,-11.989],[-17.225,18.079],[-21.388,40.355]],"c":true}]},{"t":95,"s":[{"i":[[-5.944,-7.796],[-2.14,7.763],[-2.483,6.991],[-2.061,7.547],[2.74,5.953],[0.304,-0.119],[4.908,-5.236],[0.72,-3.567],[1.114,-5.026],[3.45,-9.558],[0.177,-9.283]],"o":[[0.249,0.549],[1.973,-7.162],[2.619,-7.373],[1.671,-6.116],[-0.176,-0.385],[-5.03,-4.089],[-2.608,2.781],[-1.019,5.046],[-2.204,9.935],[-3.126,8.66],[4.158,-0.972]],"v":[[-1.566,45.583],[3.806,21.418],[11.699,0.588],[18.684,-21.863],[18.648,-40.26],[17.806,-40.585],[0.308,-40.896],[-3.416,-30.497],[-6.536,-15.372],[-15.288,13.626],[-21.388,40.355]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[301.401,137.007],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 122","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[430.663,587.889],"ix":2},"a":{"a":0,"k":[300.878,137.671],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 122","np":1,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0],[-2.23,6]],"o":[[0,0],[0,0],[0,0],[2.23,-6]],"v":[[5.77,-16.969],[-5.77,10.189],[-3.042,16.969],[0,4.355]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0,0],[0,0],[0,0],[-1.169,6.293]],"o":[[0,0],[0,0],[0,0],[1.169,-6.293]],"v":[[-1.255,-17.518],[-7.973,11.215],[-2.125,14.543],[-3.287,4.479]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0,0],[0,0],[0,0],[-1.169,6.293]],"o":[[0,0],[0,0],[0,0],[1.169,-6.293]],"v":[[-1.255,-17.518],[-7.973,11.215],[-2.125,14.543],[-3.287,4.479]],"c":true}]},{"t":95,"s":[{"i":[[0,0],[0,0],[0,0],[-2.23,6]],"o":[[0,0],[0,0],[0,0],[2.23,-6]],"v":[[5.77,-16.969],[-5.77,10.189],[-3.042,16.969],[0,4.355]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.231000006199,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 9')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[311.881,140.614],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 123","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[487.882,603.195],"ix":2},"a":{"a":0,"k":[311.881,140.614],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 123","np":1,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.479,-16.634],[-3.479,8.643],[-1.962,16.634]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-6.773,-13.622],[-9.299,12.473],[-6.436,20.086]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-6.773,-13.622],[-9.299,12.473],[-6.436,20.086]],"c":true}]},{"t":95,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[3.479,-16.634],[-3.479,8.643],[-1.962,16.634]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.231000006199,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 9')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[295.149,119.795],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 124","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[400.876,494.934],"ix":2},"a":{"a":0,"k":[295.149,119.795],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 124","np":1,"cix":2,"bm":0,"ix":11,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0,0],[0.25,-10.125],[0,0],[0.82,0.981],[2.7,5.924],[0,0]],"o":[[0,0],[-0.25,10.125],[0,0],[-0.821,-0.981],[-2.7,-5.926],[0,0]],"v":[[1.212,-29.646],[0.474,-0.012],[9.401,28.167],[5.063,28.665],[-3.63,15.08],[-9.401,1.363]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0,0],[-6.966,-7.352],[0,0],[1.235,0.332],[5.858,2.841],[0,0]],"o":[[0,0],[6.966,7.352],[0,0],[-1.235,-0.332],[-5.859,-2.842],[0,0]],"v":[[-6.459,-29.979],[0.246,-1.171],[21.891,15.014],[18.895,18.122],[3.857,12.453],[-11.603,2.389]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0,0],[-6.966,-7.352],[0,0],[1.235,0.332],[5.858,2.841],[0,0]],"o":[[0,0],[6.966,7.352],[0,0],[-1.235,-0.332],[-5.859,-2.842],[0,0]],"v":[[-6.459,-29.979],[0.246,-1.171],[21.891,15.014],[18.895,18.122],[3.857,12.453],[-11.603,2.389]],"c":true}]},{"t":95,"s":[{"i":[[0,0],[0.25,-10.125],[0,0],[0.82,0.981],[2.7,5.924],[0,0]],"o":[[0,0],[-0.25,10.125],[0,0],[-0.821,-0.981],[-2.7,-5.926],[0,0]],"v":[[1.212,-29.646],[0.474,-0.012],[9.401,28.167],[5.063,28.665],[-3.63,15.08],[-9.401,1.363]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203999996185,0.216000005603,0.317999988794,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 12')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[315.512,149.441],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 125","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[506.762,647.415],"ix":2},"a":{"a":0,"k":[315.512,149.118],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 125","np":1,"cix":2,"bm":0,"ix":12,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[3.693,-1.903],[-0.043,-0.071],[0.987,2.706],[0,0],[0.071,0.157],[1.86,-2.032]],"o":[[0.014,0.086],[1.03,1.903],[0,0],[-0.028,-0.157],[-0.344,0.974],[-2.405,2.634]],"v":[[-6.455,4.967],[-6.369,5.182],[5.468,-6.598],[5.468,-6.612],[5.311,-7.085],[2.276,-2.219]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[1.933,-3.677],[-0.074,-0.037],[2.361,1.65],[0,0],[0.148,0.088],[0.354,-2.732]],"o":[[0.061,0.062],[1.935,0.968],[0,0],[-0.113,-0.113],[0.277,0.995],[-0.459,3.537]],"v":[[12.508,-12.418],[12.702,-12.291],[15.638,-28.731],[15.63,-28.742],[15.23,-29.04],[15.536,-23.313]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[1.933,-3.677],[-0.074,-0.037],[2.361,1.65],[0,0],[0.148,0.088],[0.354,-2.732]],"o":[[0.061,0.062],[1.935,0.968],[0,0],[-0.113,-0.113],[0.277,0.995],[-0.459,3.537]],"v":[[12.508,-12.418],[12.702,-12.291],[15.638,-28.731],[15.63,-28.742],[15.23,-29.04],[15.536,-23.313]],"c":true}]},{"t":95,"s":[{"i":[[3.693,-1.903],[-0.043,-0.071],[0.987,2.706],[0,0],[0.071,0.157],[1.86,-2.032]],"o":[[0.014,0.086],[1.03,1.903],[0,0],[-0.028,-0.157],[-0.344,0.974],[-2.405,2.634]],"v":[[-6.455,4.967],[-6.369,5.182],[5.468,-6.598],[5.468,-6.612],[5.311,-7.085],[2.276,-2.219]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.231000006199,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 9')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[335.31,184.693],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 126","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[607.299,828.029],"ix":2},"a":{"a":0,"k":[334.846,183.852],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 126","np":1,"cix":2,"bm":0,"ix":13,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[-6.143,6.879],[-0.309,-0.619],[-2.956,-7.245],[-3.297,-6.657],[0.551,0.244],[1.778,1.856],[0.608,1.513],[4.972,8.878],[0.025,0.046]],"o":[[0.326,0.609],[3.488,7.004],[2.803,6.869],[3.574,7.216],[0.9,2.638],[-4.978,-5.795],[-3.79,-9.427],[-0.025,-0.045],[3.077,-8.6]],"v":[[-9.022,-38.4],[-8.068,-36.558],[1.671,-15.157],[9.653,5.639],[21.739,27.245],[3.47,36.544],[-9.779,12.54],[-22.561,-14.921],[-22.638,-15.056]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[-4.874,7.829],[-0.41,-0.557],[-3.389,-7.053],[-3.026,-1.631],[0.58,-0.164],[2.521,0.5],[3.287,2.8],[6.419,7.895],[0.033,0.041]],"o":[[0.425,0.544],[4.636,6.303],[3.278,6.821],[5.508,2.968],[2.376,1.458],[-7.125,-1.82],[-7.735,-6.589],[-0.032,-0.04],[1.559,-9]],"v":[[-19.414,-37.007],[-18.159,-35.356],[-5.708,-15.838],[3.399,-2.47],[25.762,6.435],[17.302,26.001],[-10.98,11.488],[-28.732,-11.556],[-28.831,-11.676]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[-4.874,7.829],[-0.41,-0.557],[-3.389,-7.053],[-3.026,-1.631],[0.58,-0.164],[2.521,0.5],[3.287,2.8],[6.419,7.895],[0.033,0.041]],"o":[[0.425,0.544],[4.636,6.303],[3.278,6.821],[5.508,2.968],[2.376,1.458],[-7.125,-1.82],[-7.735,-6.589],[-0.032,-0.04],[1.559,-9]],"v":[[-19.414,-37.007],[-18.159,-35.356],[-5.708,-15.838],[3.399,-2.47],[25.762,6.435],[17.302,26.001],[-10.98,11.488],[-28.732,-11.556],[-28.831,-11.676]],"c":true}]},{"t":95,"s":[{"i":[[-6.143,6.879],[-0.309,-0.619],[-2.956,-7.245],[-3.297,-6.657],[0.551,0.244],[1.778,1.856],[0.608,1.513],[4.972,8.878],[0.025,0.046]],"o":[[0.326,0.609],[3.488,7.004],[2.803,6.869],[3.574,7.216],[0.9,2.638],[-4.978,-5.795],[-3.79,-9.427],[-0.025,-0.045],[3.077,-8.6]],"v":[[-9.022,-38.4],[-8.068,-36.558],[1.671,-15.157],[9.653,5.639],[21.739,27.245],[3.47,36.544],[-9.779,12.54],[-22.561,-14.921],[-22.638,-15.056]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[317.105,141.561],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 127","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[512.792,603.973],"ix":2},"a":{"a":0,"k":[316.672,140.764],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 127","np":1,"cix":2,"bm":0,"ix":14,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[0,0],[-0.213,-4.239],[0,0],[0,0],[0,0]],"o":[[0,0],[0.212,4.237],[0,0],[0,0],[0,0]],"v":[[-2.134,-19.336],[-6.811,6.807],[-4.216,19.336],[4.693,8.461],[7.024,-17.72]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[0,0],[-0.935,-4.14],[0,0],[0,0],[0,0]],"o":[[0,0],[0.934,4.138],[0,0],[0,0],[0,0]],"v":[[-13.267,-15.735],[-13.398,10.822],[-8.696,22.721],[-1.781,10.481],[-3.968,-15.713]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[0,0],[-0.935,-4.14],[0,0],[0,0],[0,0]],"o":[[0,0],[0.934,4.138],[0,0],[0,0],[0,0]],"v":[[-13.267,-15.735],[-13.398,10.822],[-8.696,22.721],[-1.781,10.481],[-3.968,-15.713]],"c":true}]},{"t":95,"s":[{"i":[[0,0],[-0.213,-4.239],[0,0],[0,0],[0,0]],"o":[[0,0],[0.212,4.237],[0,0],[0,0],[0,0]],"v":[[-2.134,-19.336],[-6.811,6.807],[-4.216,19.336],[4.693,8.461],[7.024,-17.72]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.203999996185,0.216000005603,0.317999988794,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 12')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[297.793,117.093],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 128","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[415.159,480.884],"ix":2},"a":{"a":0,"k":[297.896,117.093],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 128","np":1,"cix":2,"bm":0,"ix":15,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[-1.476,-10.707],[-8.069,-4.967],[-0.224,0.218],[-1.782,6.718],[-0.586,7.834],[4.88,3.871],[5.115,-2.249],[0.244,-0.63],[0.07,-0.185],[-0.06,-0.228]],"o":[[1.181,8.568],[0.272,0.167],[5.041,-4.901],[2.013,-7.586],[0.443,-5.911],[-4.826,-3.828],[-0.275,-0.414],[-0.072,0.185],[-0.225,0.164],[-3.683,10.2]],"v":[[-12.392,5.897],[-1.614,30.842],[-0.746,30.73],[8.936,12.75],[13.425,-10.931],[7.662,-27.181],[-7.93,-27.271],[-9.27,-27.097],[-9.475,-26.539],[-9.704,-25.912]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[-3.288,-10.296],[-8.8,-3.512],[-0.183,0.253],[-0.606,6.924],[0.763,7.819],[5.471,2.978],[4.654,-3.092],[0.133,-0.662],[0.037,-0.194],[-0.098,-0.214]],"o":[[2.631,8.239],[0.297,0.118],[4.127,-5.692],[0.684,-7.819],[-0.576,-5.9],[-5.41,-2.945],[-0.342,-0.361],[-0.039,0.195],[-0.194,0.2],[-1.882,10.68]],"v":[[-19.17,9.955],[-4.279,32.686],[-3.443,32.427],[3.016,13.054],[3.383,-11.045],[-5.078,-26.068],[-20.455,-23.487],[-21.745,-23.086],[-21.852,-22.501],[-21.97,-21.844]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[-3.288,-10.296],[-8.8,-3.512],[-0.183,0.253],[-0.606,6.924],[0.763,7.819],[5.471,2.978],[4.654,-3.092],[0.133,-0.662],[0.037,-0.194],[-0.098,-0.214]],"o":[[2.631,8.239],[0.297,0.118],[4.127,-5.692],[0.684,-7.819],[-0.576,-5.9],[-5.41,-2.945],[-0.342,-0.361],[-0.039,0.195],[-0.194,0.2],[-1.882,10.68]],"v":[[-19.17,9.955],[-4.279,32.686],[-3.443,32.427],[3.016,13.054],[3.383,-11.045],[-5.078,-26.068],[-20.455,-23.487],[-21.745,-23.086],[-21.852,-22.501],[-21.97,-21.844]],"c":true}]},{"t":95,"s":[{"i":[[-1.476,-10.707],[-8.069,-4.967],[-0.224,0.218],[-1.782,6.718],[-0.586,7.834],[4.88,3.871],[5.115,-2.249],[0.244,-0.63],[0.07,-0.185],[-0.06,-0.228]],"o":[[1.181,8.568],[0.272,0.167],[5.041,-4.901],[2.013,-7.586],[0.443,-5.911],[-4.826,-3.828],[-0.275,-0.414],[-0.072,0.185],[-0.225,0.164],[-3.683,10.2]],"v":[[-12.392,5.897],[-1.614,30.842],[-0.746,30.73],[8.936,12.75],[13.425,-10.931],[7.662,-27.181],[-7.93,-27.271],[-9.27,-27.097],[-9.475,-26.539],[-9.704,-25.912]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[303.218,116.872],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 129","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[443.704,483.385],"ix":2},"a":{"a":0,"k":[303.385,117.574],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 129","np":1,"cix":2,"bm":0,"ix":16,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[-0.186,-0.516],[-0.043,-0.071],[0.987,2.706],[0,0],[0.071,0.157],[0.245,0.172],[0,0],[0,0],[0.802,-2.018]],"o":[[0.014,0.086],[1.03,1.903],[0,0],[-0.028,-0.157],[-0.587,-1.331],[0,-0.014],[-2.003,4.323],[0.171,2.161],[-0.357,0.759]],"v":[[-6.362,6.348],[-6.276,6.563],[5.561,-5.217],[5.561,-5.231],[5.404,-5.704],[2.869,-8.452],[2.855,-8.466],[-3.628,-2.526],[-6.048,4.387]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[-0.448,-0.316],[-0.074,-0.037],[2.361,1.65],[0,0],[0.147,0.089],[0.299,0],[0,0],[0,0],[-0.501,-2.113]],"o":[[0.061,0.062],[1.935,0.968],[0,0],[-0.113,-0.113],[-1.244,-0.753],[-0.008,-0.011],[0.84,4.69],[1.38,1.672],[0.143,0.826]],"v":[[12.601,-11.037],[12.795,-10.91],[15.731,-27.35],[15.723,-27.361],[15.323,-27.659],[11.67,-28.455],[11.651,-28.458],[9.749,-19.874],[11.733,-12.823]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[-0.448,-0.316],[-0.074,-0.037],[2.361,1.65],[0,0],[0.147,0.089],[0.299,0],[0,0],[0,0],[-0.501,-2.113]],"o":[[0.061,0.062],[1.935,0.968],[0,0],[-0.113,-0.113],[-1.244,-0.753],[-0.008,-0.011],[0.84,4.69],[1.38,1.672],[0.143,0.826]],"v":[[12.601,-11.037],[12.795,-10.91],[15.731,-27.35],[15.723,-27.361],[15.323,-27.659],[11.67,-28.455],[11.651,-28.458],[9.749,-19.874],[11.733,-12.823]],"c":true}]},{"t":95,"s":[{"i":[[-0.186,-0.516],[-0.043,-0.071],[0.987,2.706],[0,0],[0.071,0.157],[0.245,0.172],[0,0],[0,0],[0.802,-2.018]],"o":[[0.014,0.086],[1.03,1.903],[0,0],[-0.028,-0.157],[-0.587,-1.331],[0,-0.014],[-2.003,4.323],[0.171,2.161],[-0.357,0.759]],"v":[[-6.362,6.348],[-6.276,6.563],[5.561,-5.217],[5.561,-5.231],[5.404,-5.704],[2.869,-8.452],[2.855,-8.466],[-3.628,-2.526],[-6.048,4.387]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[335.217,183.312],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 130","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[607.087,820.848],"ix":2},"a":{"a":0,"k":[334.805,182.471],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 130","np":1,"cix":2,"bm":0,"ix":17,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.345,"y":1},"o":{"x":0.556,"y":0},"t":0,"s":[{"i":[[1.014,2.805],[1.385,1.206],[0,0],[1.737,1.665],[0.574,-2.633],[-0.276,-1.053],[-0.012,-0.186],[0.516,-2.063],[-0.199,-0.342]],"o":[[-0.43,-1.163],[-0.009,-0.018],[-1.081,-0.933],[-2.573,0.886],[1.267,1.424],[0.048,0.169],[0.194,2.46],[-0.161,0.55],[0.999,1.754]],"v":[[5.912,-3.321],[3.598,-6.506],[3.576,-6.529],[-0.637,-10.27],[-6.925,-6.529],[-2.994,-1.107],[-2.905,-0.576],[-5.593,7.158],[-5.564,8.516]],"c":true}]},{"i":{"x":0.468,"y":1},"o":{"x":0.47,"y":0},"t":27,"s":[{"i":[[2.452,1.698],[1.826,0.193],[0,0],[2.4,0.174],[-1.04,-2.486],[-0.83,-0.704],[-0.119,-0.144],[-0.761,-1.986],[-0.358,-0.169]],"o":[[-1.019,-0.706],[-0.018,-0.01],[-1.421,-0.144],[-1.416,2.324],[1.855,0.439],[0.136,0.111],[1.57,1.904],[0.184,0.543],[1.825,0.863]],"v":[[16.122,-25.232],[12.399,-26.513],[12.368,-26.519],[5.994,-28.427],[3.767,-20.495],[10.098,-18.309],[10.475,-17.925],[12.71,-10.049],[13.513,-8.954]],"c":true}]},{"i":{"x":0.302,"y":1},"o":{"x":0.525,"y":0},"t":71,"s":[{"i":[[2.452,1.698],[1.826,0.193],[0,0],[2.4,0.174],[-1.04,-2.486],[-0.83,-0.704],[-0.119,-0.144],[-0.761,-1.986],[-0.358,-0.169]],"o":[[-1.019,-0.706],[-0.018,-0.01],[-1.421,-0.144],[-1.416,2.324],[1.855,0.439],[0.136,0.111],[1.57,1.904],[0.184,0.543],[1.825,0.863]],"v":[[16.122,-25.232],[12.399,-26.513],[12.368,-26.519],[5.994,-28.427],[3.767,-20.495],[10.098,-18.309],[10.475,-17.925],[12.71,-10.049],[13.513,-8.954]],"c":true}]},{"t":95,"s":[{"i":[[1.014,2.805],[1.385,1.206],[0,0],[1.737,1.665],[0.574,-2.633],[-0.276,-1.053],[-0.012,-0.186],[0.516,-2.063],[-0.199,-0.342]],"o":[[-0.43,-1.163],[-0.009,-0.018],[-1.081,-0.933],[-2.573,0.886],[1.267,1.424],[0.048,0.169],[0.194,2.46],[-0.161,0.55],[0.999,1.754]],"v":[[5.912,-3.321],[3.598,-6.506],[3.576,-6.529],[-0.637,-10.27],[-6.925,-6.529],[-2.994,-1.107],[-2.905,-0.576],[-5.593,7.158],[-5.564,8.516]],"c":true}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 10')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[334.496,181.368],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 131","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[603.007,811.053],"ix":2},"a":{"a":0,"k":[334.021,180.587],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 131","np":1,"cix":2,"bm":0,"ix":18,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Head","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.792],"y":[0.802]},"o":{"x":[0.622],"y":[0]},"t":1.909,"s":[-13]},{"i":{"x":[0.379],"y":[1]},"o":{"x":[0.174],"y":[-0.874]},"t":14.318,"s":[3.657]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28.637,"s":[0]},{"i":{"x":[0.797],"y":[0.806]},"o":{"x":[0.615],"y":[0]},"t":73,"s":[0]},{"i":{"x":[0.359],"y":[1]},"o":{"x":[0.17],"y":[-1.048]},"t":83.5,"s":[-16.195]},{"t":96.86328125,"s":[-13]}],"ix":10},"p":{"a":0,"k":[296.372,53.87,0],"ix":2},"a":{"a":0,"k":[407.236,152.124,0],"ix":1},"s":{"a":0,"k":[19.231,19.231,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[4.345,0.772],[0.459,-0.611],[-0.381,-0.769],[0,0],[-0.817,1.088],[-1.676,-0.294],[0,-0.045]],"o":[[0,-0.176],[-2.242,-0.4],[-0.761,1.015],[0,0],[-0.42,-0.852],[1.027,-1.368],[4.553,0.809],[0,0]],"v":[[4.725,2.818],[0.325,-2.278],[-3.683,-0.688],[-4.331,2.343],[-4.556,2.455],[-3.882,-0.838],[0.369,-2.524],[4.975,2.818]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[302.722,43.201],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 109","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[440.841,97.224],"ix":2},"a":{"a":0,"k":[302.835,43.312],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 109","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.548,0.457],[0.176,-0.146],[-0.483,-0.221],[0.376,0.464],[-0.143,-0.176],[0.774,0.104]],"o":[[0.174,-0.145],[-0.272,0.228],[0.432,0.198],[-0.143,-0.175],[0.556,0.686],[-0.958,-0.128]],"v":[[-0.433,-0.867],[-0.183,-0.616],[-0.36,0.524],[0.43,-0.262],[0.68,-0.513],[-0.278,0.908]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.827000021935,0.340999990702,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 13')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[299.076,52.96],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 110","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[421.174,147.406],"ix":2},"a":{"a":0,"k":[299.053,52.963],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 110","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.917,-0.269],[1.61,0.023]],"o":[[0,0],[0.917,0.268],[-1.611,-0.022]],"v":[[-0.827,-0.481],[0.738,-1.129],[-0.044,1.375]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 10')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[299.063,51.153],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 111","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[421.962,138.506],"ix":2},"a":{"a":0,"k":[299.204,51.251],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 111","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.347,-0.05],[0,0],[-0.591,0.712],[0.002,0.015],[0,0],[0.944,-1.138]],"o":[[0,0],[1.272,-0.047],[0.873,-1.052],[0,0],[0.009,0.063],[-0.639,0.769]],"v":[[-1.963,1.959],[-1.972,1.709],[0.836,0.564],[1.507,-1.923],[1.754,-1.959],[1.029,0.725]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[300.247,50.77],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 112","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[426.882,136.003],"ix":2},"a":{"a":0,"k":[300.15,50.77],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 112","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.667,-1.646],[-0.202,0.044],[-2.149,0.732],[0.948,1.897],[2.409,-1.011]],"o":[[0.191,0.189],[1.365,1.711],[2.114,-0.72],[-1.206,-2.411],[-1.995,0.837]],"v":[[-3.895,1.147],[-3.283,1.331],[2.564,4.042],[4.615,-1.396],[-2.605,-3.762]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[302.771,45.761],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 113","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[441.147,110.096],"ix":2},"a":{"a":0,"k":[302.894,45.788],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 113","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.161,1.084],[-0.156,0.221],[-0.46,0.267],[-0.521,-0.058],[0.614,-3.023],[1.151,0.987],[0.125,0.682],[-0.004,0.463],[0.172,0.04],[0.141,-0.376],[0.217,-0.105],[0.199,0.048],[0.328,-0.188]],"o":[[0.037,-0.257],[-0.054,-0.457],[0.457,-0.265],[2.729,-0.928],[-0.198,0.972],[-0.572,-0.49],[-0.086,-0.473],[-0.127,0.057],[-0.425,-0.099],[-0.084,0.225],[-0.181,0.087],[-0.341,-0.082],[-0.872,0.5]],"v":[[-4.026,-0.961],[-3.72,-1.693],[-3.116,-2.883],[-1.613,-3.13],[3.573,1.393],[1.131,3.072],[0.335,0.766],[0.209,-0.634],[-0.234,-0.59],[-0.7,-0.419],[-1.122,0.052],[-1.673,0.125],[-2.257,0.189]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[297.712,48.809],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 114","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[413.189,125.91],"ix":2},"a":{"a":0,"k":[297.517,48.829],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 114","np":1,"cix":2,"bm":0,"ix":9,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.85,-3.131],[3.399,0.357],[-0.536,0.135]],"o":[[0,0],[-3.4,-0.358],[0.537,-0.133]],"v":[[4.227,-1.051],[-0.827,3.825],[-0.559,-3.78]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 10')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[295.887,50.984],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 115","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[409.492,137.099],"ix":2},"a":{"a":0,"k":[296.806,50.98],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 115","np":1,"cix":2,"bm":0,"ix":10,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Neck","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.324],"y":[1]},"o":{"x":[0.568],"y":[0]},"t":1.909,"s":[7]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28.637,"s":[0]},{"i":{"x":[0.304],"y":[1]},"o":{"x":[0.559],"y":[0]},"t":73,"s":[0]},{"t":96.86328125,"s":[7]}],"ix":10},"p":{"a":0,"k":[416.764,172.966,0],"ix":2},"a":{"a":0,"k":[298.205,57.878,0],"ix":1},"s":{"a":0,"k":[520,520,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.134,-0.895],[-1.985,0.476],[0.073,0.169],[0.347,1.694]],"o":[[0,0],[0.04,0.274],[2.163,-0.519],[-0.45,-1.041],[-0.403,-1.968]],"v":[[-2.999,-1.997],[-1.657,2.521],[1.272,4.342],[2.802,2.033],[1.295,-1.997]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999968884,0.243000000598,0.447000002394,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 10')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[297.387,55.24],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 116","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"B_hand_1","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.324],"y":[1]},"o":{"x":[0.568],"y":[0]},"t":1.909,"s":[-44]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28.637,"s":[0]},{"i":{"x":[0.304],"y":[1]},"o":{"x":[0.559],"y":[0]},"t":73,"s":[0]},{"t":96.86328125,"s":[-44]}],"ix":10},"p":{"a":0,"k":[402.958,203.172,0],"ix":2},"a":{"a":0,"k":[295.55,63.687,0],"ix":1},"s":{"a":0,"k":[520,520,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.018,-1.024],[5.228,-3.744],[0.423,-1.494],[-2.138,0.679],[-8.594,10.266]],"o":[[-6.551,2.223],[-1.597,1.144],[2.589,5.848],[1.497,-0.476],[5.041,-6.021]],"v":[[7.88,-13.592],[-12.603,-0.704],[-17.843,5.484],[-10.818,15.894],[7.235,0.345]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.894117647059,0.917647058824,0.937254901961,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 14')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[286.196,73.764],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 134","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"B_hand_2","parent":11,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.805],"y":[1.881]},"o":{"x":[0.59],"y":[0]},"t":3.818,"s":[-45]},{"i":{"x":[0.497],"y":[1]},"o":{"x":[0.183],"y":[0.142]},"t":15.272,"s":[-56.217]},{"i":{"x":[0.538],"y":[1]},"o":{"x":[0.517],"y":[0]},"t":26.728,"s":[9]},{"i":{"x":[0.538],"y":[1]},"o":{"x":[0.416],"y":[0]},"t":34.675,"s":[-14]},{"i":{"x":[0.549],"y":[1]},"o":{"x":[0.409],"y":[0]},"t":42.621,"s":[-2]},{"i":{"x":[0.538],"y":[1]},"o":{"x":[0.416],"y":[0]},"t":51.363,"s":[-14]},{"i":{"x":[0.549],"y":[1]},"o":{"x":[0.409],"y":[0]},"t":59.311,"s":[-2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.416],"y":[0]},"t":68.053,"s":[-14]},{"i":{"x":[0.304],"y":[1]},"o":{"x":[0.559],"y":[0]},"t":76,"s":[-2]},{"t":98.7724609375,"s":[-45]}],"ix":10},"p":{"a":0,"k":[274.777,89.579,0],"ix":2},"a":{"a":0,"k":[294.939,337.811,0],"ix":1},"s":{"a":0,"k":[19.231,19.231,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.01,0],[0.015,0.057],[-0.001,0.027],[-0.06,0.002],[0.003,-0.068],[-0.711,-2.89],[0.068,-0.017]],"o":[[-0.056,0],[-0.719,-2.927],[0.003,-0.069],[0.068,0.004],[-0.002,0.028],[0.016,0.067],[-0.009,0.003]],"v":[[0.352,2.945],[0.23,2.85],[-0.359,-2.826],[-0.228,-2.945],[-0.109,-2.814],[0.474,2.79],[0.381,2.941]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.723,68.861],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 91","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[222.142,230.075],"ix":2},"a":{"a":0,"k":[260.777,68.861],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 91","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.03,0],[0.025,0.026],[0.018,0.081],[-0.067,0.015],[-0.016,-0.067],[-0.635,-0.686],[0.051,-0.047]],"o":[[-0.034,0],[-0.681,-0.736],[-0.015,-0.068],[0.071,-0.015],[0.004,0.019],[0.047,0.051],[-0.024,0.022]],"v":[[0.55,1.469],[0.458,1.429],[-0.674,-1.304],[-0.579,-1.454],[-0.429,-1.359],[0.642,1.259],[0.635,1.436]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[261.612,66.966],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 92","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[226.48,220.257],"ix":2},"a":{"a":0,"k":[261.612,66.972],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 92","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.018,0],[0.021,0.046],[0.024,0.013],[-0.033,0.06],[-0.061,-0.037],[-0.798,-1.698],[0.063,-0.029]],"o":[[-0.047,0],[-0.765,-1.626],[-0.06,-0.033],[0.034,-0.062],[0.1,0.057],[0.03,0.063],[-0.017,0.008]],"v":[[1.611,1.662],[1.498,1.59],[-1.673,-1.408],[-1.721,-1.579],[-1.55,-1.626],[1.724,1.485],[1.664,1.65]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[263.194,66.832],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 93","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[234.71,219.573],"ix":2},"a":{"a":0,"k":[263.194,66.841],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 93","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.026,0],[0.025,0.033],[0.041,0.009],[-0.015,0.068],[-0.07,-0.014],[-1.33,-1.757],[0.054,-0.042]],"o":[[-0.038,0],[-1.275,-1.683],[-0.067,-0.015],[0.016,-0.066],[0.171,0.038],[0.041,0.054],[-0.023,0.017]],"v":[[2.728,1.46],[2.628,1.411],[-2.759,-1.202],[-2.854,-1.352],[-2.704,-1.446],[2.827,1.261],[2.803,1.436]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827000021935,0.878000020981,0.917999982834,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[265.092,66.257],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 94","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[244.565,216.566],"ix":2},"a":{"a":0,"k":[265.089,66.263],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 9/1 Outlines - Group 94","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.587,0.534],[-3.411,-3.951],[1.721,0.287],[-2.233,10.311]],"o":[[7.38,-2.479],[3.411,3.952],[-1.722,-0.286],[0,0]],"v":[[-7.918,-11.511],[9.698,1.696],[4.202,13.702],[-10.876,-9.092]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.894117647059,0.917647058824,0.937254901961,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 14')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[270.478,75.885],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 133","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[295.065,337.936],"ix":2},"a":{"a":0,"k":[274.801,89.603],"ix":1},"s":{"a":0,"k":[520,520],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"B_hand_1","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"B_arm","parent":12,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[-21]},{"i":{"x":[0.769],"y":[2.233]},"o":{"x":[0.461],"y":[0]},"t":3.818,"s":[-21]},{"i":{"x":[0.592],"y":[1.151]},"o":{"x":[0.425],"y":[0.317]},"t":11.454,"s":[-16.08]},{"i":{"x":[0.752],"y":[1.14]},"o":{"x":[0.243],"y":[0.087]},"t":18.137,"s":[-46.923]},{"i":{"x":[0.703],"y":[1.267]},"o":{"x":[0.262],"y":[0.153]},"t":31.05,"s":[14.997]},{"i":{"x":[0.709],"y":[1.242]},"o":{"x":[0.269],"y":[0.246]},"t":38.829,"s":[-21.152]},{"i":{"x":[0.709],"y":[1.042]},"o":{"x":[0.279],"y":[0.25]},"t":46.609,"s":[14.437]},{"i":{"x":[0.699],"y":[1.066]},"o":{"x":[0.271],"y":[0.056]},"t":55.254,"s":[-22.27]},{"i":{"x":[0.706],"y":[0.997]},"o":{"x":[0.275],"y":[0.093]},"t":63.897,"s":[8.163]},{"i":{"x":[0.797],"y":[1.549]},"o":{"x":[0.33],"y":[0.017]},"t":72.542,"s":[-9.47]},{"i":{"x":[0.359],"y":[1]},"o":{"x":[0.17],"y":[0.275]},"t":85.409,"s":[6.181]},{"t":98.7724609375,"s":[-21]}],"ix":10},"p":{"a":0,"k":[225,217.163,0],"ix":2},"a":{"a":0,"k":[554.462,371.754,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.572,-0.295],[0.66,-0.794],[0.865,-0.849],[-0.46,0.451],[-0.694,0.845],[-0.964,0.498]],"o":[[-0.915,0.472],[-0.777,0.932],[-0.461,0.451],[0.78,-0.766],[0.684,-0.834],[0.572,-0.295]],"v":[[2.222,-2.56],[-0.166,-0.674],[-2.837,1.697],[-2.131,2.404],[0.335,0.29],[2.726,-1.697]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999982834,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 4')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[191.133,62.112],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 55","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[567.135,346.341],"ix":2},"a":{"a":0,"k":[191.128,62.076],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 55","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.511,0],[0.799,0.195],[0.08,0.417],[-0.309,0.22],[-0.184,-0.086],[0.049,-0.104],[0.102,0.049],[1.696,-1.2],[-0.027,-0.042],[-0.836,0.175],[0.989,0.163],[-0.019,0.114],[-0.109,-0.015],[-0.178,-0.46],[0.075,-0.096]],"o":[[-0.923,0],[-1.135,-0.275],[-0.048,-0.251],[1.893,-1.343],[0.105,0.049],[-0.05,0.107],[-0.043,-0.021],[-0.288,0.205],[0.46,0.702],[-0.353,-0.2],[-0.115,-0.019],[0.019,-0.115],[1.001,0.166],[0.042,0.108],[-0.118,0.149]],"v":[[1.392,1.734],[-1.644,1.347],[-3.449,0.318],[-3.056,-0.391],[3.347,0.353],[3.448,0.632],[3.168,0.734],[-2.813,-0.047],[-3.01,0.297],[1.977,1.288],[-0.488,0.648],[-0.661,0.406],[-0.42,0.232],[2.465,1.204],[2.413,1.525]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.671000003815,0.764999985695,0.838999986649,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 15')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[194.208,49.967],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 56","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[583.019,286.25],"ix":2},"a":{"a":0,"k":[194.212,50.408],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 56","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.424,3.044],[0.801,0.083],[0.788,-1.288],[-0.071,-0.216],[-0.009,-0.025],[-0.243,-0.113]],"o":[[-0.209,-1.501],[-1.04,-0.108],[-0.113,0.184],[0.008,0.027],[-0.082,0.253],[0.921,0.427]],"v":[[3.576,-0.526],[0.108,-2.156],[-3.874,-1.23],[-3.928,-0.624],[-3.897,-0.556],[-3.692,0.16]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.893999993801,0.917999982834,0.936999976635,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 14')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[194.425,50.933],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 57","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[583.14,286.138],"ix":2},"a":{"a":0,"k":[194.236,50.386],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 57","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.596,9.876],[0.131,0.052],[0.166,-0.09],[2.09,1.29],[-0.11,-0.456],[1.335,-9.612],[0.002,-0.134],[0.003,-0.171],[-0.188,0.024],[-1.97,-0.851],[0.063,0.358]],"o":[[0.016,-0.255],[-0.071,-0.16],[-2.126,1.138],[-0.255,-0.157],[2.239,9.277],[-0.05,0.078],[-0.003,0.171],[-0.004,0.252],[2.025,-0.259],[0.266,0.114],[-1.706,-9.729]],"v":[[3.484,-14.68],[3.264,-15.134],[2.899,-15.299],[-3.482,-15.483],[-4.002,-14.919],[-2.619,13.784],[-2.708,14.096],[-2.716,14.609],[-2.362,15.109],[3.612,15.527],[4.048,14.911]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[194.566,65.811],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 58","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[584.937,365.623],"ix":2},"a":{"a":0,"k":[194.585,65.82],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 58","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.011,0],[0.016,0.056],[-0.067,0.017],[-0.024,0.017],[-0.042,-0.058],[0.056,-0.04],[1.395,-0.383]],"o":[[-0.055,0],[-0.018,-0.066],[1.351,-0.372],[0.055,-0.041],[0.04,0.055],[-0.097,0.072],[-0.011,0.004]],"v":[[-1.884,1.191],[-2.005,1.099],[-1.917,0.946],[1.808,-1.15],[1.983,-1.122],[1.955,-0.949],[-1.851,1.186]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.355,63.466],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 108","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[588.896,353.544],"ix":2},"a":{"a":0,"k":[195.354,63.475],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 108","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.008,0],[0.012,0.06],[-0.068,0.014],[-0.027,0.025],[-0.047,-0.051],[0.051,-0.047],[1.871,-0.366]],"o":[[-0.059,0],[-0.013,-0.068],[1.803,-0.352],[0.051,-0.046],[0.046,0.051],[-0.112,0.101],[-0.008,0.001]],"v":[[-2.294,1.551],[-2.417,1.45],[-2.318,1.303],[2.207,-1.505],[2.384,-1.496],[2.375,-1.319],[-2.27,1.549]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[195.24,62.318],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 109","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[588.303,347.625],"ix":2},"a":{"a":0,"k":[195.238,62.325],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 109","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.01,0],[0.015,0.058],[-0.067,0.016],[-0.02,0.018],[-0.046,-0.048],[0.051,-0.047],[2.212,-0.541]],"o":[[-0.056,0],[-0.016,-0.066],[2.151,-0.526],[0.052,-0.047],[0.047,0.051],[-0.085,0.078],[-0.01,0.003]],"v":[[-2.129,1.349],[-2.251,1.253],[-2.159,1.103],[2.043,-1.302],[2.22,-1.296],[2.213,-1.119],[-2.099,1.345]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[194.539,61.513],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 110","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[584.7,343.481],"ix":2},"a":{"a":0,"k":[194.539,61.521],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 110","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.306,0.02],[-0.021,0.031],[0.308,-0.465],[0.023,-0.03],[0.443,-0.345],[1.984,-0.218],[-0.469,0.231],[0.498,-0.252],[0.072,-0.073],[1.318,-2.189],[0.207,-0.372],[-0.085,-0.113],[-0.592,-0.411],[-0.065,-0.011],[-0.191,0.477],[-0.014,0.016],[-1.926,1.534],[0.397,-0.124],[0.005,0.001],[-0.006,0.005],[-0.126,0.163]],"o":[[0.022,-0.029],[0.306,-0.464],[-0.021,0.033],[-0.078,-0.28],[-0.129,0.1],[0.316,-0.32],[0.499,-0.245],[-0.079,0.04],[-2.18,1.346],[-0.242,0.339],[-0.074,0.134],[0.429,0.577],[0.059,0.041],[-0.06,0.449],[0.511,-1.277],[2.33,-0.788],[0.389,-0.31],[-0.005,-0.001],[0.006,-0.005],[0.122,-0.08],[0.244,-0.314]],"v":[[5.443,-4.121],[5.513,-4.206],[4.655,-4.808],[4.579,-4.725],[3.67,-4.95],[-0.245,-3.2],[1.075,-4.278],[0.729,-5.072],[-0.06,-4.616],[-5.381,1.054],[-6.061,2.115],[-5.999,2.547],[-3.751,4.561],[-3.562,4.631],[-2.74,4.847],[-0.944,1.385],[5.557,-1.741],[5.149,-2.61],[5.134,-2.616],[5.151,-2.632],[5.891,-3.342]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.917999982834,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 4')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[192.088,65.027],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 111","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[571.747,361.503],"ix":2},"a":{"a":0,"k":[192.024,65.02],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 111","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Backpack_back","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-3.812,"ix":10},"p":{"a":0,"k":[479.857,231.512,0],"ix":2},"a":{"a":0,"k":[282.361,396.838,0],"ix":1},"s":{"a":0,"k":[-100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.468,0],[0.195,0.063],[-0.221,1.318],[-1.183,0],[0,0],[-0.322,-0.388],[0.009,-0.05],[0.136,0.029],[-0.022,0.136],[0.481,0.578],[0.45,0.01],[0,0],[0.161,-0.96],[-0.971,-0.312],[-0.21,0.567],[0.241,0.102],[0.064,-0.585],[-0.065,-0.03],[0.057,-0.126],[0.127,0.06],[-0.068,0.618],[-0.592,0.124],[-0.215,-0.345],[0.659,-0.847]],"o":[[-0.156,0],[-1.051,-0.338],[0.2,-1.194],[0,0],[0.602,0.013],[0.627,0.756],[-0.023,0.136],[-0.136,-0.023],[0.002,-0.01],[-0.226,-0.271],[0,0],[-0.896,0],[-0.142,0.843],[0.547,0.174],[0.364,-0.986],[-0.261,0.061],[-0.048,0.444],[0.125,0.057],[-0.057,0.126],[-0.324,-0.147],[0.077,-0.699],[0.131,-0.031],[0.432,0.691],[-0.184,0.236]],"v":[[-0.127,2.601],[-0.653,2.512],[-2.018,-0.213],[0.183,-2.601],[0.22,-2.601],[1.613,-1.997],[2.044,-0.016],[1.757,0.189],[1.551,-0.098],[1.227,-1.679],[0.209,-2.101],[0.184,-2.101],[-1.525,-0.131],[-0.501,2.036],[0.626,1.455],[0.411,-1.067],[-0.184,0.083],[-0.065,0.796],[0.059,1.127],[-0.272,1.251],[-0.681,0.029],[0.33,-1.554],[0.924,-1.23],[0.827,2.125]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.671000003815,0.764999985695,0.838999986649,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 15')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[131.507,56.204],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 59","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[260.109,316.1],"ix":2},"a":{"a":0,"k":[131.512,56.204],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 59","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.105,1.147],[1.141,-1.044],[0.075,-0.183],[0.041,-0.232],[-1.11,-1.027],[-0.785,1.503]],"o":[[-1.271,-1.321],[-0.181,0.164],[-0.155,0.165],[-0.254,1.446],[1.505,1.395],[0.678,-1.3]],"v":[[2.006,-2.824],[-2.166,-2.341],[-2.545,-1.816],[-2.857,-1.221],[-1.666,2.75],[2.231,1.463]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[131.413,56.224],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 60","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[259.216,315.803],"ix":2},"a":{"a":0,"k":[131.338,56.146],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 60","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[11.906,3.446],[-11.906,3.446],[-11.906,-3.446],[11.906,-3.446]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.827000021935,0.340999990702,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 13')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[143.59,56.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 112","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[322.315,315.821],"ix":2},"a":{"a":0,"k":[143.59,56.15],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 112","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.273,4.032],[3.167,0.695],[3,-2.19],[0.042,-0.026],[2.965,-6.774],[-6.977,-3.205],[-2.642,1.85],[-2.478,2.989],[-2.253,3.325]],"o":[[-0.266,-3.935],[-3.385,-0.744],[-0.042,0.013],[-6.313,3.885],[-2.599,5.943],[2.835,1.301],[3.198,-2.24],[2.565,-3.095],[2.126,-3.138]],"v":[[17.947,-11.853],[10.049,-16.837],[-0.451,-16.355],[-0.576,-16.309],[-14.685,0.178],[-11.243,16.915],[-2.39,16.695],[6.036,8.373],[13.176,-1.333]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.337000012398,0.356999993324,0.509999990463,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 16')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[134.073,73.079],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 113","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[276.77,403.736],"ix":2},"a":{"a":0,"k":[134.747,73.221],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 113","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.813,1.405],[0.07,0.003],[0.014,0.002],[0.613,-2.726],[-2.984,-1.042],[-0.724,2.354]],"o":[[-0.074,-0.036],[-0.013,-0.003],[-3.343,-0.418],[-0.568,2.522],[2.892,1.01],[0.752,-2.443]],"v":[[1.739,-6.53],[1.523,-6.586],[1.485,-6.598],[-3.281,-0.814],[-1.568,6.006],[2.99,1.591]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[119.953,77.347],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 114","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[200.379,423.881],"ix":2},"a":{"a":0,"k":[119.913,77.132],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 114","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.635,0.099],[1.538,-0.57],[0.831,-1.122],[-0.281,-1.294],[-1.134,-0.283],[-0.097,0.053],[-0.281,0.003],[-0.512,0.575],[-0.046,2.593],[-0.073,-0.011]],"o":[[-1.548,-0.241],[-1.365,0.506],[-0.704,0.951],[0.272,1.256],[0.136,0.035],[0.306,0.106],[0.864,-0.007],[1.659,-1.867],[0.072,0.011],[0.629,0.098]],"v":[[4.793,-3.878],[-0.259,-4.207],[-3.378,-1.235],[-5.147,2.368],[-2.357,4.648],[-2.009,4.604],[-1.12,4.774],[0.804,3.208],[4.309,-2.949],[4.527,-2.914]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[126.183,64.376],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 115","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[232.591,359.065],"ix":2},"a":{"a":0,"k":[126.168,64.547],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 115","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[282.361,396.857],"ix":2},"a":{"a":0,"k":[282.361,396.857],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Backpack_back","np":4,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.92],[1.409,0],[0,1.92],[-1.409,0]],"o":[[0,1.92],[-1.409,0],[0,-1.92],[1.409,0]],"v":[[7.552,17.664],[5,21.14],[2.448,17.664],[5,14.187]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.827000021935,0.340999990702,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 13')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[246.814,50.007],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 42","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[357.917,224.689],"ix":2},"a":{"a":0,"k":[246.814,50.007],"ix":1},"s":{"a":0,"k":[515,515],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 4/Office management 2 Outlines - Group 42","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}]},{"id":"comp_1","layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Pin 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.696],"y":[0]},"t":17.429,"s":[-60]},{"i":{"x":[0.476],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31.755,"s":[15]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":42.897,"s":[-6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52.448,"s":[3]},{"i":{"x":[0.661],"y":[1]},"o":{"x":[0.278],"y":[0]},"t":66,"s":[0]},{"i":{"x":[0.835],"y":[2.049]},"o":{"x":[0.67],"y":[0]},"t":78,"s":[3]},{"i":{"x":[0.329],"y":[1]},"o":{"x":[0.165],"y":[0.164]},"t":90.5,"s":[-14.015]},{"t":99,"s":[60]}],"ix":10},"p":{"a":0,"k":[1263.712,569.783,0],"ix":2},"a":{"a":0,"k":[149.712,367.783,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.81,0.81,0.667],"y":[-0.187,-0.187,1]},"o":{"x":[0.549,0.549,0.333],"y":[0,0,0]},"t":14,"s":[0,0,100]},{"i":{"x":[0.542,0.542,0.667],"y":[1,1,1]},"o":{"x":[0.332,0.332,0.333],"y":[0.315,0.315,0]},"t":23.143,"s":[22.458,22.458,100]},{"i":{"x":[0.476,0.476,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":28.326,"s":[106,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":39.469,"s":[97,97,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":49.021,"s":[101,101,100]},{"i":{"x":[0.424,0.424,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":60.571,"s":[100,100,100]},{"i":{"x":[0.835,0.835,0.667],"y":[-1.277,-1.277,1]},"o":{"x":[0.67,0.67,0.333],"y":[0,0,0]},"t":80,"s":[100,100,100]},{"i":{"x":[0.666,0.666,0.667],"y":[1,1,1]},"o":{"x":[0.165,0.165,0.333],"y":[0.147,0.147,0]},"t":90.5,"s":[89.024,89.024,100]},{"t":96,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.638],[3.641,0],[0,3.637],[-3.637,0]],"o":[[0,3.637],[-3.637,0],[0,-3.638],[3.641,0]],"v":[[6.586,0],[-0.002,6.589],[-6.586,0],[-0.002,-6.589]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 4')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[246.87,90.027],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 66","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[149.653,257.256],"ix":2},"a":{"a":0,"k":[246.87,90.027],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 66","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.642,0],[0,3.637],[-3.637,0],[0,-3.638]],"o":[[-3.637,0],[0,-3.638],[3.642,0],[0,3.637]],"v":[[-0.002,2.194],[-6.585,-4.394],[-0.002,-10.983],[6.585,-4.394]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[4.904,4.905],[4.909,-4.908],[-3.81,-5.799],[0,0],[-2.301,3.411]],"o":[[-4.909,-4.908],[-4.904,4.905],[2.301,3.489],[0,0],[3.875,-5.755]],"v":[[8.886,-12.285],[-8.886,-12.285],[-8.886,5.484],[-0.002,17.193],[8.886,5.484]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[246.87,94.421],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 67","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[149.712,282.896],"ix":2},"a":{"a":0,"k":[246.881,95.034],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 67","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Pin 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.696],"y":[0]},"t":3.429,"s":[-60]},{"i":{"x":[0.476],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17.755,"s":[15]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28.897,"s":[-6]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":38.448,"s":[3]},{"i":{"x":[0.661],"y":[1]},"o":{"x":[0.278],"y":[0]},"t":52,"s":[0]},{"i":{"x":[0.835],"y":[0.314]},"o":{"x":[0.67],"y":[0]},"t":64,"s":[3]},{"i":{"x":[0.329],"y":[1]},"o":{"x":[0.165],"y":[0.402]},"t":79.429,"s":[27.985]},{"t":91,"s":[60]}],"ix":10},"p":{"a":0,"k":[1508.379,428.795,0],"ix":2},"a":{"a":0,"k":[394.379,226.795,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.81,0.81,0.667],"y":[-0.187,-0.187,1]},"o":{"x":[0.549,0.549,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"i":{"x":[0.542,0.542,0.667],"y":[1,1,1]},"o":{"x":[0.332,0.332,0.333],"y":[0.315,0.315,0]},"t":9.143,"s":[22.458,22.458,100]},{"i":{"x":[0.476,0.476,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":14.326,"s":[106,106,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":25.469,"s":[97,97,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":35.021,"s":[101,101,100]},{"i":{"x":[0.424,0.424,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0,0,0]},"t":46.571,"s":[100,100,100]},{"i":{"x":[0.835,0.835,0.667],"y":[-1.169,-1.169,1]},"o":{"x":[0.67,0.67,0.333],"y":[0,0,0]},"t":66.571,"s":[100,100,100]},{"i":{"x":[0.666,0.666,0.667],"y":[1,1,1]},"o":{"x":[0.165,0.165,0.333],"y":[0.161,0.161,0]},"t":79.429,"s":[89.024,89.024,100]},{"t":87.142578125,"s":[0,0,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.369],[4.374,0],[0,4.368],[-4.369,0]],"o":[[0,4.368],[-4.369,0],[0,-4.369],[4.374,0]],"v":[[7.912,0],[-0.003,7.913],[-7.912,0],[-0.003,-7.913]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 4')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[294.654,58.146],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 68","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[394.309,94.026],"ix":2},"a":{"a":0,"k":[294.654,58.146],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 68","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.374,0],[0,4.368],[-4.369,0],[0,-4.369]],"o":[[-4.369,0],[0,-4.369],[4.374,0],[0,4.368]],"v":[[-0.002,2.635],[-7.911,-5.278],[-0.002,-13.192],[7.911,-5.278]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.891,5.892],[5.897,-5.896],[-4.578,-6.965],[0,0],[-2.763,4.098]],"o":[[-5.896,-5.896],[-5.891,5.892],[2.763,4.192],[0,0],[4.656,-6.913]],"v":[[10.674,-14.757],[-10.675,-14.757],[-10.675,6.587],[-0.002,20.653],[10.674,6.587]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.913999974728,0.243000000715,0.446999996901,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 5')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[294.654,63.424],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 69","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[394.379,124.825],"ix":2},"a":{"a":0,"k":[294.668,64.161],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 69","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"mask","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1402.467,482.199,0],"ix":2},"a":{"a":0,"k":[273.982,94.508,0],"ix":1},"s":{"a":0,"k":[512,512,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,29.039],[29.039,0],[0,-29.039],[-29.038,0]],"o":[[0,-29.039],[-29.038,0],[0,29.039],[29.039,0]],"v":[[52.58,0],[-0.001,-52.579],[-52.58,0],[-0.001,52.579]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.944999964097,0.957000014361,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.982,94.508],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 81","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Land","tt":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1395.345,488.782,0],"ix":2},"a":{"a":0,"k":[281.345,286.782,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.512,-0.019],[1.596,-0.378],[0.651,-0.123],[1.096,1.354],[2.824,-0.975],[2.734,0.39],[2.106,0.969],[-0.13,-2.704],[-0.04,-0.457],[0.164,-0.676],[-0.092,-0.68],[-0.881,-1.086],[0.035,-0.11],[-2.197,0.34],[-4.431,1.183],[-0.413,2.407]],"o":[[-1.617,0.019],[-0.675,0.16],[-2.043,0.386],[-1.745,-2.157],[-2.379,0.821],[-2.291,-0.327],[-1.51,-0.695],[0.023,0.48],[0.064,0.736],[-0.159,0.653],[0.19,1.405],[-0.072,0.061],[-1.436,4.561],[4.536,-0.705],[1.744,-0.464],[0.448,-2.613]],"v":[[15.292,0.437],[10.53,0.048],[8.61,0.902],[4.377,-1.06],[-2.432,-3.602],[-9.134,-1.788],[-15.711,-3.619],[-19.855,-2.456],[-19.442,-1.131],[-19.901,0.938],[-20.084,2.815],[-18.125,6.503],[-18.294,6.749],[-0.794,9.038],[12.624,6.005],[19.728,2.822]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[277.676,141.928],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 73","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[301.598,525.152],"ix":2},"a":{"a":0,"k":[276.546,142.35],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 73","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.431,0.054],[0.009,0.04],[3.752,-0.596],[1.037,0.042],[1.271,-0.892],[-3.295,-3.702],[0.84,-2.171],[-0.977,-0.84],[0.955,-1.862],[1.858,-0.854],[-0.069,-1.871],[-0.299,0.019],[-3.067,4.342],[-1.393,2.405],[-3.249,1.968]],"o":[[-0.001,-0.038],[-0.65,-3.134],[-0.148,-0.812],[-1.848,-0.078],[-2.441,1.711],[2.391,2.685],[-0.523,1.35],[1.427,1.225],[-0.979,1.906],[-1.591,0.732],[0.009,0.274],[5.541,-0.37],[1.597,-2.263],[1.826,-3.153],[0.476,-0.289]],"v":[[12.153,-6.881],[12.152,-6.993],[6.132,-13.64],[4.353,-15.069],[-0.681,-11.572],[-5.932,-5.232],[-7.376,-0.207],[-6.116,2.969],[-4.951,7.307],[-9.484,11.111],[-12.202,14.592],[-11.666,15.128],[0.373,7.448],[5.933,1.822],[12.37,-5.888]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[314.291,123.066],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 74","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[488.545,426.569],"ix":2},"a":{"a":0,"k":[313.059,123.096],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 74","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.313,1.159],[0.303,0.455],[0.373,-0.182],[0.062,-0.006],[0.093,-0.24],[0.035,-0.091],[0.003,-0.01],[-0.176,-1.214],[0.556,-0.8],[0.351,-0.773],[-2.43,0.797],[-0.191,0.945],[-0.862,0.779],[-0.218,0.449]],"o":[[-0.146,-0.535],[-0.019,-0.338],[-0.057,-0.002],[-0.26,0.025],[-0.035,0.091],[-0.003,0.009],[-0.834,0.837],[0.117,0.809],[-0.455,0.653],[-0.674,1.481],[0.938,-0.306],[0.207,-1.019],[0.345,-0.312],[0.577,-1.194]],"v":[[2.114,-3.965],[1.571,-5.652],[0.754,-6.102],[0.588,-6.111],[0.07,-5.717],[-0.036,-5.444],[-0.04,-5.418],[-1.386,-2.315],[-1.388,-0.466],[-2.766,1.204],[-0.746,5.487],[1.08,3.29],[1.921,1.29],[2.863,0.407]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[295.295,106.852],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 75","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[397.468,341.979],"ix":2},"a":{"a":0,"k":[295.271,106.574],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 75","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.206,3.048],[1.936,0.896],[3.054,0.754],[1.396,0.1],[0.984,0.874],[0.407,0.281],[0.862,-0.151],[1.134,-0.08],[0.88,-0.105],[0.581,-1.285],[0.807,-2.281],[-2.042,-2.368],[-0.391,-0.602],[0.371,-0.865],[-0.309,-0.692],[-0.916,-0.223],[-1.516,0.55],[-0.289,0.06],[-1.182,-0.025],[-0.751,-2.508],[-0.19,-2.631],[1.169,-1.224],[0.175,-0.485],[0.019,-0.253],[-0.331,-0.552],[-0.488,-0.971],[0.712,-1.647],[-0.489,-0.938],[-1.147,1.126],[-0.497,1.56],[-2.142,2.081],[0.267,0.202],[-0.903,1.229],[-0.404,1.397],[0.212,1.204],[-0.692,1.669]],"o":[[0.159,-2.346],[-2.855,-1.321],[-1.381,-0.341],[-1.336,-0.096],[-0.363,-0.322],[-0.716,-0.493],[-1.004,0.176],[-0.88,0.062],[-1.467,0.176],[-2.167,-0.678],[-1.136,3.209],[0.503,0.508],[0.288,0.962],[0.042,0.731],[0.395,0.884],[1.895,0.461],[0.325,-0.118],[0.791,-0.542],[2.739,0.061],[0.757,2.533],[0.142,1.983],[-0.029,0.462],[-0.103,0.226],[0.079,0.459],[0.506,0.847],[0.854,1.7],[-0.38,0.878],[1.211,2.318],[1.168,-1.145],[0.822,-2.581],[0.272,-0.265],[-2.053,-1.554],[0.854,-1.164],[0.337,-1.168],[-0.293,-1.672],[1.01,-2.439]],"v":[[19.249,-14.093],[15.564,-19.011],[6.298,-22.146],[2.251,-22.447],[-1.199,-23.955],[-2.209,-25.011],[-4.357,-25.479],[-7.133,-24.786],[-9.781,-24.791],[-13.175,-22.712],[-18.272,-19.666],[-15.904,-11.373],[-14.564,-9.709],[-14.689,-6.969],[-14.484,-5.337],[-12.335,-3.64],[-8.076,-5.049],[-7.159,-5.309],[-4.284,-6.195],[0.702,-1.962],[2.412,6.179],[-0.23,10.477],[-0.529,11.899],[-0.713,12.615],[-0.361,13.619],[1.496,15.765],[1.332,20.657],[0.994,23.312],[6.724,21.852],[9.297,17.712],[11.782,10.57],[11.673,9.726],[13.61,4.425],[15.636,0.587],[15.898,-3.059],[14.401,-8.367]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[278.395,94.719],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 76","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[312.573,277.94],"ix":2},"a":{"a":0,"k":[278.69,94.066],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 76","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.542,0.819],[1.284,0.409],[-1.514,2.473],[-0.613,0.612],[-0.94,0.061],[-0.104,0.013],[-1.036,0.817],[-0.413,0.384],[-0.232,0.621],[-0.192,0.382],[-0.221,0.678],[0.85,-0.004],[0.448,-0.219],[0.348,-0.276],[0.049,-0.024],[0.106,-0.226],[1.263,-0.455],[0.453,-0.298],[0.779,0.4],[0.519,0.122],[0.356,0.03],[0.198,1.355],[-0.164,0.325],[-0.471,0.751],[0.258,0.123],[0.973,0.455],[1.391,-0.981],[2.492,-4.636],[1.677,-2.321],[0.543,-2.344],[-0.635,-1.66],[-0.274,-0.426],[-1.776,-0.721],[-0.822,-1.255],[1.798,-2.073],[-0.241,-1.263],[-0.743,-0.979],[-0.384,-1.636],[0.807,-1.495],[-1.437,-1.338],[-1.297,-1.537],[-0.741,-0.72],[0.445,-2.258],[-4.601,-1.984],[0.295,3.2],[0.005,0.966],[-0.696,1.093],[-0.757,1.075],[0.769,2.415],[-0.78,1.982],[-0.728,1.13],[0.547,1.798],[1.317,0.443],[0.925,0.143],[0.788,0.316],[0.545,0.764],[1.567,1.099],[0.96,0.185],[0.388,0.126],[-0.443,1.566],[1.294,1.342],[1.086,1.113],[0.121,0.123],[-1.62,1.457],[-0.354,-0.605],[-1.606,-0.569],[-1.164,-0.57],[-0.717,0.311],[-1.067,1.468],[-1.835,0.521],[-1.217,0.518],[-0.307,0.515],[-0.904,1.651]],"o":[[-1.191,-0.633],[-1.611,-0.514],[-0.089,-0.624],[0.641,-0.639],[0.097,-0.014],[1.279,-0.165],[0.439,-0.346],[0.465,-0.433],[0.148,-0.398],[0.302,-0.602],[0.167,-0.875],[-0.492,0.237],[-0.529,0.397],[-0.046,0.036],[-0.125,0.176],[-0.543,1.161],[-0.259,0.436],[-0.738,0.484],[-0.413,-0.212],[-0.327,-0.076],[-1.178,-0.101],[-0.059,-0.399],[0.274,-1.238],[0.195,-0.312],[0.092,-0.975],[-1.816,-0.851],[-4.359,3.075],[-1.359,2.526],[-1.385,1.919],[-0.402,1.736],[0.169,0.443],[1.591,1.056],[1.335,0.541],[1.638,2.5],[-0.209,1.32],[0.568,1.103],[0.983,1.295],[0.44,1.879],[-0.905,1.679],[1.504,1.399],[0.671,0.795],[1.691,1.642],[-0.716,3.64],[2.527,1.089],[-0.083,-0.898],[-0.008,-1.298],[0.706,-1.111],[1.454,-2.065],[-0.573,-1.8],[0.494,-1.255],[0.924,-1.43],[-0.465,-1.532],[-0.888,-0.299],[-0.867,-0.132],[-0.948,-0.38],[-1.113,-1.56],[-0.804,-0.562],[-0.392,-0.076],[-3.028,-0.988],[0.522,-1.847],[-1.081,-1.118],[-0.121,-0.123],[-1.234,-0.912],[0.767,0.13],[0.883,1.508],[1.495,0.531],[0.703,0.345],[1.857,-0.805],[1.166,-1.607],[1.316,-0.375],[0.588,-0.25],[0.962,-1.618],[0.722,-1.317]],"v":[[14.764,-24.981],[10.809,-26.304],[5.918,-29.84],[6.659,-31.782],[9.119,-32.752],[9.41,-32.796],[13.285,-33.997],[14.458,-35.221],[15.542,-36.351],[15.888,-37.502],[16.903,-39.209],[16.098,-40.92],[14.68,-40.274],[13.29,-39.127],[13.145,-39.053],[12.797,-38.451],[10.691,-35.949],[9.678,-34.812],[7.338,-34.706],[6.28,-35.432],[5.066,-35.386],[2.015,-37.411],[2.204,-38.489],[3.949,-41.904],[3.714,-42.631],[2.429,-44.912],[-2.846,-43.205],[-12.557,-31.429],[-17.13,-24.281],[-20.474,-17.984],[-20.096,-12.826],[-19.413,-11.528],[-14.419,-8.761],[-10.876,-6.482],[-12.666,1.238],[-12.596,5.113],[-10.638,8.256],[-7.867,12.545],[-9.439,17.055],[-8.998,21.548],[-6.374,26.327],[-4.068,28.399],[-2.373,33.984],[-0.314,44.674],[5.973,40.989],[5.552,38.618],[6.617,35.324],[9.018,32.188],[10.193,25.861],[9.518,20.761],[11.833,17.416],[13.952,11.141],[9.549,8.411],[6.825,7.755],[3.812,7.456],[1.81,5.097],[-2.489,1.015],[-4.937,-0.144],[-6.35,-0.248],[-7.399,-3.438],[-8.944,-7.354],[-12.221,-10.675],[-12.585,-11.045],[-12.007,-14.597],[-10.126,-12.145],[-6.91,-9.456],[-3.616,-7.919],[-1.501,-7.928],[1.586,-12.227],[6.82,-15.12],[11.051,-15.559],[12.202,-16.926],[14.979,-21.86]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[238.473,88.501],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 77","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[111.938,259.931],"ix":2},"a":{"a":0,"k":[239.503,90.549],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 77","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.38,-1.525],[0.971,-1.312],[-0.089,-0.554],[-0.594,0.214],[-0.557,0.525],[-0.337,0.284],[-0.684,0.917],[-0.071,0.739],[-0.356,0.953],[-0.258,0.409],[1.378,1.003],[0.97,-0.321],[0.411,-1.082]],"o":[[-0.402,1.614],[-0.295,0.398],[0.148,0.926],[0.608,-0.218],[0.252,-0.327],[0.842,-0.71],[0.46,-0.615],[0.089,-0.928],[0.117,-0.31],[0.827,-1.309],[-0.934,-0.679],[-0.887,0.846],[-0.072,1.436]],"v":[[-1.208,0.106],[-3.785,4.229],[-5.103,6.389],[-2.799,7.369],[-1.003,5.917],[-0.134,4.995],[2.743,2.883],[3.226,1.165],[3.254,-1.821],[4.27,-3.154],[3.815,-6.904],[0.888,-7.262],[-0.93,-4.331]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[266.224,49.835],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 78","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[248.149,51.394],"ix":2},"a":{"a":0,"k":[266.107,49.819],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 78","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.142,0.352],[0.618,0.356],[0.318,2.197],[1.906,1.369],[0.294,0.882],[-1.094,0.618],[0.689,1.999],[1.704,3.817],[0.991,0.761],[1.427,2.527],[2.626,2.196],[1.893,-1.069],[0.036,-0.104],[1.957,0],[1.753,0.633],[1.11,-1.618],[0.3,-0.355],[1.373,0.167],[0.786,-0.437],[0.235,-0.592],[0.085,-0.581],[1.835,-0.466],[-0.1,-0.268],[-0.463,-0.492],[-1.26,-0.089],[0.058,-0.537],[1.179,-0.991],[0.495,-0.208],[-0.302,-0.934],[0.009,-0.206],[-4.067,-2.918],[-0.678,-0.455],[-2.622,-0.298],[-1.732,-1.481],[-0.88,-1.951],[-1.784,-0.45],[-1.947,-4.094],[0.03,-0.181],[-0.747,-0.529],[-0.675,-0.713],[0.027,-1.331],[-1.59,-2.675],[-1.791,0.939],[-0.19,0.374],[-0.014,0.277],[-0.52,0.473],[-0.094,0.292],[0.021,0.87],[-0.115,1.787]],"o":[[-0.303,-0.749],[-2.274,-1.312],[-0.347,-2.408],[-0.83,-0.597],[-0.645,-1.936],[2.088,-1.178],[-1.362,-3.952],[-0.489,-1.096],[-1.686,-1.294],[-1.447,-2.564],[-2.235,-1.869],[-0.124,0.07],[-1.71,0.94],[-2.008,0],[-1.825,-0.659],[-0.282,0.413],[-0.965,1.141],[-0.86,-0.104],[-0.55,0.306],[-0.231,0.584],[-0.262,1.808],[-0.263,0.068],[0.285,0.763],[1.247,0.058],[0.467,0.034],[-0.173,1.605],[-0.494,0.208],[-0.724,0.77],[-0.147,0.556],[4.656,0.484],[0.673,0.483],[4.185,2.807],[2.171,0.246],[1.746,1.493],[0.542,1.201],[2.211,0.559],[0.095,0.203],[0.476,0.698],[0.777,0.551],[1.013,1.068],[-0.057,2.962],[0.731,1.232],[0.42,-0.22],[0.014,-0.276],[0.072,-0.741],[0.127,-0.265],[0.291,-0.885],[-0.087,-3.433],[0.024,-0.374]],"v":[[28.518,16.92],[26.963,15.768],[24.671,10.917],[20.914,5.69],[17.632,3.263],[20.58,0.909],[21.791,-4.149],[17.132,-15.888],[15.186,-19.195],[10.374,-21.666],[8.246,-30.638],[0.586,-28.124],[0.361,-27.852],[-5.154,-26.031],[-10.186,-28.053],[-15.04,-26.487],[-15.72,-24.965],[-20.424,-23.434],[-23.022,-23.135],[-24.182,-21.836],[-24.243,-20.046],[-27.284,-16.503],[-27.66,-15.843],[-26.561,-14.298],[-22.814,-14.263],[-21.863,-13.312],[-23.688,-10.616],[-25.171,-9.992],[-25.804,-7.435],[-25.967,-6.417],[-10.987,-6.002],[-9.217,-4.214],[-4.21,-3.51],[0.82,-1.772],[4.039,3.157],[9.243,9.504],[15.16,9.545],[15.231,10.122],[16.912,11.953],[19.343,13.46],[20.302,17.269],[21.525,25.446],[25.956,28.256],[26.703,27.317],[26.746,26.487],[27.635,24.667],[28.039,23.858],[27.956,21.327],[28.567,18.023]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[298.802,71.123],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 79","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[415.736,161.807],"ix":2},"a":{"a":0,"k":[298.839,71.384],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 79","np":1,"cix":2,"bm":0,"ix":7,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.197,0.398],[0.45,-0.055],[0.674,0.447],[0.378,0.294],[-0.173,-0.257],[-0.482,-0.707],[-0.777,-0.291],[-0.304,1.424],[0.212,0.538]],"o":[[-0.754,0.411],[-0.874,0.107],[-0.444,-0.294],[0.184,0.192],[0.518,0.668],[0.455,0.668],[1.252,0.467],[0.126,-0.583],[-0.158,-0.405]],"v":[[2.192,-2.198],[0.055,-0.951],[-2.025,-1.616],[-3.255,-2.497],[-2.719,-1.831],[-1.603,0.527],[0.097,2.031],[3.128,0.66],[2.864,-1.01]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[309.069,83.866],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 80","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[467.898,224.989],"ix":2},"a":{"a":0,"k":[309.027,83.724],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 80","np":1,"cix":2,"bm":0,"ix":8,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Stand","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1435.256,611.852,0],"ix":2},"a":{"a":0,"k":[321.256,409.852,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[19.198,2.787],[0,0],[0,0]],"o":[[0,0],[-19.198,2.787],[0,0],[0,0],[0,0]],"v":[[28.948,4.404],[28.948,4.404],[-28.948,4.404],[-4.448,-7.191],[4.448,-7.191]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 7')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.442,191.238],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 70","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[285.702,773.676],"ix":2},"a":{"a":0,"k":[273.442,190.89],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 70","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[11.398,12.338],[1.348,-0.761],[0,-15.906],[11.714,-11.714],[16.307,-0.178],[11.855,11.666],[1.067,-1.115],[-17.637,-0.001],[-0.251,0.002],[-12.392,12.392],[0,17.798]],"o":[[-1.179,1.021],[10.842,11.539],[0,16.567],[-11.534,11.534],[-16.618,0.202],[-1.068,1.105],[12.563,12.391],[0.251,0],[17.52,-0.19],[12.586,-12.585],[0,-16.894]],"v":[[39.041,-55.893],[35.287,-53.164],[52.093,-10.741],[33.928,33.116],[-9.247,51.277],[-53.416,33.468],[-56.706,36.702],[-9.949,55.893],[-9.197,55.889],[37.188,36.377],[56.706,-10.741]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 7')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[282.664,103.613],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 71","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[332.919,326.821],"ix":2},"a":{"a":0,"k":[282.664,103.613],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 71","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.457,0],[0,2.456],[0,0],[2.457,0],[0,-2.457],[0,0]],"o":[[2.457,0],[0,0],[0,-2.457],[-2.457,0],[0,0],[0,2.456]],"v":[[0.001,18.7],[4.449,14.252],[4.449,-14.251],[0.001,-18.7],[-4.449,-14.251],[-4.449,14.252]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.442,174.414],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 72","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[285.701,689.319],"ix":2},"a":{"a":0,"k":[273.442,174.414],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 72","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,29.039],[29.039,0],[0,-29.039],[-29.038,0]],"o":[[0,-29.039],[-29.038,0],[0,29.039],[29.039,0]],"v":[[52.58,0],[-0.001,-52.579],[-52.58,0],[-0.001,52.579]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000020027,0.944999992847,0.957000017166,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[273.982,94.508],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 81","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[288.467,280.199],"ix":2},"a":{"a":0,"k":[273.982,94.508],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 81","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[-45.347,44.718],[-47.403,42.532],[45.347,-44.718],[47.403,-42.532]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.165000006557,0.168999999762,0.234999999404,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 2')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[277.019,97.385],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 82","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[304.018,294.932],"ix":2},"a":{"a":0,"k":[277.019,97.385],"ix":1},"s":{"a":0,"k":[512,512],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 7/Office management 2 Outlines - Group 82","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Plant","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[1704.537,891.319,0],"ix":2},"a":{"a":0,"k":[332.98,174.414,0],"ix":1},"s":{"a":0,"k":[512,512,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.235,0.449],[5.042,-0.657],[3.385,-3.127],[-1.221,2.376],[0.254,-0.243],[2.048,-5.95],[0.627,-3.484],[1.468,4.058],[5.91,5.06],[-1.339,-3.719],[-0.694,-5.568],[5.395,4.948],[4.28,-1.192],[-6.856,-6.129],[-0.374,-0.656],[3.565,0.597],[-0.133,-0.22],[-10.225,-1.43],[-5.802,-3.146],[-0.394,0.175],[0.347,0.281],[6.171,-1.692],[4.51,-3.097],[-1.589,1.524]],"o":[[-1.139,-2.17],[-4.139,0.538],[1.245,-2.419],[2.851,-5.55],[-4.5,4.322],[-1.158,3.364],[-0.018,0.102],[-2.713,-7.496],[-0.33,-0.282],[1.907,5.289],[-0.781,-2.217],[-5.702,-5.232],[-0.449,0.125],[2.466,2.204],[-1.027,-1.27],[-13.838,-2.321],[-0.023,0.315],[9.119,1.277],[0.059,0.19],[18.372,-8.145],[-4.657,-3.766],[-5.216,1.428],[1.148,-1.835],[17.399,-16.682]],"v":[[37.845,7.737],[23.596,-0.913],[5.788,11.531],[10.936,1.02],[16.581,-16.772],[6.174,-1.408],[3.213,12.174],[2.177,-3.315],[-12.379,-21.392],[-3.508,-2.113],[0.05,14.285],[-7.711,-3.301],[-30.471,-8.422],[-11.279,0.496],[-3.654,13.665],[-15.131,3.875],[-37.946,11.117],[-16.419,9.384],[1.293,21.322],[3.508,21.5],[34.549,16.204],[19.002,11.246],[4.606,19.103],[8.268,13.72]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.573000021542,0.808000033509,1,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 8')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[333.03,174.325],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 65","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Cloud 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[925.909,225.169,0],"ix":2},"a":{"a":0,"k":[180.904,44.306,0],"ix":1},"s":{"a":0,"k":[512,512,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.683,0.48],[0.089,0.164],[2.134,0.587],[0.19,0.076],[1.377,1.182],[2.563,-1.921],[5.042,-0.717],[-0.124,-4.231],[1.276,-3.149],[-0.38,-0.181],[-0.517,0],[0,0]],"o":[[-0.001,-0.152],[-1.013,-1.889],[-0.368,-0.102],[-2.211,-0.884],[-2.799,-2.403],[-0.355,-3.456],[-4.669,0.663],[-2.957,-0.322],[-0.208,0.514],[0.102,0.386],[0,0],[1.058,0]],"v":[[18.457,5.486],[18.339,5.01],[13.781,1.034],[10.672,0.909],[8.606,-1.094],[0.779,-1.444],[-5.396,-6.588],[-12.251,2.185],[-18.932,5.524],[-18.511,6.595],[-17.593,7.304],[17.907,7.304]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.944999964097,0.957000014361,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[180.976,44.011],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 83","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Cloud 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[708.913,52.106,0],"ix":2},"a":{"a":0,"k":[138.522,10.505,0],"ix":1},"s":{"a":0,"k":[512,512,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.065,0],[0,0],[5.536,-1.708],[0.728,1.782],[1.783,0.498],[2.551,-2.009],[4.813,-0.218],[0.128,-4.785],[2.64,-0.902],[1.685,-0.741],[2.02,-2.161],[-0.314,-1.06],[-0.76,-0.593],[0,0],[-0.316,0.32]],"o":[[0,0],[-4.554,-3.678],[0.52,-1.671],[-0.745,-1.82],[-3.26,-0.912],[-0.929,-4.4],[-5.097,0.233],[-2.677,-0.732],[-2.283,0.779],[-2.283,1.004],[-0.877,0.938],[0.433,0.834],[0,0],[0.452,0.273],[0.694,-0.474]],"v":[[33.522,7.939],[33.438,7.939],[17.636,4.793],[18.254,-0.223],[13.699,-3.604],[4.377,-2.074],[-5.093,-9.994],[-13.851,-0.387],[-22.129,-0.512],[-26.714,3.3],[-33.652,4.699],[-34.442,7.787],[-32.595,9.939],[32.787,9.939],[34.062,9.766]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.944999964097,0.957000014361,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[138.571,10.462],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 84","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Spruce_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[336.467,561.683,0],"ix":2},"a":{"a":0,"k":[418.467,533.683,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.439,0.582],[3.829,12.566],[0.382,-0.82],[-0.002,-0.303],[-0.053,0.086],[-1.877,1.033],[0.395,-1.044],[-0.725,0.477],[-1.039,0.925],[0.062,-1.413],[-0.606,0.566],[-0.98,1.474],[-0.655,-1.255],[-0.26,0.768],[-0.073,1.534],[-3.227,-0.547],[0.206,0.792],[0,0],[-2.823,-0.298]],"o":[[-7.919,-10.475],[-0.231,-0.757],[-5.57,11.935],[-0.595,1.075],[1.877,-1.033],[-0.181,1.094],[-0.258,0.811],[1.197,-0.788],[-0.062,1.413],[-0.041,0.902],[1.347,-1.26],[0.545,1.291],[0.421,0.808],[0.536,-1.587],[2.721,1.627],[0.738,0.124],[0,0],[2.372,1.262],[0.713,0.075]],"v":[[19.447,15.09],[1.76,-19.554],[-0.067,-19.793],[-19.291,15.707],[-17.387,16.406],[-11.757,13.305],[-12.608,16.513],[-11.139,17.642],[-7.813,15.083],[-8,19.323],[-6.293,20.03],[-2.852,15.984],[-1.076,19.805],[0.752,19.566],[1.635,15.029],[10.412,18.242],[11.642,17.012],[10.946,14.332],[18.584,16.595]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[86.217,23.519],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 7","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[436.902,196.111],"ix":2},"a":{"a":0,"k":[86.324,23.528],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 7","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.731,0.491],[3.306,8.604],[5.833,2.761],[3.15,-4.247],[1.748,-2.81],[2.292,-4.055],[-0.843,0.189],[-2.573,1.782],[0.603,-1.176],[-0.877,0.284],[-2.429,2.605],[0.463,-2.482],[-0.713,0.823],[-0.788,1.573],[-0.474,-1.45],[-0.38,0.837],[-0.476,1.762],[-1.678,-2.19],[0.133,1.116],[0,0],[-1.8,-1.077],[0.15,0.82],[0.066,0.988],[-3.123,-1.1],[0.064,0.625],[0.064,0.615],[-1.891,-0.798],[0.159,0.658],[0.493,2.049],[-3.871,-0.498]],"o":[[-7.761,-5.213],[-2.112,-5.493],[-4.874,-2.308],[-1.969,2.655],[-2.576,4.143],[-0.442,0.784],[3.201,-0.715],[-0.465,1.212],[-0.375,0.732],[3.529,-1.144],[0.208,2.395],[-0.166,0.892],[1.229,-1.419],[0.365,1.464],[0.242,0.741],[0.776,-1.707],[1.258,2.402],[0.515,0.674],[0,0],[1.498,1.374],[0.803,0.481],[-0.198,-1.088],[1.855,2.449],[0.572,0.201],[-0.063,-0.616],[1.291,1.338],[0.748,0.316],[-0.492,-2.049],[2.763,2.398],[1.07,0.137]],"v":[[30.429,15.777],[14.396,-5.959],[5.169,-20.335],[-8.16,-16.87],[-13.104,-8.191],[-30.717,15.137],[-29.588,16.606],[-21.013,12.887],[-22.607,16.464],[-21.477,17.934],[-12.956,11.998],[-13.328,19.146],[-11.656,20.119],[-8.665,15.69],[-7.418,20.053],[-5.59,20.293],[-3.731,15.124],[0.645,21.969],[2.508,21.465],[1.729,14.898],[6.625,18.552],[8.094,17.423],[7.706,14.355],[14.797,19.923],[16.063,18.958],[15.872,17.112],[20.6,20.261],[21.829,19.031],[20.353,12.884],[29.924,17.641]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.141000002623,0.513999998569,0.885999977589,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[85.887,46.78],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 8","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[434.798,313.677],"ix":2},"a":{"a":0,"k":[85.899,47.278],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 8","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.61,0.729],[5.627,10.498],[0.166,0.221],[3.979,1.923],[0.035,0.013],[2.708,0.49],[3.221,0.159],[2.029,-1.189],[1.132,-2.348],[1.854,-3.819],[7.842,-4.457],[-0.938,-0.08],[-3.38,1.162],[0.856,-0.78],[-0.888,0.119],[-3.155,2.371],[0.577,-1.206],[-0.892,0.305],[-2.992,1.93],[0.396,-1.686],[-0.763,0.503],[-1.799,2.578],[-1.366,-2.123],[-0.109,0.962],[-0.578,1.529],[-3.247,-1.333],[0.309,0.688],[0.394,1.509],[-4.246,-0.43],[0.403,0.594],[0.438,0.734],[-3.307,0.144],[0.453,0.66],[0.757,1.003],[-4.716,-1.32]],"o":[[-7.589,-9.064],[0.062,-0.231],[-2.637,-3.525],[-0.036,-0.017],[-2.576,-1.218],[-3.176,-0.575],[-2.232,-0.109],[-2.432,1.424],[-1.844,3.824],[-3.852,7.936],[-0.92,0.522],[3.726,0.322],[-0.69,0.871],[-0.824,0.751],[4.076,-0.551],[-0.391,1.139],[-0.345,0.721],[3.411,-1.162],[-0.237,1.671],[-0.19,0.8],[2.704,-1.78],[0.918,2.292],[0.569,0.886],[0.114,-1.653],[2.106,2.635],[0.906,0.372],[-0.673,-1.498],[3.323,2.377],[0.742,0.074],[-0.486,-0.719],[2.684,1.525],[0.703,-0.031],[-0.716,-1.04],[4.751,1.189],[1.024,0.287]],"v":[[42.192,17.846],[19.784,-10.67],[19.663,-11.368],[9.961,-19.429],[9.858,-19.456],[1.643,-21.837],[-7.978,-22.826],[-15.714,-22.543],[-20.243,-14.455],[-25.775,-2.982],[-41.882,15.934],[-41.378,17.797],[-30.832,16.536],[-33.137,18.998],[-32.163,20.67],[-21.362,16.276],[-22.787,19.74],[-21.658,21.208],[-12.099,16.6],[-13.038,21.573],[-11.57,22.701],[-4.882,16.228],[-1.5,22.799],[0.363,22.294],[1.403,17.52],[9.304,23.36],[10.434,21.891],[8.87,17.421],[20.108,21.588],[20.971,20.083],[19.594,17.903],[28.366,20.332],[29.229,18.827],[27.015,15.769],[41.219,19.517]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[87.513,72.574],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 9","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[442.964,439.493],"ix":2},"a":{"a":0,"k":[87.548,72.696],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 9","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.624,0.534],[3.818,8.406],[2.649,2.905],[3.556,0.907],[10.504,-0.151],[3.92,-7.223],[12.505,-8.825],[-1.049,-0.013],[-4.658,1.794],[0.95,-1.499],[-0.729,-0.024],[-4.244,3.173],[0.871,-2.21],[-0.678,0.259],[-3.579,3.504],[-0.49,-2.368],[-0.531,0.448],[-1.684,3.189],[-2.369,-2.781],[-0.316,0.401],[-0.993,3.099],[-4,-2.495],[0.603,0.826],[0.404,2.79],[-5.777,-0.998],[0.465,0.808],[0.358,2.039],[-4.703,-1.552],[0.211,0.708],[0.562,1.886],[-4.985,-0.638]],"o":[[-7.166,-6.122],[-1.604,-3.527],[-2.59,-2.84],[-10.173,-2.593],[-7.625,0.109],[-11.388,10.005],[-0.782,0.551],[5.081,0.062],[-0.618,1.616],[-0.421,0.664],[5.557,0.187],[-0.59,2.273],[-0.268,0.684],[4.742,-1.808],[0.872,2.221],[0.166,0.796],[2.811,-2.367],[2.371,2.781],[0.323,0.378],[2.076,-2.631],[2.896,3.638],[0.808,0.505],[-1.741,-2.388],[4.45,3.622],[0.83,0.144],[-1.111,-1.93],[3.89,2.952],[0.725,0.24],[-0.561,-1.887],[4.61,1.87],[0.852,0.109]],"v":[[56.458,17.148],[41.669,-5.824],[35.943,-16.744],[25.536,-21.238],[-5.727,-25.016],[-25.46,-16.306],[-56.299,16.744],[-55.794,18.607],[-41.305,16.009],[-43.633,20.643],[-42.77,22.148],[-28.501,16.587],[-30.68,23.292],[-29.449,24.522],[-17.063,16.634],[-15.048,23.436],[-13.376,23.877],[-6.71,15.638],[0.398,23.982],[1.813,23.982],[6.368,15.513],[16.639,24.662],[18.007,23.294],[14.811,15.614],[30.109,22.554],[31.24,21.086],[29.078,15.277],[41.9,22.011],[43.132,20.781],[41.447,15.12],[55.751,18.855]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.141000002623,0.513999998569,0.885999977589,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[82.474,103.779],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 10","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[417.796,592.781],"ix":2},"a":{"a":0,"k":[82.464,103.663],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 10","np":1,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.618,0.903],[5.623,13.358],[0.479,-0.014],[25.821,-2.676],[-0.775,-0.343],[10.123,-9.507],[-0.713,-0.188],[-5.283,2.574],[1.08,-2.641],[-0.659,0.129],[-4.333,4.642],[0.33,-2.518],[-0.595,0.298],[-3.132,3.103],[-0.414,-2.263],[-0.509,0.328],[-1.654,4.162],[-2.561,-3.278],[-0.051,0.766],[-0.245,3.67],[-3.397,-3.017],[-0.212,0.755],[-1.066,1.244],[-1.709,-1.403],[-1.906,-2.227],[0.032,0.834],[-0.308,2.913],[-4.13,-2.425],[-0.033,0.747],[-0.828,2.947],[-4.233,-2.566],[-0.009,0.746],[-1.094,3.16],[-3.667,-1.325],[0.333,0.714],[0.008,2.487],[-2.275,-1.066]],"o":[[-8.067,-11.781],[-0.17,-0.402],[-25.945,0.815],[-1.084,0.113],[-7.368,11.759],[-0.548,0.516],[5.869,1.548],[-1.282,2.54],[-0.316,0.77],[6.404,-1.256],[-0.329,2.518],[-0.106,0.815],[3.999,-2.002],[0.414,2.262],[0.114,0.625],[3.926,-2.523],[1.327,3.829],[0.582,0.745],[0.245,-3.67],[2.592,3.669],[0.555,0.493],[0.438,-1.56],[1.469,-1.713],[2.264,1.859],[0.565,0.659],[-0.109,-2.964],[2.938,3.676],[0.677,0.397],[0.131,-3.112],[3.066,3.801],[0.657,0.398],[0.039,-3.394],[3.353,1.964],[0.9,0.325],[-1.131,-2.419],[1.922,1.499],[0.818,0.385]],"v":[[65.141,13.229],[40.057,-25.766],[39.092,-26.499],[-38.475,-17.029],[-38.943,-15.103],[-65.21,16.852],[-64.768,18.523],[-47.809,16.954],[-51.35,24.718],[-50.122,25.949],[-33.913,16.968],[-34.902,24.52],[-33.396,25.383],[-22.788,17.788],[-21.548,24.575],[-20.079,25.173],[-11.704,15.126],[-5.906,25.699],[-4.199,24.992],[-3.465,13.982],[5.475,23.985],[7.146,23.544],[9.008,18.896],[12.044,18.847],[18.039,25.854],[19.746,25.148],[20.039,16.39],[30.565,25.501],[32.072,24.637],[33.524,15.627],[44.393,25.126],[45.897,24.261],[47.591,14.531],[58.067,19.45],[59.199,17.981],[57.561,10.781],[63.772,14.597]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.250999987125,0.575999975204,0.964999973774,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 3')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[82.692,137.506],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 11","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[418.467,759.438],"ix":2},"a":{"a":0,"k":[82.599,137.331],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 11","np":1,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.802,10.04],[0.301,2.281],[0.895,0.716],[1.422,-0.02],[0,0],[0.094,-0.038],[0.068,-0.001],[-0.475,-0.006],[2.609,-13.538],[-0.261,-0.018],[-7.03,0.785],[0.043,0.257]],"o":[[-0.184,-2.293],[-0.144,-1.091],[-1.201,-0.959],[0,0],[-0.098,-0.037],[-0.069,0.001],[-0.48,0.007],[0.215,13.784],[-0.043,0.229],[7.365,0.507],[0.261,-0.029],[-1.65,-9.935]],"v":[[7.054,-10.219],[6.542,-17.132],[5.194,-20.059],[0.796,-20.783],[-6.459,-20.688],[-6.762,-20.684],[-6.966,-20.68],[-6.975,-19.929],[-10.749,20.032],[-10.384,20.51],[10.386,20.232],[10.749,19.756]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.24699999392,0.259000003338,0.379999995232,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 7')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[82.463,159.349],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 12","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[417.794,868.539],"ix":2},"a":{"a":0,"k":[82.463,159.371],"ix":1},"s":{"a":0,"k":[495,495],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Layer 8/Summer camping 20 Outlines - Group 12","np":1,"cix":2,"bm":0,"ix":6,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Floor","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[942.484,1007.751,0],"ix":2},"a":{"a":0,"k":[184.141,197.155,0],"ix":1},"s":{"a":0,"k":[512,512,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.593],[101.561,0],[0,2.593],[-101.56,0]],"o":[[0,2.593],[-101.56,0],[0,-2.593],[101.561,0]],"v":[[183.892,-0.001],[-0.001,4.694],[-183.892,-0.001],[-0.001,-4.694]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.929000016755,0.944999964097,0.957000014361,1],"ix":4,"x":"var $bm_rt;\ntry {\n $bm_rt = thisComp.layer('Controller')('Effects')('Color 6')('ADBE Color Control-0001');\n} catch (e) {\n $bm_rt = value;\n}"},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[184.141,197.155],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 85","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":0,"nm":"Woman (scene 3)","refId":"comp_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[747,667.5,0],"ix":2},"a":{"a":0,"k":[400,450,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":800,"h":900,"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":0,"nm":"BG elements (scene 3)","refId":"comp_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[957.5,538,0],"ix":2},"a":{"a":0,"k":[949.5,516,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"w":1899,"h":1032,"ip":0,"op":120,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/raw/sign_up_animation.json b/android/HowAboutTrip/app/src/main/res/raw/sign_up_animation.json new file mode 100644 index 00000000..f86e393f --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/raw/sign_up_animation.json @@ -0,0 +1 @@ +{"v":"5.7.12","fr":30,"ip":0,"op":90,"w":252,"h":252,"nm":"main","ddd":0,"assets":[],"fonts":{"list":[{"fName":"Montserrat-Bold","fFamily":"Montserrat","fStyle":"Bold","ascent":74.1989135742188}]},"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 3","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":100,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":130,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":145,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":155,"s":[100]},{"t":160,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.63],[2.566,0],[0,2.63],[-2.566,0]],"o":[[0,2.63],[-2.566,0],[0,-2.63],[2.566,0]],"v":[[-44.259,-59.35],[-48.904,-54.589],[-53.55,-59.35],[-48.904,-64.111]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.890196084976,0.945098042488,0.984313726425,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 2","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":100,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":110,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":130,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":145,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":155,"s":[0]},{"t":160,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.63],[2.566,0],[0,2.63],[-2.566,0]],"o":[[0,2.63],[-2.566,0],[0,-2.63],[2.566,0]],"v":[[-56.928,-59.35],[-61.573,-54.589],[-66.219,-59.35],[-61.573,-64.111]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.890196084976,0.945098042488,0.984313726425,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Layer 1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":75,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":100,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":110,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":120,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":130,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":140,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":145,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":155,"s":[100]},{"t":160,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-2.63],[2.566,0],[0,2.63],[-2.566,0]],"o":[[0,2.63],[-2.566,0],[0,-2.63],[2.566,0]],"v":[[-69.597,-59.35],[-74.242,-54.589],[-78.888,-59.35],[-74.242,-64.111]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.890196084976,0.945098042488,0.984313726425,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Layer 7","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-3.088],[3.088,0],[0,3.088],[-3.088,0]],"o":[[0,3.088],[-3.088,0],[0,-3.088],[3.088,0]],"v":[[-87.118,-9.954],[-92.71,-4.362],[-98.302,-9.954],[-92.71,-15.546]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Layer 5","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[137.345,174.696,0],"to":[-9.333,-7.333,0],"ti":[9.333,7.333,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":15,"s":[81.345,130.696,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[81.345,130.696,0],"to":[11.833,6.5,0],"ti":[-11.833,-6.5,0]},{"t":27,"s":[152.345,169.696,0]}],"ix":2,"l":2},"a":{"a":0,"k":[10.345,17.696,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":15,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":18,"s":[110,110,100]},{"t":21,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.233,-1.568],[0,0],[-2.153,3.171],[0,0],[0,0],[-2.174,1.654],[0,0]],"o":[[-1.059,0.884],[0,0],[0.564,3.792],[0,0],[0,0],[1.507,2.278],[0,0],[0,0]],"v":[[11.869,18.693],[10.393,22.526],[16.754,65.315],[24.398,67.06],[30.261,58.425],[46.769,83.381],[53.569,84.534],[59.311,80.165]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.240553642722,0.28746511422,0.309803921569,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[1.794,2.059],[0,0],[0,0],[3.504,1.555],[0,0],[1.158,-0.966],[0,0]],"o":[[2.174,-1.654],[0,0],[0,0],[3.631,-1.23],[0,0],[-1.584,-0.703],[0,0],[0,0]],"v":[[64.511,76.208],[65.213,69.348],[45.563,46.783],[55.448,43.434],[55.805,35.602],[16.262,18.06],[11.869,18.693],[59.311,80.165]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.270588235294,0.352941176471,0.392156892664,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":5,"nm":"|||||||||||||","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[77.157,143.452,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"s":26.6488609313965,"f":"Montserrat-Bold","t":"|","ca":0,"j":0,"tr":0,"lh":31.9786331176758,"ls":0,"fc":[0.071,0.2,0.294]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":17,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"t":23,"s":[0]}],"ix":1},"r":1},"a":{"o":{"a":0,"k":0,"ix":9}}}]},"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":5,"nm":"ABCD36","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[77.157,143.452,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"t":{"d":{"k":[{"s":{"s":26.6488609313965,"f":"Montserrat-Bold","t":"FB5D01","ca":0,"j":0,"tr":0,"lh":31.9786331176758,"ls":0,"fc":[0.071,0.2,0.294]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[{"nm":"Animator 1","s":{"t":0,"xe":{"a":0,"k":0,"ix":7},"ne":{"a":0,"k":0,"ix":8},"a":{"a":0,"k":100,"ix":4},"b":1,"rn":0,"sh":1,"s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[0]},{"t":68,"s":[100]}],"ix":1},"r":1},"a":{"o":{"a":0,"k":0,"ix":9}}}]},"ip":23,"op":173,"st":23,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Layer 6","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.096,11.694],[1.436,3.137],[6.626,7.822],[34.572,0],[0,-62.394],[-62.394,0],[-20.723,24.463],[-4.608,13.156],[0,13.093]],"o":[[-1.15,-3.283],[-4.321,-9.433],[-20.723,-24.463],[-62.394,0],[0,62.394],[34.572,0],[8.829,-10.423],[4.096,-11.694],[0,-13.093]],"v":[[106.625,-37.348],[102.739,-46.977],[86.221,-72.965],[0,-112.974],[-112.974,0],[0,112.974],[86.221,72.965],[106.625,37.348],[112.974,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[3.458,0],[0,0],[0,-3.457],[0,0],[-3.458,0],[0,0],[0,3.457],[0,0]],"o":[[0,0],[-3.458,0],[0,0],[0,3.457],[0,0],[3.458,0],[0,0],[0,-3.457]],"v":[[64.802,-17.804],[-53.181,-17.804],[-59.451,-11.534],[-59.451,25.836],[-53.181,32.106],[64.802,32.106],[71.071,25.836],[71.071,-11.534]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[2.623,0],[0,0],[0,2.623],[0,0],[-2.623,0],[0,0],[0,-2.623],[0,0]],"o":[[0,0],[-2.623,0],[0,0],[0,-2.623],[0,0],[2.623,0],[0,0],[0,2.623]],"v":[[70.881,36.666],[-59.261,36.666],[-64.011,31.916],[-64.011,-17.614],[-59.261,-22.364],[70.881,-22.364],[75.631,-17.614],[75.631,31.916]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.901960790157,0.96862745285,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.111,0],[0,0],[0,-6.263],[0,0],[0,0],[0,0]],"o":[[0,0],[-6.111,0],[0,0],[0,0],[0,0],[0,-6.263]],"v":[[95.561,-72.965],[-80.408,-72.965],[-91.472,-61.625],[-91.472,-46.977],[106.625,-46.977],[106.625,-61.625]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.070588238537,0.20000000298,0.29411765933,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-6.111,0],[0,0],[0,6.263],[0,0],[0,0]],"o":[[0,6.263],[0,0],[6.111,0],[0,0],[0,0],[0,0]],"v":[[-91.472,61.625],[-80.408,72.965],[95.561,72.965],[106.625,61.625],[106.625,-46.977],[-91.472,-46.977]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.309803932905,0.674509823322,0.972549021244,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"++++","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[101.537,37.094,0],"ix":2,"l":2},"a":{"a":0,"k":[-24.463,-88.906,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":20,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":40,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":50,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":60,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":70,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":80,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":90,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":100,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":110,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":120,"s":[0,0,100]},{"t":130,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.596,0],[0,0],[0,0],[0,-3.194],[0,0],[0,0],[0,-1.243],[-1.596,0],[0,0],[0,0],[-1.243,0],[0,1.596],[0,0],[0,0],[0,1.243]],"o":[[0,0],[0,0],[0,-3.19],[0,0],[0,0],[-1.596,0],[0,1.241],[0,0],[0,0],[0,1.596],[1.242,0],[0,0],[0,0],[1.596,0],[0,-1.241]],"v":[[-18.355,-91.424],[-21.962,-91.424],[-21.962,-95.032],[-26.961,-95.032],[-26.961,-91.424],[-30.57,-91.424],[-33.002,-88.923],[-30.57,-86.425],[-26.961,-86.425],[-26.961,-82.817],[-24.461,-80.386],[-21.962,-82.817],[-21.962,-86.425],[-18.355,-86.425],[-15.923,-88.926]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.533333361149,0.482352942228,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Ellipes","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-6.663],[6.663,0],[0,6.663],[-6.663,0]],"o":[[0,6.663],[-6.663,0],[0,-6.663],[6.663,0]],"v":[[61.359,-72.965],[49.294,-60.9],[37.229,-72.965],[49.294,-85.03]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0.96862745285,0.800000011921,0.498039215803,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":22,"s":[1,0.533333361149,0.482352972031,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":45,"s":[0.309803932905,0.674509823322,0.972549080849,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":68,"s":[0.917647123337,0.592156887054,0.211764723063,1]},{"t":90,"s":[0.890196084976,0.945098042488,0.984313726425,1]}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"gear 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[15]},{"t":90,"s":[375]}],"ix":10},"p":{"a":0,"k":[145.154,237.027,0],"ix":2,"l":2},"a":{"a":0,"k":[13.654,69.027,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.015,-4.165],[-4.086,0.012],[0.056,4.297],[3.946,-0.013]],"o":[[0.015,4.159],[4.26,-0.013],[-0.05,-3.882],[-4.209,0.014]],"v":[[6.309,68.847],[13.716,76.338],[21.27,68.594],[13.812,61.371]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.816,-0.082],[0.252,1.736],[1.616,0.504],[1.027,-1.139],[1.39,1.123],[0.807,0.724],[-0.984,1.258],[0.887,1.526],[1.285,-0.022],[0.187,2.108],[0.112,0.905],[-1.478,0.195],[-0.448,1.735],[1.116,1.001],[-1.078,1.329],[-0.717,0.813],[-1.311,-1.002],[-1.369,0.721],[0.114,1.915],[-1.379,0.196],[-1.222,0.057],[-0.197,-1.489],[-1.438,-0.466],[-1.261,1.439],[-1.035,-0.768],[-0.967,-0.885],[0.899,-1.15],[-0.718,-1.41],[-1.675,0.116],[-0.226,-1.317],[-0.023,-1.312],[1.329,-0.175],[0.481,-1.717],[-1.139,-1.012],[1.12,-1.417],[0.708,-0.821],[1.194,0.933],[1.529,-0.807],[0.033,-1.554],[1.99,-0.296]],"o":[[-1.953,0.057],[-0.234,-1.609],[-1.53,-0.478],[-1.208,1.339],[-0.844,-0.681],[-1.211,-1.087],[1.061,-1.356],[-0.709,-1.219],[-2.1,0.036],[-0.08,-0.908],[-0.186,-1.503],[1.801,-0.238],[0.374,-1.449],[-1.288,-1.154],[0.683,-0.842],[1.1,-1.246],[1.318,1.006],[1.533,-0.807],[-0.081,-1.352],[1.212,-0.172],[1.52,-0.071],[0.204,1.543],[1.646,0.533],[0.83,-0.947],[1.053,0.781],[1.035,0.948],[-0.994,1.272],[0.759,1.49],[1.301,-0.09],[0.222,1.293],[0.024,1.373],[-1.793,0.236],[-0.4,1.427],[1.352,1.201],[-0.672,0.851],[-0.989,1.147],[-1.363,-1.065],[-1.415,0.748],[-0.042,1.98],[-1.014,0.151]],"v":[[14.124,90.48],[10.971,88.198],[8.449,85.359],[5,86.195],[1.14,86.403],[-1.356,84.316],[-1.851,80.753],[-1.66,76.824],[-4.366,75.217],[-7.473,72.329],[-7.762,69.608],[-5.775,66.587],[-2.834,63.776],[-3.652,60.439],[-3.837,56.646],[-1.757,54.145],[1.788,53.61],[5.389,53.635],[7.734,50.206],[9.95,47.932],[13.607,47.576],[16.359,49.684],[18.75,52.221],[22.834,51.484],[25.87,51.393],[28.91,53.893],[29.256,57.199],[29.126,60.814],[32.325,62.864],[34.725,64.97],[35.098,68.883],[33.206,71.442],[30.236,74.256],[30.971,77.557],[31.18,81.417],[29.107,83.924],[25.604,84.454],[21.682,84.28],[19.669,87.329],[16.981,90.186]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"gear","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":90,"s":[360]}],"ix":10},"p":{"a":0,"k":[139.654,195.027,0],"ix":2,"l":2},"a":{"a":0,"k":[13.654,69.027,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.015,-4.165],[-4.086,0.012],[0.056,4.297],[3.946,-0.013]],"o":[[0.015,4.159],[4.26,-0.013],[-0.05,-3.882],[-4.209,0.014]],"v":[[6.309,68.847],[13.716,76.338],[21.27,68.594],[13.812,61.371]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.816,-0.082],[0.252,1.736],[1.616,0.504],[1.027,-1.139],[1.39,1.123],[0.807,0.724],[-0.984,1.258],[0.887,1.526],[1.285,-0.022],[0.187,2.108],[0.112,0.905],[-1.478,0.195],[-0.448,1.735],[1.116,1.001],[-1.078,1.329],[-0.717,0.813],[-1.311,-1.002],[-1.369,0.721],[0.114,1.915],[-1.379,0.196],[-1.222,0.057],[-0.197,-1.489],[-1.438,-0.466],[-1.261,1.439],[-1.035,-0.768],[-0.967,-0.885],[0.899,-1.15],[-0.718,-1.41],[-1.675,0.116],[-0.226,-1.317],[-0.023,-1.312],[1.329,-0.175],[0.481,-1.717],[-1.139,-1.012],[1.12,-1.417],[0.708,-0.821],[1.194,0.933],[1.529,-0.807],[0.033,-1.554],[1.99,-0.296]],"o":[[-1.953,0.057],[-0.234,-1.609],[-1.53,-0.478],[-1.208,1.339],[-0.844,-0.681],[-1.211,-1.087],[1.061,-1.356],[-0.709,-1.219],[-2.1,0.036],[-0.08,-0.908],[-0.186,-1.503],[1.801,-0.238],[0.374,-1.449],[-1.288,-1.154],[0.683,-0.842],[1.1,-1.246],[1.318,1.006],[1.533,-0.807],[-0.081,-1.352],[1.212,-0.172],[1.52,-0.071],[0.204,1.543],[1.646,0.533],[0.83,-0.947],[1.053,0.781],[1.035,0.948],[-0.994,1.272],[0.759,1.49],[1.301,-0.09],[0.222,1.293],[0.024,1.373],[-1.793,0.236],[-0.4,1.427],[1.352,1.201],[-0.672,0.851],[-0.989,1.147],[-1.363,-1.065],[-1.415,0.748],[-0.042,1.98],[-1.014,0.151]],"v":[[14.124,90.48],[10.971,88.198],[8.449,85.359],[5,86.195],[1.14,86.403],[-1.356,84.316],[-1.851,80.753],[-1.66,76.824],[-4.366,75.217],[-7.473,72.329],[-7.762,69.608],[-5.775,66.587],[-2.834,63.776],[-3.652,60.439],[-3.837,56.646],[-1.757,54.145],[1.788,53.61],[5.389,53.635],[7.734,50.206],[9.95,47.932],[13.607,47.576],[16.359,49.684],[18.75,52.221],[22.834,51.484],[25.87,51.393],[28.91,53.893],[29.256,57.199],[29.126,60.814],[32.325,62.864],[34.725,64.97],[35.098,68.883],[33.206,71.442],[30.236,74.256],[30.971,77.557],[31.18,81.417],[29.107,83.924],[25.604,84.454],[21.682,84.28],[19.669,87.329],[16.981,90.186]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Gear_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":90,"s":[-360]}],"ix":10},"p":{"a":0,"k":[108.05,216.774,0],"ix":2,"l":2},"a":{"a":0,"k":[-17.95,90.774,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-3.375,0.047],[0.004,3.262],[3.428,-0.055],[-0.026,-3.347]],"o":[[3.291,-0.046],[-0.005,-3.425],[-3.372,0.055],[0.026,3.353]],"v":[[-17.807,96.659],[-11.814,90.628],[-17.999,84.557],[-23.99,90.648]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0.001,-0.013],[0.449,0.07],[0.1,1.364],[1.115,0.469],[1.008,-0.86],[1.077,0.951],[0.49,0.555],[-0.993,1.128],[0.681,0.922],[0.037,0.193],[0.902,0.059],[0.022,1.882],[0,0.428],[-1.948,0.144],[-0.319,0.683],[1.204,1.287],[-0.53,0.797],[-0.604,0.562],[-1.335,-1.135],[-0.634,0.428],[-0.081,0.024],[-0.118,1.812],[-0.975,0.055],[-0.937,-0.064],[-0.089,-1.403],[-0.851,-0.155],[-0.094,-0.061],[-1.225,0.991],[-0.854,-0.791],[-0.512,-0.61],[0.958,-1.111],[-0.263,-0.798],[-1.671,-0.135],[-0.132,-0.936],[0.144,-0.961],[1.527,-0.089],[0.335,-0.673],[-1.241,-1.372],[0.545,-0.766],[0.683,-0.602],[1.09,0.978],[0.986,-0.349],[0.102,-1.568],[1.411,-0.142],[0.342,-0.003]],"o":[[-0.454,-0.037],[-1.374,-0.214],[-0.086,-1.17],[-1.164,-0.49],[-1.085,0.925],[-0.555,-0.49],[-0.994,-1.127],[0.723,-0.821],[-0.115,-0.155],[-0.166,-0.853],[-1.86,-0.123],[-0.005,-0.428],[-0.002,-1.986],[0.861,-0.064],[0.64,-1.372],[-0.652,-0.697],[0.463,-0.696],[1.283,-1.193],[0.603,0.513],[0.07,-0.048],[1.449,-0.428],[0.059,-0.909],[0.938,-0.053],[1.271,0.087],[0.057,0.9],[0.11,0.02],[1.164,0.756],[0.898,-0.726],[0.585,0.542],[0.873,1.04],[-0.574,0.665],[0.475,1.441],[0.95,0.077],[0.135,0.959],[-0.212,1.411],[-0.781,0.045],[-0.71,1.427],[0.602,0.666],[-0.534,0.75],[-1.092,0.962],[-0.847,-0.759],[-1.337,0.474],[-0.079,1.217],[-0.339,0.034],[-0.001,0.013]],"v":[[-18.099,108.227],[-19.46,108.111],[-21.714,105.831],[-23.389,103.638],[-26.336,103.939],[-29.589,103.753],[-31.162,102.179],[-31.252,98.698],[-30.959,96.238],[-31.178,95.683],[-32.713,94.43],[-35.4,91.464],[-35.407,90.18],[-32.673,87.073],[-31.273,85.914],[-31.533,82.079],[-31.394,79.606],[-29.701,77.796],[-25.888,77.669],[-24.143,77.85],[-23.915,77.734],[-21.229,74.878],[-19.28,73.366],[-16.457,73.365],[-14.218,75.753],[-12.943,77.278],[-12.624,77.398],[-9.205,77.297],[-6.317,77.74],[-4.656,79.465],[-4.738,82.807],[-4.992,84.792],[-2.193,87.3],[-0.6,89.205],[-0.595,92.107],[-3.171,94.371],[-4.59,95.433],[-4.396,99.474],[-4.57,101.866],[-6.459,103.843],[-9.868,103.917],[-12.364,103.623],[-14.611,106.225],[-17.07,108.175],[-18.096,108.188]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"BG","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[126,126,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-62.394],[62.394,0],[0,62.394],[-62.394,0]],"o":[[0,62.394],[-62.394,0],[0,-62.394],[62.394,0]],"v":[[112.974,0],[0,112.974],[-112.974,0],[0,-112.974]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.890196084976,0.945098042488,0.984313726425,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":150,"st":0,"bm":0}],"markers":[],"chars":[{"ch":"F","size":26.6488609313965,"style":"Bold","w":60.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[6.848,-70.596],[6.848,0],[25.278,0],[25.278,-25.479],[56.9,-25.479],[56.9,-40.182],[25.278,-40.182],[25.278,-55.893],[59.518,-55.893],[59.619,-70.596]],"c":true},"ix":2},"nm":"F","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"F","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"B","size":26.6488609313965,"style":"Bold","w":71.6,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[16.013,0],[0,0],[0,0],[0,0],[0,12.085],[8.963,1.813],[0,7.352]],"o":[[0,0],[0,0],[0,0],[16.617,0],[0,-9.064],[7.452,-2.014],[0,-10.876]],"v":[[40.384,-70.596],[6.848,-70.596],[6.848,0],[41.693,0],[68.884,-19.638],[54.382,-37.463],[66.568,-52.771]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[5.136,0],[0,0],[0,0],[0,0],[0,-4.23]],"o":[[0,0],[0,0],[0,0],[5.136,-0.101],[0,4.431]],"v":[[39.377,-42.499],[25.278,-42.499],[25.278,-56.195],[39.377,-56.195],[47.635,-49.548]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[6.445,0],[0,0],[0,0],[0,0],[0,-4.431]],"o":[[0,0],[0,0],[0,0],[6.445,-0.101],[0,4.834]],"v":[[39.377,-14.301],[25.278,-14.301],[25.278,-29.105],[39.377,-29.105],[49.85,-21.954]],"c":true},"ix":2},"nm":"B","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"B","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"5","size":26.6488609313965,"style":"Bold","w":61.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.511,-0.101],[0,0],[0,0],[0,0],[0,0],[0,0],[-3.726,0],[0,-5.338],[7.15,0],[5.539,5.841],[0,0],[-9.467,0],[0,14.905],[16.617,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[4.431,-0.302],[7.05,0],[0,5.74],[-6.445,0],[0,0],[7.654,5.338],[17.422,0],[0,-12.589],[-1.41,0]],"v":[[24.774,-45.016],[24.774,-55.792],[55.289,-55.792],[55.289,-70.697],[7.855,-70.697],[7.855,-30.414],[27.594,-30.817],[38.672,-22.458],[27.493,-13.495],[9.064,-22.357],[1.712,-8.057],[29.709,0.806],[58.511,-23.767],[31.119,-45.117]],"c":true},"ix":2},"nm":"5","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"5","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"D","size":26.6488609313965,"style":"Bold","w":76.6,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.357,0],[0,0],[0,0],[0,0],[0,21.048]],"o":[[0,0],[0,0],[0,0],[22.861,0],[0,-20.947]],"v":[[37.262,-70.596],[6.848,-70.596],[6.848,0],[36.557,0],[74.826,-35.349]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[10.675,0],[0,0],[0,0],[0,0],[0,-12.186]],"o":[[0,0],[0,0],[0,0],[11.179,0],[0,12.085]],"v":[[37.766,-14.905],[25.278,-14.905],[25.278,-55.591],[36.859,-55.591],[55.994,-35.147]],"c":true},"ix":2},"nm":"D","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"D","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"0","size":26.6488609313965,"style":"Bold","w":61.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[17.725,0],[0,-23.062],[-17.926,0],[0,23.062]],"o":[[-17.926,0],[0,23.062],[17.725,0],[0,-23.062]],"v":[[31.119,-71.1],[3.424,-35.349],[31.119,0.504],[58.713,-35.349]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-7.15,0],[0,-16.214],[7.05,0],[0,16.113]],"o":[[7.05,0],[0,16.113],[-7.15,0],[0,-16.214]],"v":[[31.119,-58.209],[41.089,-35.349],[31.119,-12.488],[21.048,-35.349]],"c":true},"ix":2},"nm":"0","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"0","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"1","size":26.6488609313965,"style":"Bold","w":61.7,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[9.265,-70.596],[9.265,-55.49],[22.861,-55.49],[22.861,0],[41.29,0],[41.29,-70.596]],"c":true},"ix":2},"nm":"1","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"},{"ch":"|","size":26.6488609313965,"style":"Bold","w":30.2,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[8.56,-80.768],[8.56,10.977],[21.854,10.977],[21.854,-80.768]],"c":true},"ix":2},"nm":"|","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"|","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Montserrat"}]} \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/values-night/themes.xml b/android/HowAboutTrip/app/src/main/res/values-night/themes.xml new file mode 100644 index 00000000..95d02503 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/values-night/themes.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/values/colors.xml b/android/HowAboutTrip/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..d2a60bbd --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #FF000000 + #FFFFFFFF + #FFD9D9D9 + #FFCF2727 + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/values/strings.xml b/android/HowAboutTrip/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..9cc5ec15 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/values/strings.xml @@ -0,0 +1,35 @@ + + HowAboutTrip + HowAboutTripì—ì„œ\n여행 계íšì„ 세우는 ê±´ 어떤가요? + google login + How About Trip + ìž…ë ¥ 완료 + How About Tripì„ ì²˜ìŒ ì‚¬ìš©í•˜ì‹œëŠ” êµ°ìš”! + ì‹ ê·œ 가입ìžëŠ” 추가 정보를 입력하셔야 합니다.\nì´ë¦„ì€ ë³¸ì¸ ì‹¤ëª…ì„ ìž…ë ¥í•˜ì…”ì•¼ 합니다. + ì´ë¦„ + ë³¸ì¸ ëª…ì˜ ì´ë¦„으로 작성 + 전화번호 + 010-1234-5678 + ìƒë…„ ì›”ì¼ + ìž…ë ¥ + 미입력 + 2024 + 01 + SUN + MON + TUE + WED + THU + FRI + SAT + 확정 + 초기화 + saveCheck + í•­ê³µ 숙박 + ì¼ì • 조회 + 사진 ê¸°ë¡ + My + + Hello blank fragment + Bearer %1$s + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/values/themes.xml b/android/HowAboutTrip/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..eb54e315 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/values/themes.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/xml/backup_rules.xml b/android/HowAboutTrip/app/src/main/res/xml/backup_rules.xml new file mode 100644 index 00000000..fa0f996d --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/xml/data_extraction_rules.xml b/android/HowAboutTrip/app/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..9ee9997b --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/main/res/xml/network_security_config.xml b/android/HowAboutTrip/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..176d19a4 --- /dev/null +++ b/android/HowAboutTrip/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/HowAboutTrip/app/src/test/java/com/project/how/ExampleUnitTest.kt b/android/HowAboutTrip/app/src/test/java/com/project/how/ExampleUnitTest.kt new file mode 100644 index 00000000..d6e1d653 --- /dev/null +++ b/android/HowAboutTrip/app/src/test/java/com/project/how/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.project.how + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/android/HowAboutTrip/build.gradle.kts b/android/HowAboutTrip/build.gradle.kts new file mode 100644 index 00000000..817165d5 --- /dev/null +++ b/android/HowAboutTrip/build.gradle.kts @@ -0,0 +1,12 @@ +buildscript { + dependencies { + classpath("com.google.gms:google-services:4.4.0") + } +} +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + id("com.android.application") version "8.2.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.10" apply false + id("com.google.gms.google-services") version "4.4.0" apply false + kotlin("kapt") version "1.9.22" +} \ No newline at end of file diff --git a/android/HowAboutTrip/gradle.properties b/android/HowAboutTrip/gradle.properties new file mode 100644 index 00000000..3c5031eb --- /dev/null +++ b/android/HowAboutTrip/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.jar b/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.properties b/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..58e3c691 --- /dev/null +++ b/android/HowAboutTrip/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Jan 07 22:58:56 KST 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/android/HowAboutTrip/gradlew b/android/HowAboutTrip/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/android/HowAboutTrip/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/android/HowAboutTrip/gradlew.bat b/android/HowAboutTrip/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/android/HowAboutTrip/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/HowAboutTrip/settings.gradle.kts b/android/HowAboutTrip/settings.gradle.kts new file mode 100644 index 00000000..15ecec1d --- /dev/null +++ b/android/HowAboutTrip/settings.gradle.kts @@ -0,0 +1,18 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "HowAboutTrip" +include(":app") + \ No newline at end of file diff --git a/android/README.md b/android/README.md new file mode 100644 index 00000000..1277c26a --- /dev/null +++ b/android/README.md @@ -0,0 +1,25 @@ +# HowAboutTrip 여행어때 (Android) +### Android 개발 환경 +- Lanaguages : Kotlin +- Tool : Android Studio + - Version : Hedgehog + - Java : Java 8 + - Android Gradle Plugin : 8.2.1 + - Gradle Version : 8.2 +- - - +### ì‚¬ìš©ëœ ì˜¤í”ˆì†ŒìŠ¤ ë¼ì´ë¸ŒëŸ¬ë¦¬ ëª©ë¡ +| ì´ë¦„ | ë¼ì´ì„¼ìŠ¤ | +|---|---| +|[Glide](https://github.com/bumptech/glide) | BSD-2, part MIT and Apache 2.0 [(ìƒì„¸)](https://github.com/bumptech/glide/blob/master/LICENSE) | +|[CircleImageView](https://github.com/hdodenhof/CircleImageView) | Apache License 2.0 | +|[Retrofit2](https://square.github.io/retrofit/) | Apache License 2.0 | +|[lottie-android](https://github.com/airbnb/lottie-android) | Apache License 2.0 | +|[OkHttp3](https://square.github.io/okhttp/)| Apache License 2.0 | +- - - +### ì‚¬ìš©ëœ API ëª©ë¡ +| ì´ë¦„ | 설명 | +|---|---| +| Google Maps SDK for Android | 구글 지ë„를 활용하여 ì§€ë„ ê¸°ëŠ¥ì„ êµ¬í˜„ | +| Google OAuth | 구글 ë¡œê·¸ì¸ | +| 백엔드 API | 비즈니스 ë¡œì§ êµ¬í˜„ì„ ìœ„í•œ API | + diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..0584e7bc --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,129 @@ +### Java template +# Compiled class file +*.class +.env +.idea/ +.gradle/ + +# Log file +*.log +.DS_Store +/.DS_Store +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Gradle template +.gradle +**/build/ +!src/**/build/ +.idea/sonarlint + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + + +.idea/misc.xml + +*.lock + +.idea/ + +.env +/.env \ No newline at end of file diff --git a/backend/appspec.yml b/backend/appspec.yml new file mode 100644 index 00000000..64e0e04f --- /dev/null +++ b/backend/appspec.yml @@ -0,0 +1,15 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/howabouttrip +permissions: + - object: /home/ubuntu/howabouttrip/ + owner: ubuntu + group: ubuntu +hooks: + AfterInstall: + - location: scripts/deploy.sh + timeout: 60 + runas: ubuntu \ No newline at end of file diff --git a/backend/build.gradle b/backend/build.gradle new file mode 100644 index 00000000..cafd748d --- /dev/null +++ b/backend/build.gradle @@ -0,0 +1,58 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '3.2.1' + id 'io.spring.dependency-management' version '1.1.4' +} + +group = 'com.isp' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' +} + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-security' + + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + runtimeOnly 'com.mysql:mysql-connector-j' + + // JWT + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // AWS + implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.4.2' + implementation platform("io.awspring.cloud:spring-cloud-aws-dependencies:3.0.2") + implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3' + + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'io.netty:netty-resolver-dns-native-macos:4.1.68.Final:osx-aarch_64' + + // Open API + implementation "com.amadeus:amadeus-java:8.0.0" + + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + + //GSON + implementation 'com.google.code.gson:gson:2.8.8' +} + +tasks.named('test') { + useJUnitPlatform() +} \ No newline at end of file diff --git a/backend/gradle/wrapper/gradle-wrapper.jar b/backend/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..d64cd491 Binary files /dev/null and b/backend/gradle/wrapper/gradle-wrapper.jar differ diff --git a/backend/gradle/wrapper/gradle-wrapper.properties b/backend/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..1af9e093 --- /dev/null +++ b/backend/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/backend/gradlew b/backend/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/backend/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/backend/gradlew.bat b/backend/gradlew.bat new file mode 100644 index 00000000..6689b85b --- /dev/null +++ b/backend/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/backend/scripts/deploy.sh b/backend/scripts/deploy.sh new file mode 100644 index 00000000..35e87630 --- /dev/null +++ b/backend/scripts/deploy.sh @@ -0,0 +1,19 @@ +REPOSITORY=/home/ubuntu/howabouttrip +cd $REPOSITORY + +JAR_NAME=$(ls $REPOSITORY/build/libs/ | grep 'SNAPSHOT.jar' | tail -n 1) +JAR_PATH=$REPOSITORY/build/libs/$JAR_NAME + +CURRENT_PID=$(pgrep -fl 'java -jar /home/ubuntu/howabouttrip/build/libs/backend-0.0.1-SNAPSHOT.jar') + +if [ -z $CURRENT_PID ] +then + echo "> 종료할 애플리케ì´ì…˜ì´ 없습니다." +else + echo "> kill -9 $CURRENT_PID" + kill -15 $CURRENT_PID + sleep 5 +fi + +echo "> Deploy - $JAR_PATH " +nohup java -jar $JAR_PATH > $REPOSITORY/build/libs/nohup.out 2>&1 & \ No newline at end of file diff --git a/backend/settings.gradle b/backend/settings.gradle new file mode 100644 index 00000000..0f5036dc --- /dev/null +++ b/backend/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'backend' diff --git a/backend/src/main/java/com/isp/backend/BackendApplication.java b/backend/src/main/java/com/isp/backend/BackendApplication.java new file mode 100644 index 00000000..5fd1a247 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/BackendApplication.java @@ -0,0 +1,21 @@ +package com.isp.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.client.RestTemplate; + +@SpringBootApplication +@EnableScheduling +public class BackendApplication { + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + + @Bean + public RestTemplate restTemplate() { + return new RestTemplate(); + } + +} diff --git a/backend/src/main/java/com/isp/backend/HealthCheckController.java b/backend/src/main/java/com/isp/backend/HealthCheckController.java new file mode 100644 index 00000000..d8f5bb94 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/HealthCheckController.java @@ -0,0 +1,13 @@ +package com.isp.backend; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HealthCheckController { + + @GetMapping("/") + public String healthCheck() { + return "Service is up and running........"; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java new file mode 100644 index 00000000..ab75715f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/controller/CountryController.java @@ -0,0 +1,78 @@ +package com.isp.backend.domain.country.controller; + +import com.isp.backend.domain.country.dto.request.LocationRequest; +import com.isp.backend.domain.country.dto.response.DailyWeatherResponse; +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import com.isp.backend.domain.country.dto.response.LocationResponse; +import com.isp.backend.domain.country.dto.response.WeatherResponse; +import com.isp.backend.domain.country.service.CountryService; +import com.isp.backend.domain.country.service.ExchangeRateService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalTime; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("/countries") +@RequiredArgsConstructor +public class CountryController { + + private final CountryService countryService; + private final ExchangeRateService exchangeRateService ; + + /** 여행지 좌표 찾기 **/ + @PostMapping("/locations") + public ResponseEntity findLocation(@RequestBody LocationRequest requestDTO) { + String country = requestDTO.getCountry(); + Optional responseDTO = countryService.findLocationByCity(country); + if (responseDTO.isPresent()) { + return ResponseEntity.ok(responseDTO.get()); + } else { + return ResponseEntity.notFound().build(); + } + } + + + /** 여행지 현재 날씨 ì •ë³´ 가져오기 **/ + @PostMapping("/weather/current") + public ResponseEntity getCurrentWeather(@RequestBody LocationRequest requestDTO) { + String city = requestDTO.getCountry(); + WeatherResponse weatherResponse = countryService.getCurrentWeather(city); + return ResponseEntity.ok(weatherResponse); + } + + + /** 여행지 í•œ 주 날씨 ì •ë³´ 조회 **/ + @PostMapping("/weather/weekly") + public ResponseEntity> getWeeklyWeather(@RequestBody LocationRequest requestDTO) { + String city = requestDTO.getCountry(); + LocalTime requestedTime = LocalTime.of(12, 0); + List weeklyWeather = countryService.getWeeklyWeather(city, requestedTime); + return ResponseEntity.ok(weeklyWeather); + } + + + /** 여행지 환율 ì •ë³´ ì—…ë°ì´íŠ¸ API **/ + @GetMapping("/exchange-rates/update") + public String updateExchangeRate() { + try { + exchangeRateService.updateExchangeRates(); + return "updated Successfully"; + } catch (Exception e) { + return "failed to update exchange rates: " + e.getMessage(); + } + } + + + /** 여행지 환율 ì •ë³´ 조회 API **/ + @GetMapping("/exchange-rates") + public ResponseEntity> getAllExchangeRates() { + List exchangeRates = exchangeRateService.getAllExchangeRates(); + return ResponseEntity.ok(exchangeRates); + + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java b/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java new file mode 100644 index 00000000..b9772314 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/request/CurrencyCalculateRequest.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.country.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class CurrencyCalculateRequest { + + private String baseCity; + + private double basePrice ; + + private String targetCity ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/request/LocationRequest.java b/backend/src/main/java/com/isp/backend/domain/country/dto/request/LocationRequest.java new file mode 100644 index 00000000..5adf9bc4 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/request/LocationRequest.java @@ -0,0 +1,10 @@ +package com.isp.backend.domain.country.dto.request; + +import lombok.Getter; + +@Getter +public class LocationRequest { + + private String country; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java new file mode 100644 index 00000000..f2e96331 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/DailyWeatherResponse.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.country.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class DailyWeatherResponse { + + private String date; + + private String temp; + + private String iconUrl; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java new file mode 100644 index 00000000..a766cec8 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/ExchangeRateResponse.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.country.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.math.BigDecimal; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ExchangeRateResponse { + private String baseCurrency; + private String targetCurrency; + private BigDecimal rate; +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/LocationResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/LocationResponse.java new file mode 100644 index 00000000..03ccefdd --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/LocationResponse.java @@ -0,0 +1,23 @@ +package com.isp.backend.domain.country.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class LocationResponse { + + private double latitude; + + private double longitude; + + public void setLatitude(double latitude) { + this.latitude = latitude; + } + public void setLongitude(double longitude) { + this.longitude = longitude; + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/dto/response/WeatherResponse.java b/backend/src/main/java/com/isp/backend/domain/country/dto/response/WeatherResponse.java new file mode 100644 index 00000000..52034b56 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/dto/response/WeatherResponse.java @@ -0,0 +1,27 @@ +package com.isp.backend.domain.country.dto.response; + +import lombok.Data; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class WeatherResponse { + + private String main; + private String description; + + @JsonProperty("temp_min") + private String tempMin; + + @JsonProperty("temp_max") + private String tempMax; + + private String temp; + + private String localTime; + + private String iconUrl ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/entity/Country.java b/backend/src/main/java/com/isp/backend/domain/country/entity/Country.java new file mode 100644 index 00000000..1c713c82 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/entity/Country.java @@ -0,0 +1,40 @@ +package com.isp.backend.domain.country.entity; + +import com.isp.backend.domain.schedule.entity.Schedule; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@NoArgsConstructor +@Table(name = "country") +public class Country { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String nation; + + private String city; + + private String imageUrl; + + private String airportCode; // 공항 코드 + + private double latitude ; // ìœ„ë„ + + private double longitude ; // ê²½ë„ + + private String currencyCode ; + + private String currencyName ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java new file mode 100644 index 00000000..8b5e974e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/entity/ExchangeRate.java @@ -0,0 +1,34 @@ +package com.isp.backend.domain.country.entity; + +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.math.BigDecimal; + + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@Entity +@Table(name = "exchange_rate") +public class ExchangeRate extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String baseCurrency; + + private String targetCurrency; + + private Double rate; + + public ExchangeRate(String baseCurrency, String targetCurrency, Double rate) { + this.baseCurrency = baseCurrency; + this.targetCurrency = targetCurrency; + this.rate = rate; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java b/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java new file mode 100644 index 00000000..d082e329 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/mapper/ExchangeRateMapper.java @@ -0,0 +1,22 @@ +package com.isp.backend.domain.country.mapper; + +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import com.isp.backend.domain.country.entity.ExchangeRate; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; + +@Component +@RequiredArgsConstructor +public class ExchangeRateMapper { + + public ExchangeRateResponse convertToDto(ExchangeRate exchangeRate) { + ExchangeRateResponse dto = new ExchangeRateResponse(); + dto.setBaseCurrency(exchangeRate.getBaseCurrency()); + dto.setTargetCurrency(exchangeRate.getTargetCurrency()); + dto.setRate(BigDecimal.valueOf(exchangeRate.getRate())); + return dto; + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/mapper/WeatherMapper.java b/backend/src/main/java/com/isp/backend/domain/country/mapper/WeatherMapper.java new file mode 100644 index 00000000..be0bc727 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/mapper/WeatherMapper.java @@ -0,0 +1,31 @@ +package com.isp.backend.domain.country.mapper; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.io.InputStream; + +/** weather id ê°’ì„ í†µí•´ description 출력 **/ +public class WeatherMapper { + + private static final ObjectMapper objectMapper = new ObjectMapper(); + private static final String MAPPING_FILE_PATH = "/weather_mapping.json"; + + public static String getWeatherDescriptionTranslation(String descriptionId) { + try { + InputStream inputStream = WeatherMapper.class.getResourceAsStream(MAPPING_FILE_PATH); + JsonNode mappingNode = objectMapper.readTree(inputStream); + JsonNode descriptionNode = mappingNode.get(descriptionId); + + // 만약 해당 IDì— ëŒ€í•œ ì„¤ëª…ì´ ì—†ë‹¤ë©´ 기본값 반환 --> 추후 공통 ì—러 반환 + if (descriptionNode == null) { + return "해당 ì„¤ëª…ì„ ì°¾ì„ ìˆ˜ 없습니다."; + } + return descriptionNode.asText(); + } catch (IOException e) { + e.printStackTrace(); + throw new RuntimeException("Failed to read weather mapping file", e); + } + } +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/CountryRepository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/CountryRepository.java new file mode 100644 index 00000000..abf5f106 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/repository/CountryRepository.java @@ -0,0 +1,15 @@ +package com.isp.backend.domain.country.repository; + +import com.isp.backend.domain.country.entity.Country; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CountryRepository extends JpaRepository { + + Optional findIdByCity(String city); // ì—¬í–‰ë‚˜ë¼ ì´ë¦„으로 id 찾기 + Country findCountryById(Long countryId); + Optional findAirportCodeByCity(String countryName); // ë‚˜ë¼ ì´ë¦„으로 공항 코드 찾기 + Optional findCountryByAirportCode(String airportCode); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java new file mode 100644 index 00000000..aa4381d3 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/repository/ExchangeRateRepository.java @@ -0,0 +1,9 @@ +package com.isp.backend.domain.country.repository; + +import com.isp.backend.domain.country.entity.ExchangeRate; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExchangeRateRepository extends JpaRepository { + ExchangeRate findByBaseCurrencyAndTargetCurrency(String baseCurrency, String targetCurrency); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java b/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java new file mode 100644 index 00000000..86ff3c5d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/CountryService.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.country.service; + +import com.isp.backend.domain.country.dto.response.DailyWeatherResponse; +import com.isp.backend.domain.country.dto.response.LocationResponse; +import com.isp.backend.domain.country.dto.response.WeatherResponse; +import com.isp.backend.domain.country.entity.Country; + +import java.text.DecimalFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +public interface CountryService { + Optional findLocationByCity(String city); + + WeatherResponse getCurrentWeather(String city); + + List getWeeklyWeather(String city, LocalTime requestedTime); + + Optional findCountryByCity(String city); + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/CountryServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/country/service/CountryServiceImpl.java new file mode 100644 index 00000000..1a113aea --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/CountryServiceImpl.java @@ -0,0 +1,201 @@ +package com.isp.backend.domain.country.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.isp.backend.domain.country.dto.response.DailyWeatherResponse; +import com.isp.backend.domain.country.dto.response.LocationResponse; +import com.isp.backend.domain.country.dto.response.WeatherResponse; +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.country.mapper.WeatherMapper; +import com.isp.backend.domain.country.repository.CountryRepository; +import com.isp.backend.global.exception.openApi.OpenWeatherSearchFailedException; +import com.isp.backend.global.exception.schedule.CountryNotFoundException; +import com.isp.backend.global.s3.constant.S3BucketDirectory; +import com.isp.backend.global.s3.service.S3ImageService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.text.DecimalFormat; +import java.time.*; +import java.time.format.DateTimeFormatter; +import java.time.format.TextStyle; +import java.util.*; + + +@Service +public class CountryServiceImpl implements CountryService { + + @Autowired + private CountryRepository countryRepository; + + @Value("${api-key.open-weather}") + private String weatherApiKey; + + private final RestTemplate restTemplate = new RestTemplate(); + + @Autowired + private S3ImageService s3ImageService; + + + /** 여행지 좌표 찾기 **/ + @Override + public Optional findLocationByCity(String city) { + Optional findCountry = countryRepository.findIdByCity(city); + + if (findCountry.isPresent()) { + return findCountry.map(country -> { + LocationResponse locationDTO = new LocationResponse(); + locationDTO.setLatitude(country.getLatitude()); + locationDTO.setLongitude(country.getLongitude()); + return locationDTO; + }); + } else { + throw new CountryNotFoundException(); + } + } + + + + /** ì—¬í–‰ì§€ì˜ ë‚ ì”¨ ì •ë³´ 가져오기 **/ + @Override + public WeatherResponse getCurrentWeather(String city) { + Optional findCountry = findCountryByCity(city); + Country country = findCountry.get(); + double latitude = country.getLatitude(); + double longitude = country.getLongitude(); + + String url = String.format("http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f&appid=%s", latitude, longitude, weatherApiKey); + String jsonResponse = restTemplate.getForObject(url, String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + WeatherResponse weatherResponse = new WeatherResponse(); + + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + + JsonNode weatherNode = rootNode.path("weather").get(0); + weatherResponse.setMain(weatherNode.path("main").asText()); + String descriptionId = weatherNode.path("id").asText(); // ID ê°’ 가져오기 + String descriptionTranslation = WeatherMapper.getWeatherDescriptionTranslation(descriptionId); // 날씨 설명 + weatherResponse.setDescription(descriptionTranslation); + + String icon = weatherNode.path("icon").asText(); + weatherResponse.setIconUrl(s3ImageService.get(S3BucketDirectory.WEATHER.getDirectory(), icon + ".png")); + + JsonNode mainNode = rootNode.path("main"); + weatherResponse.setTemp(convertToCelsius(mainNode.path("temp").asDouble())); + weatherResponse.setTempMin(convertToCelsius(mainNode.path("temp_min").asDouble())); + weatherResponse.setTempMax(convertToCelsius(mainNode.path("temp_max").asDouble())); + + int timezoneOffset = rootNode.path("timezone").asInt(); + weatherResponse.setLocalTime(getLocalTime(timezoneOffset)); + + } catch (IOException e) { + e.printStackTrace(); + throw new OpenWeatherSearchFailedException(); + } + + return weatherResponse; + } + + + + /** í•œ ì£¼ì˜ ë‚ ì”¨ ì •ë³´ 조회 **/ + @Override + public List getWeeklyWeather(String city, LocalTime requestedTime) { + Optional findCountry = findCountryByCity(city); + Country country = findCountry.get(); + double latitude = country.getLatitude(); + double longitude = country.getLongitude(); + + List weeklyWeather = new ArrayList<>(); + String url = String.format("http://api.openweathermap.org/data/2.5/forecast?lat=%f&lon=%f&appid=%s", latitude, longitude, weatherApiKey); + String jsonResponse = restTemplate.getForObject(url, String.class); + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode forecastList = rootNode.path("list"); + + Map dailyWeatherMap = new HashMap<>(); // ê° ë‚ ì§œì˜ ë‚ ì”¨ 정보를 저장할 맵 + + for (JsonNode forecast : forecastList) { + String dateTime = forecast.path("dt_txt").asText(); // 예측 시간 ì •ë³´ + LocalDateTime localDateTime = LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + + // ìš”ì²­ëœ ì‹œê°„ëŒ€ì˜ ì˜¨ë„만 ê³ ë ¤ + if (localDateTime.toLocalTime().equals(requestedTime)) { + String dateString = localDateTime.toLocalDate().toString(); + double temperature = forecast.path("main").path("temp").asDouble(); // ì˜¨ë„ ì •ë³´ + + // 해당 ë‚ ì§œì˜ ì˜¨ë„ ì •ë³´ë¥¼ 저장 (ë™ì¼í•œ ì‹œê°„ëŒ€ì— ì—¬ëŸ¬ ë°ì´í„°ê°€ ìžˆì„ ê²½ìš°, 가장 첫 번째 ë°ì´í„°ë¥¼ 사용) + if (!dailyWeatherMap.containsKey(dateString)) { + // ì•„ì´ì½˜ URL 가져오기 + String iconCode = forecast.path("weather").get(0).path("icon").asText(); + String iconUrl = String.format(s3ImageService.get(S3BucketDirectory.WEATHER.getDirectory(), iconCode + ".png")); + + DailyWeatherResponse dailyWeather = new DailyWeatherResponse(); + dailyWeather.setDate(parseDayOfWeek(dateString)); + dailyWeather.setTemp(convertToCelsius(temperature)); + dailyWeather.setIconUrl(iconUrl); + + dailyWeatherMap.put(dateString, dailyWeather); + } + } + } + + weeklyWeather.addAll(dailyWeatherMap.values()); + weeklyWeather.sort(Comparator.comparing(DailyWeatherResponse::getDate)); + + } catch (IOException e) { + e.printStackTrace(); + throw new OpenWeatherSearchFailedException(); + } + + return weeklyWeather; + } + + + /** ë„ì‹œì´ë¦„으로 ë‚˜ë¼ ì°¾ê¸° **/ + @Override + public Optional findCountryByCity(String city) { + Optional findCountry = countryRepository.findIdByCity(city); + if (!findCountry.isPresent()) { + throw new CountryNotFoundException(); + } + return findCountry; + } + + + /** 현지 시간으로 변환 **/ + private String getLocalTime(int timezoneOffset) { + Instant now = Instant.now(); + ZoneOffset offset = ZoneOffset.ofTotalSeconds(timezoneOffset); + LocalDateTime localDateTime = LocalDateTime.ofInstant(now, offset); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); + return localDateTime.format(formatter); + } + + + /** 온ë„를 섭씨로 변환 **/ + private String convertToCelsius(double kelvin) { + double celsius = kelvin - 273.15; + DecimalFormat df = new DecimalFormat("#.##"); + return df.format(celsius); + } + + + /** 날짜 문ìžì—´ì—ì„œ ìš”ì¼ì„ 파싱 **/ + private String parseDayOfWeek(String dateString) { + LocalDate localDate = LocalDate.parse(dateString); + DayOfWeek dayOfWeek = localDate.getDayOfWeek(); + String weekDay = dayOfWeek.getDisplayName(TextStyle.FULL, Locale.KOREAN); + return (dateString + "," + weekDay); + } + + +} + diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateScheduler.java b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateScheduler.java new file mode 100644 index 00000000..0b7740ba --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateScheduler.java @@ -0,0 +1,23 @@ +package com.isp.backend.domain.country.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +@Component +public class ExchangeRateScheduler { + + @Autowired + private ExchangeRateService exchangeRateService; + + @Scheduled(cron = "0 0 2 * * ?", zone = "Asia/Seoul") // ë§¤ì¼ í•œêµ­ì‹œê°„ 새벽 2ì‹œì— ì‹¤í–‰ + public void scheduleExchangeRateUpdate() { + try { + exchangeRateService.updateExchangeRates(); + } catch (Exception e) { + // 예외 처리 ë¡œì§ + System.out.println("Failed to update exchange rates: " + e.getMessage()); + } + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java new file mode 100644 index 00000000..14a6469a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateService.java @@ -0,0 +1,13 @@ +package com.isp.backend.domain.country.service; + +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +public interface ExchangeRateService { + @Transactional + void updateExchangeRates(); + List getAllExchangeRates(); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateServiceImpl.java new file mode 100644 index 00000000..3411defe --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/country/service/ExchangeRateServiceImpl.java @@ -0,0 +1,119 @@ +package com.isp.backend.domain.country.service; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.isp.backend.domain.country.dto.response.ExchangeRateResponse; +import com.isp.backend.domain.country.entity.ExchangeRate; +import com.isp.backend.domain.country.mapper.ExchangeRateMapper; +import com.isp.backend.domain.country.repository.ExchangeRateRepository; +import com.isp.backend.global.exception.openApi.ExchangeRateIsFailedException; +import com.isp.backend.global.exception.openApi.ExchangeRateSearchFailedException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class ExchangeRateServiceImpl implements ExchangeRateService { + + private final ExchangeRateRepository exchangeRateRepository; + private final ExchangeRateMapper exchangeRateMapper ; + + @Value("${api-key.exchange-rate}") + private String exchangeRateApiKey; + + @Autowired + public ExchangeRateServiceImpl(ExchangeRateRepository exchangeRateRepository, ExchangeRateMapper exchangeRateMapper) { + this.exchangeRateRepository = exchangeRateRepository; + this.exchangeRateMapper = exchangeRateMapper; + } + + /** 환율 ë°ì´í„° 가져와서 ì—…ë°ì´íŠ¸ 하는 API 메서드 **/ + @Override + @Transactional + public void updateExchangeRates() { + // 한국과 미국 통화 환율 비율 저장 + updateRatesForBaseCurrency("KRW"); + updateRatesForBaseCurrency("USD"); + } + + + /** 환율 ë°ì´í„° 가져와서 ì—…ë°ì´íŠ¸ 하는 API 메서드 **/ + @Override + public List getAllExchangeRates() { + try { + // DBì—ì„œ 모든 환율 ë°ì´í„°ë¥¼ 가져옴 + List exchangeRates = exchangeRateRepository.findAll(); + + // baseCurrencyê°€ "KRW" ë˜ëŠ” "USD"ì¸ ë°ì´í„°ë§Œ í•„í„°ë§í•˜ê³  DTOë¡œ 변환 + return exchangeRates.stream() + .filter(rate -> ("KRW".equals(rate.getBaseCurrency()) || "USD".equals(rate.getBaseCurrency())) + && !rate.getBaseCurrency().equals(rate.getTargetCurrency())) + .map(exchangeRateMapper::convertToDto) + .collect(Collectors.toList()); + } catch (Exception e) { + throw new ExchangeRateIsFailedException() ; + } + } + + + /** 측정 baseCurrency í†µí™”ì— ëŒ€í•´ ì—…ë°ì´íŠ¸ 하는 메서드**/ + private void updateRatesForBaseCurrency(String baseCurrency) { + String exchangeRateAPI_URL = "https://v6.exchangerate-api.com/v6/" + exchangeRateApiKey + "/latest/" + baseCurrency; + + try { + URL url = new URL(exchangeRateAPI_URL); + HttpURLConnection request = (HttpURLConnection) url.openConnection(); + request.connect(); + + // API ì‘답 ë°ì´í„°ë¥¼ JsonObjectë¡œ 변환 + Gson gson = new Gson(); + JsonObject jsonobj = gson.fromJson(new InputStreamReader(request.getInputStream()), JsonObject.class); + + // API ì‘답 ê²°ê³¼ + String req_result = jsonobj.get("result").getAsString(); + + if ("success".equals(req_result)) { + JsonObject conversionRates = jsonobj.getAsJsonObject("conversion_rates"); + for (String targetCurrency : conversionRates.keySet()) { + // 필요한 통화만 가져온다 + if (isSupportedCurrency(targetCurrency)) { + double rate = conversionRates.get(targetCurrency).getAsDouble(); + + // DBì—ì„œ 환율 ë°ì´í„° 존재 여부 í™•ì¸ + ExchangeRate existingRate = exchangeRateRepository.findByBaseCurrencyAndTargetCurrency(baseCurrency, targetCurrency); + + if (existingRate == null) { + ExchangeRate newExchangeRate = new ExchangeRate(baseCurrency, targetCurrency, rate); + exchangeRateRepository.save(newExchangeRate); + } else { + // 기ë¡ì´ ì´ë¯¸ 존재하면, rate만 수정한다. + existingRate.setRate(rate); + exchangeRateRepository.save(existingRate); + } + } + } + } else { + throw new ExchangeRateSearchFailedException(); + } + } catch (Exception e) { + throw new ExchangeRateIsFailedException() ; + } + } + + /** 지ì›í•˜ëŠ” 통화만 가져오는 메서드 **/ + private boolean isSupportedCurrency(String currencyCode) { + return Set.of("JPY", "GBP", "EUR", "CHF", "CZK", "USD", "SGD", "TWD", "LAK", "MYR", "VND", "THB", "IDR", "PHP", "KRW").contains(currencyCode); + } + + + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/controller/FlightOfferController.java b/backend/src/main/java/com/isp/backend/domain/flight/controller/FlightOfferController.java new file mode 100644 index 00000000..684adfe6 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/controller/FlightOfferController.java @@ -0,0 +1,81 @@ +package com.isp.backend.domain.flight.controller; + +import com.amadeus.exceptions.ResponseException; +import com.isp.backend.domain.flight.dto.request.FlightLikeRequest; +import com.isp.backend.domain.flight.dto.request.SkyScannerRequest; +import com.isp.backend.domain.flight.dto.response.FlightLikeResponse; +import com.isp.backend.domain.flight.service.FlightOfferService; +import com.isp.backend.domain.flight.dto.request.FlightSearchRequest; +import com.isp.backend.global.exception.openApi.AmadeusSearchFailedException; +import com.isp.backend.global.exception.openApi.SkyScannerGenerateFailedException; +import com.isp.backend.global.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/bookings/flights") +@RequiredArgsConstructor +public class FlightOfferController { + + @Autowired + private FlightOfferService flightOfferService; + + /** 항공권 검색 API **/ + @PostMapping("/search") + public ResponseEntity getFlightOffers(@RequestBody FlightSearchRequest request) { + try { + String flightOffersJson = flightOfferService.getFlightOffers(request); + return ResponseEntity.ok(flightOffersJson); + } catch (ResponseException e) { + throw new AmadeusSearchFailedException(); + } + } + + + /** 항공권 ì„ íƒì‹œ 스카ì´ìŠ¤ìºë„ˆ 사ì´íŠ¸ë¡œ ì—°ê²° API **/ + @PostMapping("/connect") + public ResponseEntity getFlightSearchUrl(@RequestBody SkyScannerRequest request) { + try { + String skyscannerUrl = flightOfferService.generateSkyscannerUrl(request); + return ResponseEntity.ok("{\"skyscannerUrl\": \"" + skyscannerUrl + "\"}"); + } catch (Exception e) { + throw new SkyScannerGenerateFailedException(); + } + } + + + /** 항공권 좋아요 저장 API **/ + @PostMapping("/like") + public ResponseEntity addLikeFlight(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @RequestBody FlightLikeRequest flightLikeRequest) { + String memberUid = customUserDetails.getUsername(); + Long flightId = flightOfferService.addLikeFlight(memberUid, flightLikeRequest); + return ResponseEntity.status(HttpStatus.CREATED).body(flightId); + } + + + /** 항공권 ë‚˜ì˜ ì¢‹ì•„ìš” ëª©ë¡ ë¶ˆëŸ¬ì˜¤ê¸° API **/ + @GetMapping("/likes") + public ResponseEntity> getLikedFlights(@AuthenticationPrincipal CustomUserDetails customUserDetails) { + String memberUid = customUserDetails.getUsername(); + List likedFlights = flightOfferService.getLikedFlights(memberUid); + return ResponseEntity.ok(likedFlights); + } + + + /** 항공권 ë‚˜ì˜ ì¢‹ì•„ìš” ì‚­ì œ API **/ + @DeleteMapping("/like/{id}") + public ResponseEntity deleteLikeFlight(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long id) { + String memberUid = customUserDetails.getUsername(); + flightOfferService.deleteLikeFlight(memberUid, id); + return ResponseEntity.ok().build(); + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightLikeRequest.java b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightLikeRequest.java new file mode 100644 index 00000000..179053e5 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightLikeRequest.java @@ -0,0 +1,40 @@ +package com.isp.backend.domain.flight.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class FlightLikeRequest { + + private String carrierCode; + + private int totalPrice; + + private String abroadDuration; + + private String abroadDepartureTime; + + private String abroadArrivalTime; + + private String homeDuration; + + private String homeDepartureTime; + + private String homeArrivalTime; + + private String departureIataCode; + + private String arrivalIataCode; + + private String transferCount; + + private int adult ; + + private int children ; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightSearchRequest.java b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightSearchRequest.java new file mode 100644 index 00000000..8c0af1f2 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/FlightSearchRequest.java @@ -0,0 +1,28 @@ +package com.isp.backend.domain.flight.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class FlightSearchRequest { + + private String originCity; + + private String destinationCity; + + private String departureDate; + + private String returnDate; + + private int adults; + + private int children; + + private int max ; + + private boolean nonStop ; // ì§í•­ 항공편 유무 + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/flight/dto/request/SkyScannerRequest.java b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/SkyScannerRequest.java new file mode 100644 index 00000000..ff6e8d8e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/dto/request/SkyScannerRequest.java @@ -0,0 +1,28 @@ +package com.isp.backend.domain.flight.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class SkyScannerRequest { + + private String departureIataCode; + + private String arrivalIataCode; + + private String departureDate; + + private String returnDate; + + private int adult; + + private int children; + + private String departureTime; + + private int transferCount; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/flight/dto/response/FlightLikeResponse.java b/backend/src/main/java/com/isp/backend/domain/flight/dto/response/FlightLikeResponse.java new file mode 100644 index 00000000..4f539e55 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/dto/response/FlightLikeResponse.java @@ -0,0 +1,42 @@ +package com.isp.backend.domain.flight.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class FlightLikeResponse { + + private Long id; + + private String carrierCode; + + private int totalPrice; + + private String abroadDuration; + + private String abroadDepartureTime; + + private String abroadArrivalTime; + + private String homeDuration; + + private String homeDepartureTime; + + private String homeArrivalTime; + + private String departureIataCode; + + private String arrivalIataCode; + + private String transferCount; + + private int adult ; + + private int children ; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/flight/entity/Flight.java b/backend/src/main/java/com/isp/backend/domain/flight/entity/Flight.java new file mode 100644 index 00000000..3ed80cef --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/entity/Flight.java @@ -0,0 +1,57 @@ +package com.isp.backend.domain.flight.entity; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.member.entity.Member; +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@NoArgsConstructor +@Table(name = "flight") +public class Flight { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id ; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + private String carrierCode ; + + private Double price ; + + private String abroadDuration ; + + private String abroadDepartureTime ; // 출국-출발 + + private String abroadArrivalTime ; // 출국-ë„ì°© + + @ManyToOne + @JoinColumn(name = "departure_iata_code_id") + private Country departureIataCode ; + + private String homeDuration ; + + private String homeDepartureTime ; // ìž…êµ­-출발 + + private String homeArrivalTime ; // ìž…êµ­-ë„ì°© + @ManyToOne + @JoinColumn(name = "arrival_iata_code_id") + private Country arrivalIataCode ; + + private int transferCount ; + + private int adult ; + + private int children ; + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightMapper.java b/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightMapper.java new file mode 100644 index 00000000..9a5a8e3e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightMapper.java @@ -0,0 +1,61 @@ +package com.isp.backend.domain.flight.mapper; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.flight.dto.request.FlightLikeRequest; +import com.isp.backend.domain.flight.dto.response.FlightLikeResponse; +import com.isp.backend.domain.flight.entity.Flight; +import com.isp.backend.domain.member.entity.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import java.util.List; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class FlightMapper { + + public Flight toEntity(FlightLikeRequest request, Member member, Country departureIataCode, Country arrivalIataCode) { + return Flight.builder() + .member(member) + .carrierCode(request.getCarrierCode()) + .price((double) request.getTotalPrice()) + .abroadDuration(request.getAbroadDuration()) + .abroadDepartureTime(request.getAbroadDepartureTime()) + .abroadArrivalTime(request.getAbroadArrivalTime()) + .homeDuration(request.getHomeDuration()) + .homeDepartureTime(request.getHomeDepartureTime()) + .homeArrivalTime(request.getHomeArrivalTime()) + .departureIataCode(departureIataCode) + .arrivalIataCode(arrivalIataCode) + .transferCount(Integer.parseInt(request.getTransferCount())) + .adult(request.getAdult()) + .children(request.getChildren()) + .build(); + } + + public FlightLikeResponse toFlightLikeRequest(Flight flight) { + FlightLikeResponse response = new FlightLikeResponse(); + response.setId(flight.getId()); + response.setCarrierCode(flight.getCarrierCode()); + response.setTotalPrice((int) Math.round(flight.getPrice())); + response.setAbroadDuration(flight.getAbroadDuration()); + response.setAbroadDepartureTime(flight.getAbroadDepartureTime()); + response.setAbroadArrivalTime(flight.getAbroadArrivalTime()); + response.setHomeDuration(flight.getHomeDuration()); + response.setHomeDepartureTime(flight.getHomeDepartureTime()); + response.setHomeArrivalTime(flight.getHomeArrivalTime()); + response.setDepartureIataCode(flight.getDepartureIataCode().getAirportCode()); + response.setArrivalIataCode(flight.getArrivalIataCode().getAirportCode()); + response.setTransferCount(String.valueOf(flight.getTransferCount())); + response.setAdult(flight.getAdult()); + response.setChildren(flight.getChildren()); + return response; + } + + public List toFlightLikeRequestList(List flights) { + return flights.stream() + .map(this::toFlightLikeRequest) + .collect(Collectors.toList()); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightOfferProcessor.java b/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightOfferProcessor.java new file mode 100644 index 00000000..8e84539f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/mapper/FlightOfferProcessor.java @@ -0,0 +1,128 @@ +package com.isp.backend.domain.flight.mapper; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import org.springframework.stereotype.Service; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Service +public class FlightOfferProcessor { + + /** 추출한 ì •ë³´ json으로 반환 **/ + public String processFlightOffers(String flightOffersJson) { + JsonArray flightOffersArray = new Gson().fromJson(flightOffersJson, JsonArray.class); + + JsonArray filteredFlightOffers = new JsonArray(); + for (JsonElement flightOfferElement : flightOffersArray) { + JsonObject flightOffer = flightOfferElement.getAsJsonObject(); + JsonObject filteredFlightOffer = filterFlightOffer(flightOffer); + filteredFlightOffers.add(filteredFlightOffer); + } + return filteredFlightOffers.toString(); + } + + /** í•­ê³µ ì •ë³´ì—ì„œ 필요한 ì •ë³´ 추출 **/ + public JsonObject filterFlightOffer(JsonObject flightOffer) { + JsonObject filteredFlightOffer = new JsonObject(); + + // 필요한 ì •ë³´ 추출 + String id = flightOffer.get("id").getAsString(); + String carrierCode = flightOffer.getAsJsonArray("itineraries").get(0).getAsJsonObject() + .getAsJsonArray("segments").get(0).getAsJsonObject().get("carrierCode").getAsString(); + Double price = flightOffer.getAsJsonObject("price").get("total").getAsDouble(); + + // 왕복-íŽ¸ë„ ì—¬í–‰ 여부 í™•ì¸ (itineraries ë°°ì—´ ê¸¸ì´ í™•ì¸) + boolean isRoundTrip = flightOffer.getAsJsonArray("itineraries").size() == 2; + + // 출국 ì •ë³´ 추출 + JsonObject abroadItinerary = flightOffer.getAsJsonArray("itineraries").get(0).getAsJsonObject(); + String abroadDuration = abroadItinerary.get("duration").getAsString(); + String abroadDepartureTime = abroadItinerary.getAsJsonArray("segments").get(0).getAsJsonObject().getAsJsonObject("departure").get("at").getAsString(); + String abroadArrivalTime = abroadItinerary.getAsJsonArray("segments").get(0).getAsJsonObject().getAsJsonObject("arrival").get("at").getAsString(); + + // 출발지 ë° ë„착지 ì •ë³´ 추출 + JsonArray segments = flightOffer.getAsJsonArray("itineraries").get(0).getAsJsonObject().getAsJsonArray("segments"); + JsonObject lastSegment = segments.get(segments.size() - 1).getAsJsonObject(); + String departureIataCode = segments.get(0).getAsJsonObject().getAsJsonObject("departure").get("iataCode").getAsString(); + String arrivalIataCode = lastSegment.getAsJsonObject("arrival").get("iataCode").getAsString(); + + // 경유 횟수 í™•ì¸ + int transferCount = countTransfers(segments); + + // 시간 변환 ë° ì €ìž¥ + String filteredAbroadDepartureTime = formatTime(abroadDepartureTime); + String filteredAbroadArrivalTime = formatTime(abroadArrivalTime); + + // duration 변환 ë° ì €ìž¥ + String filteredAbroadDuration = formatDuration(abroadDuration); + + // 경유 ë° ì§í•­ 여부 추출 + boolean nonstop = segments.size() == 1; // ì§í•­ì¼ 경우 segments ë°°ì—´ì˜ í¬ê¸°ëŠ” 1ì´ ë¨ + + filteredFlightOffer.addProperty("id", id); + filteredFlightOffer.addProperty("carrierCode", carrierCode); + filteredFlightOffer.addProperty("totalPrice", price); + filteredFlightOffer.addProperty("departureIataCode", departureIataCode); + filteredFlightOffer.addProperty("arrivalIataCode", arrivalIataCode); + filteredFlightOffer.addProperty("nonstop", nonstop); + filteredFlightOffer.addProperty("transferCount", transferCount); + + filteredFlightOffer.addProperty("abroadDuration", filteredAbroadDuration); + filteredFlightOffer.addProperty("abroadDepartureTime", filteredAbroadDepartureTime); + filteredFlightOffer.addProperty("abroadArrivalTime", filteredAbroadArrivalTime); + + + // 왕복 ì—¬í–‰ì˜ ê²½ìš° + if (isRoundTrip) { + // ìž…êµ­ 여행 ì •ë³´ 추출 + JsonObject homeItinerary = flightOffer.getAsJsonArray("itineraries").get(1).getAsJsonObject(); + String homeDuration = homeItinerary.get("duration").getAsString(); + String homeDepartureTime = homeItinerary.getAsJsonArray("segments").get(0).getAsJsonObject().getAsJsonObject("departure").get("at").getAsString(); + String homeArrivalTime = homeItinerary.getAsJsonArray("segments").get(1).getAsJsonObject().getAsJsonObject("arrival").get("at").getAsString(); + String filteredHomeDepartureTime = formatTime(homeDepartureTime); + String filteredHomeArrivalTime = formatTime(homeArrivalTime); + String filteredHomeDuration = formatDuration(homeDuration); + + // ì‘답 ê°’ì— ì¶”ê°€ + filteredFlightOffer.addProperty("homeDuration", filteredHomeDuration); + filteredFlightOffer.addProperty("homeDepartureTime", filteredHomeDepartureTime); + filteredFlightOffer.addProperty("homeArrivalTime", filteredHomeArrivalTime); + } + return filteredFlightOffer; + } + + + /** 경유 횟수 í™•ì¸ **/ + private int countTransfers(JsonArray segments) { + // ì§í•­ì¼ 경우 경유 ì—†ìŒ + if (segments.size() == 1) { + return 0; + } + // 경유 횟수는 항공편 세그먼트 개수 - 1 + return segments.size() - 1; + } + + + /** 시간 변환 **/ + private String formatTime(String timeStr) { + LocalDateTime time = LocalDateTime.parse(timeStr, DateTimeFormatter.ISO_DATE_TIME); + return time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + } + + + /** duration 비행 시간 변환 **/ + private String formatDuration(String durationStr) { + Duration duration = Duration.parse(durationStr); + + long hours = duration.toHours(); + long minutes = duration.minusHours(hours).toMinutes(); + return String.format("%d:%02d", hours, minutes); + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/repository/FlightRepository.java b/backend/src/main/java/com/isp/backend/domain/flight/repository/FlightRepository.java new file mode 100644 index 00000000..ad5aae33 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/repository/FlightRepository.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.flight.repository; + +import com.isp.backend.domain.flight.entity.Flight; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.entity.Schedule; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface FlightRepository extends JpaRepository { + @Query("SELECT f FROM Flight f WHERE f.member = :member ORDER BY f.id DESC") + List findByMember(@Param("member") Member member); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferService.java b/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferService.java new file mode 100644 index 00000000..d61aa97d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferService.java @@ -0,0 +1,25 @@ +package com.isp.backend.domain.flight.service; + +import com.amadeus.exceptions.ResponseException; +import com.isp.backend.domain.flight.dto.request.FlightLikeRequest; +import com.isp.backend.domain.flight.dto.request.FlightSearchRequest; +import com.isp.backend.domain.flight.dto.request.SkyScannerRequest; +import com.isp.backend.domain.flight.dto.response.FlightLikeResponse; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +public interface FlightOfferService { + + String getFlightOffers(FlightSearchRequest request) throws ResponseException; + + String generateSkyscannerUrl(SkyScannerRequest request); + + @Transactional + Long addLikeFlight(String uid, FlightLikeRequest flightLikeRequest); + + List getLikedFlights(String memberUid); + + @Transactional + void deleteLikeFlight(String memberUid, Long id); +} diff --git a/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferServiceImpl.java new file mode 100644 index 00000000..e38c4716 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/flight/service/FlightOfferServiceImpl.java @@ -0,0 +1,178 @@ +package com.isp.backend.domain.flight.service; + +import com.amadeus.Amadeus; +import com.amadeus.Params; +import com.amadeus.exceptions.ResponseException; +import com.amadeus.resources.FlightOfferSearch; +import com.google.gson.Gson; +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.country.repository.CountryRepository; +import com.isp.backend.domain.flight.dto.request.FlightLikeRequest; +import com.isp.backend.domain.flight.dto.request.FlightSearchRequest; +import com.isp.backend.domain.flight.dto.request.SkyScannerRequest; +import com.isp.backend.domain.flight.dto.response.FlightLikeResponse; +import com.isp.backend.domain.flight.entity.Flight; +import com.isp.backend.domain.flight.mapper.FlightMapper; +import com.isp.backend.domain.flight.mapper.FlightOfferProcessor; +import com.isp.backend.domain.flight.repository.FlightRepository; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.service.ScheduleService; +import com.isp.backend.global.exception.openApi.FlightNotFoundException; +import com.isp.backend.global.exception.openApi.NotYourFlightException; +import com.isp.backend.global.exception.schedule.CountryNotFoundException; +import com.isp.backend.global.exception.schedule.IataCodeNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class FlightOfferServiceImpl implements FlightOfferService { + + private final Amadeus amadeus; + private final CountryRepository countryRepository; + private final ScheduleService scheduleService; + private final FlightOfferProcessor flightOfferProcessor ; + private final FlightRepository flightRepository; + private final FlightMapper flightMapper; + + + /** 항공편 조회 **/ + @Override + public String getFlightOffers(FlightSearchRequest request) throws ResponseException { + + String originLocationCode = findAirportCode(request.getOriginCity()); + String destinationLocationCode = findAirportCode(request.getDestinationCity()); + + Params params = Params.with("originLocationCode", originLocationCode) + .and("destinationLocationCode", destinationLocationCode) + .and("departureDate", request.getDepartureDate()) + .and("adults", request.getAdults()) + .and("children", request.getChildren()) + .and("max", request.getMax()) + .and("nonStop", request.isNonStop()) + .and("currencyCode", "KRW"); // ì›í™” 설정 -> 추후 유저ì—게 ìž…ë ¥ë°›ì„ ìˆ˜ 있게 남겨둠 + + // returnDateê°€ 빈 문ìžì—´ì´ ì•„ë‹Œ 경우ì—만 파ë¼ë¯¸í„° 추가 + if (request.getReturnDate() != null && !request.getReturnDate().isEmpty()) { + params.and("returnDate", request.getReturnDate()); + } + + FlightOfferSearch[] flightOffers = amadeus.shopping.flightOffersSearch.get(params); + + Gson gson = new Gson(); + String flightOffersJson = gson.toJson(flightOffers); + return flightOfferProcessor.processFlightOffers(flightOffersJson); + } + + + /** 항공권 ì„ íƒì‹œ 스카ì´ìŠ¤ìºë„ˆ 사ì´íŠ¸ë¡œ ì—°ê²° **/ + @Override + public String generateSkyscannerUrl(SkyScannerRequest request) { + // 요청 ë°ì´í„° ì •ì œ + String departureDate = request.getDepartureDate().replace("-", "").substring(2) + "/"; + String returnDate = (request.getReturnDate() != null && !request.getReturnDate().isEmpty()) ? request.getReturnDate().replace("-", "").substring(2) + "/" : ""; + int departureTimeMinutes = convertToMinutes(request.getDepartureTime()); + String childrenParam = buildChildrenParam(request.getChildren()); + + String url = "https://www.skyscanner.co.kr/transport/flights/" + + request.getDepartureIataCode().toLowerCase() + "/" + + request.getArrivalIataCode().toLowerCase() + "/" + + departureDate + returnDate + "?adultsv2=" + request.getAdult() + childrenParam + + "&departure-times=" + departureTimeMinutes + + "&inboundaltsenabled=false&outboundaltsenabled=false&ref=home&rtn=" + (returnDate.isEmpty() ? "0" : "1"); + + // ì§í•­ì¸ 경우 + if (request.getTransferCount() == 0) { + url += "&preferdirects=true"; + } else if (request.getTransferCount() == 1) { + url += "&preferdirects=false&stops=!direct"; + } else { + url += "&preferdirects=false&stops=!direct,!oneStop,!oneStop"; + } + return url; + } + + /** childern 계산 **/ + private String buildChildrenParam(int count) { + if (count <= 0) { return ""; } + StringBuilder childrenBuilder = new StringBuilder("&children="); + childrenBuilder.append(count); + if (count > 1) { + childrenBuilder.append("&childrenv2="); + childrenBuilder.append(String.join("%7c", Collections.nCopies(count, "8"))); + } + return childrenBuilder.toString(); + } + + /** + * 항공권 좋아요 저장 + **/ + @Override + @Transactional + public Long addLikeFlight(String uid, FlightLikeRequest flightLikeRequest) { + // 유저 ì •ë³´ í™•ì¸ + Member findMember = scheduleService.validateUserCheck(uid); + + // 출발지, ë„착지 ì •ë³´ + Country departureIataCode = countryRepository.findCountryByAirportCode(flightLikeRequest.getDepartureIataCode()) + .orElseThrow(()-> new IataCodeNotFoundException()); + Country arrivalIataCode = countryRepository.findCountryByAirportCode(flightLikeRequest.getArrivalIataCode()) + .orElseThrow(()-> new IataCodeNotFoundException()); + + // ë°ì´í„° 변환 ë° ì €ìž¥ + Flight flight = flightMapper.toEntity(flightLikeRequest, findMember, departureIataCode, arrivalIataCode); + flightRepository.save(flight); + return flight.getId(); + } + + + /** 항공권 ë‚˜ì˜ ì¢‹ì•„ìš” ëª©ë¡ ë¶ˆëŸ¬ì˜¤ê¸° **/ + @Override + public List getLikedFlights(String memberUid) { + Member findMember = scheduleService.validateUserCheck(memberUid); + List likedFlights = flightRepository.findByMember(findMember); + + return flightMapper.toFlightLikeRequestList(likedFlights); + } + + + /** 항공권 ë‚˜ì˜ ì¢‹ì•„ìš” ì‚­ì œ **/ + @Override + @Transactional + public void deleteLikeFlight(String memberUid, Long id) { + Member findMember = scheduleService.validateUserCheck(memberUid); + + // 해당 항공권 id 조회 + Flight flight = flightRepository.findById(id).orElseThrow(() -> new FlightNotFoundException()); + + // 해당 í•­ê³µê¶Œì´ ìœ ì €ì˜ ê²ƒì¸ì§€ í™•ì¸ + if (!flight.getMember().getId().equals(findMember.getId())) { + throw new NotYourFlightException(); + } + + flightRepository.delete(flight); + } + + /** 공항 코드 찾기 **/ + String findAirportCode(String countryName) { + Country findCountry = countryRepository.findAirportCodeByCity(countryName) + .orElseThrow(() -> new CountryNotFoundException()); + return findCountry.getAirportCode(); + } + + + /** ì‹œê°„ì„ ë¶„ìœ¼ë¡œ 변환 **/ + static int convertToMinutes(String timeString) { + String[] parts = timeString.split(":"); + int hours = Integer.parseInt(parts[0]); + int minutes = Integer.parseInt(parts[1]); + return hours * 60 + minutes; + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/config/GptConfig.java b/backend/src/main/java/com/isp/backend/domain/gpt/config/GptConfig.java new file mode 100644 index 00000000..0e483671 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/config/GptConfig.java @@ -0,0 +1,73 @@ +package com.isp.backend.domain.gpt.config; + +import org.springframework.context.annotation.Configuration; + +@Configuration +public class GptConfig { + public static final String BEARER = "Bearer "; + public static final String CHAT_MODEL = "gpt-4o"; + public static final Integer MAX_TOKEN = 4095; + public static final Boolean STREAM = false; + public static final String ROLE = "user"; + public static final Double TEMPERATURE = 0.9; + public static final String CHAT_URL = "https://api.openai.com/v1/chat/completions"; + public static final String PROMPT = """ + 너는 매우 ì¸ê¸°ìžˆëŠ” 여행사 ì§ì›ì´ì•¼. + 여행사 ê³ ê°ì„ 위해 패키지 ì—¬í–‰ì„ ê³„íší•˜ì—¬ ì¼ì •ì„ 만들어줘야 ë¼. + + 여행지: %s + ì´ë²ˆ ì—¬í–‰ì˜ ëª©ì : %s + ì—¬í–‰ì— í¬í•¨í•˜ê³  ì‹¶ì€ í™œë™: %s + 여행ì—ì„œ 제외하고 ì‹¶ì€ í™œë™: %s + 한국ì—ì„œ 출국하는 날짜: %s + 한국으로 귀국하는 날짜: %s + + 위 ì •ë³´ë“¤ì„ í† ëŒ€ë¡œ ì¼ì •ì„ 만들어줘 + + 너가 주ì˜í•´ì•¼ í•  ì‚¬í•­ì€ ì•„ëž˜ 네가지야. + + 1. ê³ ê°ì´ ì›í•˜ëŠ” 목ì ì§€ë¥¼ 기반으로 여행 계íšì„ 만들어 줘야 하는ë°, 너무 멀리 있는 ìž¥ì†Œë“¤ì„ ë¬¶ìœ¼ë©´ 안ë¼. + 2. í•˜ë£¨ì— ìµœì†Œ 세 ê°œì˜ ëª…ì†Œë¥¼ 방문하고 싶어. + 3. í•˜ë£¨ì— ìµœì†Œ 9ì‹œê°„ì„ ëŒì•„다ë‹ê±°ì•¼. + 4. 3ì¼ ê°„ì˜ ì—¬í–‰ì´ë©´ 3ì¼ì§œë¦¬ 계íšì„ 만들어주면 ë¼. + 5. ìž¥ì†Œì˜ ì¢Œí‘¯ê°’ë„ ì†Œìˆ˜ì ê¹Œì§€ 함께 알려줘 + 6. 특수 문ìžë¥¼ 사용하지 ì•Šê³ , 아래 예시대로 ì¼ì •ì„ 작성해줘 + + 4ë²ˆì˜ ì˜ˆì‹œëŠ” 아래와 같아 + + <2024.02.06> + + - 세비야 대성당 37.38610100, -5.99220400 + - 알카사르 세비야 37.38338500, -5.99051600 + - ì ì‹¬ì‹ì‚¬ ì—스피넬리 글로리아 37.39404600, -5.99379500 + - 히랄다 탑 37.38603000, -5.99303300 + - ìŠ¤íŽ˜ì¸ ê´‘ìž¥ 37.37722200, -5.98694400 + - ì €ë…ì‹ì‚¬ 바루로 37.37734300, -5.98742700 + - 쇼핑 세비야 ì—˜ 코르테 잉글레스 37.38942400, -5.99407200 + + <2024.02.07> + + - 세비야 대성당 37.38610100, -5.99220400 + - 알카사르 세비야 37.38338500, -5.99051600 + - ì ì‹¬ì‹ì‚¬ ì—스피넬리 글로리아 37.39404600, -5.99379500 + - 히랄다 탑 37.38603000, -5.99303300 + - ìŠ¤íŽ˜ì¸ ê´‘ìž¥ 37.37722200, -5.98694400 + - ì €ë…ì‹ì‚¬ 바루로 37.37734300, -5.98742700 + - 쇼핑 세비야 ì—˜ 코르테 잉글레스 37.38942400, -5.99407200 + + <2024.02.08> + + - 세비야 대성당 37.38610100, -5.99220400 + - 알카사르 세비야 37.38338500, -5.99051600 + - ì ì‹¬ì‹ì‚¬ ì—스피넬리 글로리아 37.39404600, -5.99379500 + - 히랄다 탑 37.38603000, -5.99303300 + - ìŠ¤íŽ˜ì¸ ê´‘ìž¥ 37.37722200, -5.98694400 + - ì €ë…ì‹ì‚¬ 바루로 37.37734300, -5.98742700 + - 쇼핑 세비야 ì—˜ 코르테 잉글레스 37.38942400, -5.99407200 + + 날짜 형ì‹ì€ 반드시 ì„ ì§€ì¼œì¤˜. + ë§¤ì¼ ì ì‹¬ê³¼ ì €ë…ì€ ë ˆìŠ¤í† ëž‘ì—ì„œ ë¨¹ì„ ê±°ë‹ˆê¹Œ, 실제로 맛있고 유명한 ë ˆìŠ¤í† ëž‘ë„ ì¼ì •ì— í¬í•¨ì‹œì¼œì¤˜. + 모ë‘ê°€ ë„ˆì˜ ê³„íšì„ 기대하고있어. + 다른 ë§ì€ 하지 ë§ê³ , ì˜¤ì§ ì¼ì •ë§Œ 제공해줘"""; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java b/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java new file mode 100644 index 00000000..72b4a361 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/config/WebClientConfig.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.gpt.config; + +import io.netty.channel.ChannelOption; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.handler.timeout.WriteTimeoutHandler; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.client.reactive.ReactorClientHttpConnector; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.netty.http.client.HttpClient; + + +@Configuration +public class WebClientConfig { + @Bean + public WebClient webClient() { + HttpClient httpClient = HttpClient.create() + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 30000) + .doOnConnected(conn -> conn + .addHandlerLast(new ReadTimeoutHandler(50)) + .addHandlerLast(new WriteTimeoutHandler(50))); + return WebClient.builder() + .clientConnector(new ReactorClientHttpConnector(httpClient)) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/constant/ParsingConstants.java b/backend/src/main/java/com/isp/backend/domain/gpt/constant/ParsingConstants.java new file mode 100644 index 00000000..dd27dcbc --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/constant/ParsingConstants.java @@ -0,0 +1,13 @@ +package com.isp.backend.domain.gpt.constant; + +import java.util.List; + +public class ParsingConstants { + public static final String DATE_REGEX = "(\\d{4}\\.\\d{2}\\.\\d{2})"; + public static final String NEW_LINE_REGEX = "\n"; + public static final String CURRENT_DATE = ""; + public static final String COMMA = ", "; + public static final List FILTER_STRINGS = List.of( + "Message(role=assistant, content=", ")" + ); +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/controller/GptController.java b/backend/src/main/java/com/isp/backend/domain/gpt/controller/GptController.java new file mode 100644 index 00000000..04dade00 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/controller/GptController.java @@ -0,0 +1,23 @@ +package com.isp.backend.domain.gpt.controller; + +import com.isp.backend.domain.gpt.dto.request.GptScheduleRequest; +import com.isp.backend.domain.gpt.dto.response.GptSchedulesResponse; +import com.isp.backend.domain.gpt.service.GptService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.concurrent.CompletableFuture; + +@RequiredArgsConstructor +@RequestMapping("/gpt") +@RestController +@ResponseStatus(value = HttpStatus.CREATED) +public class GptController { + private final GptService gptService; + + @PostMapping("/schedules") + public CompletableFuture sendQuestion(@RequestBody GptScheduleRequest gptScheduleRequest) { + return gptService.askQuestion(gptScheduleRequest); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptRequest.java b/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptRequest.java new file mode 100644 index 00000000..db204941 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptRequest.java @@ -0,0 +1,22 @@ +package com.isp.backend.domain.gpt.dto.request; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.isp.backend.domain.gpt.entity.GptMessage; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GptRequest { + private String model; + + @JsonProperty("max_tokens") + private Integer maxTokens; + private Double temperature; + private Boolean stream; + private List messages; +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptScheduleRequest.java b/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptScheduleRequest.java new file mode 100644 index 00000000..70e51901 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/dto/request/GptScheduleRequest.java @@ -0,0 +1,19 @@ +package com.isp.backend.domain.gpt.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GptScheduleRequest { + private String destination; + private List purpose; + private List includedActivities; + private List excludedActivities; + private String departureDate; + private String returnDate; +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptResponse.java b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptResponse.java new file mode 100644 index 00000000..eefdf30c --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptResponse.java @@ -0,0 +1,22 @@ +package com.isp.backend.domain.gpt.dto.response; + +import com.isp.backend.domain.gpt.entity.GptMessage; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GptResponse { + private List choices; + + @Getter + @Setter + public static class Choice { + private GptMessage message; + } +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptScheduleResponse.java b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptScheduleResponse.java new file mode 100644 index 00000000..9ca3d044 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptScheduleResponse.java @@ -0,0 +1,15 @@ +package com.isp.backend.domain.gpt.dto.response; + +import com.isp.backend.domain.gpt.entity.GptSchedule; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GptScheduleResponse { + private List schedule; +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptSchedulesResponse.java b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptSchedulesResponse.java new file mode 100644 index 00000000..52dadab1 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/dto/response/GptSchedulesResponse.java @@ -0,0 +1,15 @@ +package com.isp.backend.domain.gpt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class GptSchedulesResponse { + private String countryImage; + private List schedules; +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java new file mode 100644 index 00000000..f3bb8bc7 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/Coordinate.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Coordinate { + private Double latitude; + private Double longitude; + + public Coordinate(Double latitude, Double longitude) { + this.latitude = latitude; + this.longitude = longitude; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptMessage.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptMessage.java new file mode 100644 index 00000000..beaac34b --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptMessage.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@NoArgsConstructor +public class GptMessage implements Serializable { + private String role; + private String content; + + @Builder + public GptMessage(String role, String content) { + this.role = role; + this.content = content; + } +} diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java new file mode 100644 index 00000000..c78e099d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptSchedule.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@NoArgsConstructor +public class GptSchedule { + private String date; + private List scheduleDetail; + + public GptSchedule(String date, List scheduleDetail) { + this.date = date; + this.scheduleDetail = scheduleDetail; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java new file mode 100644 index 00000000..99781771 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleDetail.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class GptScheduleDetail { + private String detail; + private Coordinate coordinate; + + public GptScheduleDetail(String detail, Coordinate coordinate) { + this.detail = detail; + this.coordinate = coordinate; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java new file mode 100644 index 00000000..ce3914e3 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/entity/GptScheduleParser.java @@ -0,0 +1,65 @@ +package com.isp.backend.domain.gpt.entity; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +@Component +public class GptScheduleParser { + + public List parseScheduleText(String scheduleText) { + System.out.println(scheduleText); + List schedules = new ArrayList<>(); + + String[] entries = scheduleText.split("<"); + + for (String entry : entries) { + if (entry.trim().isEmpty()) continue; + + String[] lines = entry.split("\n"); + String date = lines[0].trim().replace(">", ""); + + List scheduleDetails = new ArrayList<>(); + + for (int i = 1; i < lines.length; i++) { + String line = lines[i].trim(); + if (line.isEmpty()) continue; + + String[] parts = line.split(" "); + StringBuilder detail = new StringBuilder(); + double latitude = 0.0; + double longitude = 0.0; + + for (int j = 0; j < parts.length; j++) { + try { + if (j == parts.length - 2) { + latitude = Double.parseDouble(parts[j].replace(",", "")); + } else if (j == parts.length - 1) { + longitude = Double.parseDouble(parts[j]); + } else { + if (!detail.isEmpty()) detail.append(" "); + detail.append(parts[j]); + } + } catch (NumberFormatException e) { + if (!detail.isEmpty()) detail.append(" "); + detail.append(parts[j]); + } + } + + String detailString = detail.toString().trim(); + if (detailString.startsWith("-")) { + detailString = detailString.substring(1).trim(); + } + + scheduleDetails.add(new GptScheduleDetail(detailString, new Coordinate(latitude, longitude))); + } + + schedules.add(new GptSchedule(date, scheduleDetails)); + } + + return schedules; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java b/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java new file mode 100644 index 00000000..cbb9fbb2 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/gpt/service/GptService.java @@ -0,0 +1,116 @@ +package com.isp.backend.domain.gpt.service; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.gpt.config.GptConfig; +import com.isp.backend.domain.gpt.constant.ParsingConstants; +import com.isp.backend.domain.gpt.dto.request.GptRequest; +import com.isp.backend.domain.gpt.dto.request.GptScheduleRequest; +import com.isp.backend.domain.gpt.dto.response.GptResponse; +import com.isp.backend.domain.gpt.dto.response.GptScheduleResponse; +import com.isp.backend.domain.gpt.dto.response.GptSchedulesResponse; +import com.isp.backend.domain.gpt.entity.GptMessage; +import com.isp.backend.domain.gpt.entity.GptSchedule; +import com.isp.backend.domain.gpt.entity.GptScheduleParser; +import com.isp.backend.domain.schedule.service.ScheduleService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Service +@RequiredArgsConstructor +public class GptService { + + private final GptScheduleParser gptScheduleParser; + private final ScheduleService scheduleService; + private final WebClient webClient; + + @Value("${api-key.gpt-trip}") + private String apiKey; + + public GptScheduleResponse getResponse(HttpHeaders headers, GptRequest gptRequest) { + GptResponse response = webClient.post() + .uri(GptConfig.CHAT_URL) + .headers(h -> h.addAll(headers)) + .contentType(MediaType.APPLICATION_JSON) + .body(BodyInserters.fromValue(gptRequest)) + .retrieve() + .bodyToMono(GptResponse.class) + .block(); + + List gptSchedules = gptScheduleParser.parseScheduleText(extractScheduleText(response)); + return new GptScheduleResponse(gptSchedules); + } + + private String extractScheduleText(GptResponse gptResponse) { + return gptResponse.getChoices().get(0).getMessage().getContent(); + } + + @Async + public CompletableFuture askQuestion(GptScheduleRequest questionRequest) { + String question = formatQuestion(questionRequest); + List messages = Collections.singletonList(createMessage(question)); + Country country = scheduleService.validateCountry(questionRequest.getDestination()); + String countryImage = country.getImageUrl(); + + ExecutorService executorService = Executors.newFixedThreadPool(5); + List> futures = Arrays.asList( + sendRequestAsync(messages, executorService), + sendRequestAsync(messages, executorService), + sendRequestAsync(messages, executorService) + ); + + CompletableFuture> combinedFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(v -> futures.stream().map(CompletableFuture::join).collect(Collectors.toList())); + + return combinedFuture.thenApply(schedules -> new GptSchedulesResponse(countryImage, schedules)); + } + + private CompletableFuture sendRequestAsync(List messages, ExecutorService executor) { + HttpHeaders headers = buildHttpHeaders(apiKey); + GptRequest request = createGptRequest(messages); + return CompletableFuture.supplyAsync(() -> getResponse(headers, request), executor); + } + + private GptRequest createGptRequest(List messages) { + return new GptRequest(GptConfig.CHAT_MODEL, GptConfig.MAX_TOKEN, GptConfig.TEMPERATURE, GptConfig.STREAM, messages); + } + + private HttpHeaders buildHttpHeaders(String key) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.add(HttpHeaders.AUTHORIZATION, GptConfig.BEARER + key); + return headers; + } + + private String formatQuestion(GptScheduleRequest questionRequest) { + return String.format(GptConfig.PROMPT, + questionRequest.getDestination(), + questionRequest.getPurpose(), + questionRequest.getIncludedActivities(), + questionRequest.getExcludedActivities(), + questionRequest.getDepartureDate(), + questionRequest.getReturnDate(), + String.join(ParsingConstants.COMMA, questionRequest.getPurpose()) + ); + } + + private GptMessage createMessage(String content) { + return GptMessage.builder() + .role(GptConfig.ROLE) + .content(content) + .build(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/controller/HotelController.java b/backend/src/main/java/com/isp/backend/domain/hotel/controller/HotelController.java new file mode 100644 index 00000000..d262634a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/controller/HotelController.java @@ -0,0 +1,43 @@ +package com.isp.backend.domain.hotel.controller; + +import com.amadeus.exceptions.ResponseException; +import com.isp.backend.domain.hotel.dto.request.SearchGeocodeRequest; +import com.isp.backend.domain.hotel.service.HotelService; +import com.isp.backend.global.exception.openApi.AmadeusSearchFailedException; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/bookings/hotels") +@RequiredArgsConstructor +public class HotelController { + + private final HotelService hotelService ; + + /** 좌표 주변 호텔 리스트 API **/ + @PostMapping("/search") + public ResponseEntity searchHotelsByGeocode(@RequestBody SearchGeocodeRequest request) { + try { + String hotelsJson = hotelService.searchHotelsByGeocode(request); + return ResponseEntity.ok(hotelsJson); + } catch (ResponseException e) { + throw new AmadeusSearchFailedException(); + } + } + + /** 호텔 ì„ íƒì‹œ 스카ì´ìŠ¤ìºë„ˆ 사ì´íŠ¸ë¡œ ì—°ê²° API **/ + + + /** 호텔 좋아요 저장 API **/ + + + /** 호텔 좋아요 ëª©ë¡ ë¶ˆëŸ¬ì˜¤ê¸° API **/ + + + /** 호텔 좋아요 삭제하기 API **/ + + + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchCityRequest.java b/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchCityRequest.java new file mode 100644 index 00000000..1d9aa3ac --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchCityRequest.java @@ -0,0 +1,4 @@ +package com.isp.backend.domain.hotel.dto.request; + +public class SearchCityRequest { +} diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchGeocodeRequest.java b/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchGeocodeRequest.java new file mode 100644 index 00000000..3069b085 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/dto/request/SearchGeocodeRequest.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.hotel.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class SearchGeocodeRequest { + + private String latitude; + + private String longitude; + + private int radius ; + + private String ratings ; // 호텔 등급 + +} diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelSearchService.java b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelSearchService.java new file mode 100644 index 00000000..d623efaa --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelSearchService.java @@ -0,0 +1,16 @@ +package com.isp.backend.domain.hotel.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class HotelSearchService { + + /** 좌표 주변 호텔 리스트 API **/ + + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelService.java b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelService.java new file mode 100644 index 00000000..bc4fd247 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelService.java @@ -0,0 +1,10 @@ +package com.isp.backend.domain.hotel.service; + +import com.amadeus.exceptions.ResponseException; +import com.amadeus.resources.Hotel; +import com.isp.backend.domain.hotel.dto.request.SearchGeocodeRequest; + +public interface HotelService { + public String searchHotelsByGeocode(SearchGeocodeRequest request) throws ResponseException; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelServiceImpl.java new file mode 100644 index 00000000..b1d88ea6 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/hotel/service/HotelServiceImpl.java @@ -0,0 +1,44 @@ +package com.isp.backend.domain.hotel.service; + +import com.amadeus.Amadeus; +import com.amadeus.Params; +import com.amadeus.exceptions.ResponseException; +import com.amadeus.resources.Hotel; +import com.amadeus.shopping.HotelOffersSearch; +import com.google.gson.Gson; +import com.isp.backend.domain.hotel.dto.request.SearchGeocodeRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class HotelServiceImpl implements HotelService { + private final Amadeus amadeus; + + /** + * 좌표 주변 호텔 리스트 API + **/ + @Override + public String searchHotelsByGeocode(SearchGeocodeRequest request) throws ResponseException { + String latitude = request.getLatitude(); + String longitude = request.getLongitude(); + int radius = request.getRadius(); + String ratings = request.getRatings(); + + Hotel[] hotelSearch = amadeus.referenceData.locations.hotels.byGeocode.get( + Params.with("latitude", latitude) + .and("longitude", longitude) + .and("radius", radius) + .and("radiusUnit", "KM") + .and("ratings", ratings) + .and("hotelSource", "ALL") + ); + Gson gson = new Gson(); + String hotelSearchJson = gson.toJson(hotelSearch); + return hotelSearchJson; + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/controller/MemberController.java b/backend/src/main/java/com/isp/backend/domain/member/controller/MemberController.java new file mode 100644 index 00000000..1dfe8c9d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/controller/MemberController.java @@ -0,0 +1,55 @@ +package com.isp.backend.domain.member.controller; + +import com.isp.backend.domain.member.dto.request.AuthRecreateRequest; +import com.isp.backend.domain.member.dto.request.GoogleLoginRequest; +import com.isp.backend.domain.member.dto.response.MemberDetailResponse; +import com.isp.backend.domain.member.dto.request.SignUpRequest; +import com.isp.backend.domain.member.service.MemberService; +import com.isp.backend.global.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/members") +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + + + /** ë¡œê·¸ì¸ API **/ + @PostMapping("/login") + public ResponseEntity memberLogin(@RequestBody GoogleLoginRequest request) { + return memberService.memberLogin(request); + } + + + + /** 회ì›ê°€ìž… - 추가 ì •ë³´ API **/ + @PutMapping("/signUp") + public ResponseEntity signUp(@RequestBody SignUpRequest request, + @AuthenticationPrincipal CustomUserDetails customUserDetails) { + String memberUid = customUserDetails.getUsername(); + memberService.signUp(request, memberUid); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + + /** í† í° ìž¬ë°œí–‰ **/ + @PostMapping("/refresh") + public ResponseEntity authRecreate(@RequestBody AuthRecreateRequest authRecreateRequest) { + return memberService.authRecreate(authRecreateRequest); + } + + + /** 멤버 ì •ë³´ 조회 **/ + @GetMapping("/information") + public ResponseEntity getMemberInfo(@AuthenticationPrincipal CustomUserDetails customUserDetails) { + return ResponseEntity.ok(memberService.getMemberInfo(customUserDetails.getUsername())); + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/dto/request/AuthRecreateRequest.java b/backend/src/main/java/com/isp/backend/domain/member/dto/request/AuthRecreateRequest.java new file mode 100644 index 00000000..f5dc4732 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/dto/request/AuthRecreateRequest.java @@ -0,0 +1,14 @@ +package com.isp.backend.domain.member.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class AuthRecreateRequest { + + private String refreshToken; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/dto/request/GoogleLoginRequest.java b/backend/src/main/java/com/isp/backend/domain/member/dto/request/GoogleLoginRequest.java new file mode 100644 index 00000000..33da9035 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/dto/request/GoogleLoginRequest.java @@ -0,0 +1,14 @@ +package com.isp.backend.domain.member.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class GoogleLoginRequest { + + private String uid; + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/member/dto/request/SignUpRequest.java b/backend/src/main/java/com/isp/backend/domain/member/dto/request/SignUpRequest.java new file mode 100644 index 00000000..abca41a0 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/dto/request/SignUpRequest.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.member.dto.request; + +import com.isp.backend.domain.member.entity.Member; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class SignUpRequest { + + private String name; + + private String birth; + + private String phoneNumber; + + + public void toEntity(Member member) { + member.setName(this.name); + member.setBirth(this.birth); + member.setPhoneNumber(this.phoneNumber); + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/dto/response/MemberDetailResponse.java b/backend/src/main/java/com/isp/backend/domain/member/dto/response/MemberDetailResponse.java new file mode 100644 index 00000000..8eb44212 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/dto/response/MemberDetailResponse.java @@ -0,0 +1,29 @@ +package com.isp.backend.domain.member.dto.response; + +import com.isp.backend.domain.member.entity.Member; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class MemberDetailResponse { + + private final String uid; + + private final String name; + + private final String birth; + + private final String phoneNumber; + + + public static MemberDetailResponse fromEntity(Member member) { + return new MemberDetailResponse( + member.getUid(), + member.getName(), + member.getBirth(), + member.getPhoneNumber()); + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/entity/Member.java b/backend/src/main/java/com/isp/backend/domain/member/entity/Member.java new file mode 100644 index 00000000..c35ee15e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/entity/Member.java @@ -0,0 +1,34 @@ +package com.isp.backend.domain.member.entity; + +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "member") +public class Member extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String uid; + + private String name; + + private String birth; + + private String phoneNumber; + + private String loginType; + + @Builder.Default + @Column(nullable = false) + private boolean activated = true; +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/repository/MemberRepository.java b/backend/src/main/java/com/isp/backend/domain/member/repository/MemberRepository.java new file mode 100644 index 00000000..f062c07e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/repository/MemberRepository.java @@ -0,0 +1,14 @@ +package com.isp.backend.domain.member.repository; + +import com.isp.backend.domain.member.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + + Optional findByUidAndActivatedIsTrue(String Uid); + + Optional findByUid(String Uid); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/service/MemberService.java b/backend/src/main/java/com/isp/backend/domain/member/service/MemberService.java new file mode 100644 index 00000000..44f6645c --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/service/MemberService.java @@ -0,0 +1,22 @@ +package com.isp.backend.domain.member.service; + +import com.isp.backend.domain.member.dto.request.AuthRecreateRequest; +import com.isp.backend.domain.member.dto.request.GoogleLoginRequest; +import com.isp.backend.domain.member.dto.response.MemberDetailResponse; +import com.isp.backend.domain.member.dto.request.SignUpRequest; +import com.isp.backend.domain.member.entity.Member; +import org.springframework.http.ResponseEntity; + +public interface MemberService { + ResponseEntity memberLogin(GoogleLoginRequest request); + + void signUp(SignUpRequest signUpRequest, String memberUid); + + ResponseEntity handleExistingMemberLogin(Member existingMember); + + ResponseEntity handleNewMemberLogin(GoogleLoginRequest request); + + ResponseEntity authRecreate(AuthRecreateRequest authRecreateRequest); + + MemberDetailResponse getMemberInfo(String uid); +} diff --git a/backend/src/main/java/com/isp/backend/domain/member/service/MemberServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/member/service/MemberServiceImpl.java new file mode 100644 index 00000000..bc385072 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/member/service/MemberServiceImpl.java @@ -0,0 +1,129 @@ +package com.isp.backend.domain.member.service; + +import com.isp.backend.domain.member.dto.request.AuthRecreateRequest; +import com.isp.backend.domain.member.dto.request.GoogleLoginRequest; +import com.isp.backend.domain.member.dto.response.MemberDetailResponse; +import com.isp.backend.domain.member.dto.request.SignUpRequest; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.member.repository.MemberRepository; +import com.isp.backend.global.exception.common.MemberNotActivatedException; +import com.isp.backend.global.exception.common.MemberNotFoundException; +import com.isp.backend.global.exception.common.RefreshTokenInvalidException; +import com.isp.backend.global.jwt.TokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service + +public class MemberServiceImpl implements MemberService { + + private final MemberRepository memberRepository; + private final TokenProvider tokenProvider; + + + /** ë¡œê·¸ì¸ ë©”ì„œë“œ - jwt í† í° ìƒì„±í›„ ì‘답 **/ + @Transactional + @Override + public ResponseEntity memberLogin(GoogleLoginRequest request) { + Optional existingMemberOptional = memberRepository.findByUid(request.getUid()); + + if (existingMemberOptional.isPresent()) { + Member existingMember = existingMemberOptional.get(); + if (existingMember.isActivated()) { + return handleExistingMemberLogin(existingMember); + } else { + throw new MemberNotActivatedException(); + } + } else { + return handleNewMemberLogin(request); + } + } + + + + /** 기존 회ì›ì˜ ë¡œê·¸ì¸ **/ + @Override + public ResponseEntity handleExistingMemberLogin(Member existingMember) { + String accessToken = tokenProvider.createAccessToken(existingMember.getUid()); + String refreshToken = tokenProvider.createRefreshToken(existingMember.getUid()); + return ResponseEntity.ok() + .header("Access-Token", accessToken) + .header("Refresh-Token", refreshToken) + .body("기존 íšŒì› ë¡œê·¸ì¸"); + } + + + + /** ì‹ ê·œ 회ì›ì˜ ë¡œê·¸ì¸ -> DB 저장 **/ + @Override + public ResponseEntity handleNewMemberLogin(GoogleLoginRequest request) { + Member newMember = Member.builder() + .uid(request.getUid()) + .loginType("google") + .build(); + memberRepository.save(newMember); + + String accessToken = tokenProvider.createAccessToken(newMember.getUid()); + String refreshToken = tokenProvider.createRefreshToken(newMember.getUid()); + + return ResponseEntity.status(HttpStatus.CREATED) + .header("Access-Token", accessToken) + .header("Refresh-Token", refreshToken) + .body("ì‹ ê·œ íšŒì› ë¡œê·¸ì¸"); + } + + + + /** 회ì›ê°€ìž… - ì‹ ê·œ ìœ ì €ì˜ ê²½ìš° 추가 ì •ë³´ 저장 **/ + @Transactional + @Override + public void signUp(SignUpRequest signUpRequest, String memberUid) { + Member findMember = memberRepository.findByUid(memberUid) + .orElseThrow(() -> new MemberNotFoundException()); + signUpRequest.toEntity(findMember); + memberRepository.save(findMember); + } + + + + /** í† í° ìž¬ë°œí–‰ **/ + @Transactional + @Override + public ResponseEntity authRecreate(AuthRecreateRequest authRecreateRequest) { + + if (!tokenProvider.validateRefreshToken(authRecreateRequest.getRefreshToken())) { + throw new RefreshTokenInvalidException(); + } + String uid = tokenProvider.getUid(authRecreateRequest.getRefreshToken()); + + Member member = memberRepository.findByUidAndActivatedIsTrue(uid) + .orElseThrow(MemberNotFoundException::new); + + String accessToken = tokenProvider.createAccessToken(member.getUid()); + String refreshToken = tokenProvider.createRefreshToken(member.getUid()); + + return ResponseEntity.ok() + .header("Access-Token", accessToken) + .header("Refresh-Token", refreshToken) + .body(null); + } + + + /** 멤버 ì •ë³´ 조회 **/ + @Override + public MemberDetailResponse getMemberInfo (String uid) { + Member member = memberRepository.findByUid(uid) + .orElseThrow(MemberNotFoundException::new); + return(MemberDetailResponse.fromEntity(member)); + } + + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/controller/ReceiptController.java b/backend/src/main/java/com/isp/backend/domain/receipt/controller/ReceiptController.java new file mode 100644 index 00000000..394f3d18 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/controller/ReceiptController.java @@ -0,0 +1,100 @@ +package com.isp.backend.domain.receipt.controller; + +import com.isp.backend.domain.receipt.dto.request.ChangeReceiptOrderRequest; +import com.isp.backend.domain.receipt.dto.request.SaveReceiptRequest; +import com.isp.backend.domain.receipt.dto.response.ReceiptResponse; +import com.isp.backend.domain.receipt.dto.response.ScheduleListWithReceiptResponse; +import com.isp.backend.domain.receipt.dto.response.ScheduleReceiptResponse; +import com.isp.backend.domain.receipt.entity.Receipt; +import com.isp.backend.domain.receipt.service.ReceiptService; +import com.isp.backend.global.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequestMapping("/receipts") +@RequiredArgsConstructor +public class ReceiptController { + + private final ReceiptService receiptService; + + + /** ì˜ìˆ˜ì¦ 저장 API **/ + @PostMapping + public ResponseEntity saveReceipt( @RequestPart("request") SaveReceiptRequest request, + @RequestPart(value = "receiptImg", required = false) MultipartFile receiptImg) { + Long receiptId = receiptService.saveReceipt(request, receiptImg); + return ResponseEntity.status(HttpStatus.CREATED).body(receiptId); + } + + + /** ì˜ìˆ˜ì¦ ì‚­ì œ API **/ + @DeleteMapping("/{receiptId}") + public ResponseEntity deleteReceipt(@PathVariable Long receiptId) { + receiptService.deleteReceipt(receiptId); + return ResponseEntity.ok().build(); + } + + + /** ì˜ìˆ˜ì¦ 수정 API **/ + @PutMapping("/{receiptId}") + public ResponseEntity updateReceipt(@PathVariable Long receiptId, + @RequestPart("request") SaveReceiptRequest request, + @RequestPart(value = "receiptImg", required = false) MultipartFile receiptImg) { + Long newReceiptId = receiptService.updateReceipt(receiptId, request, receiptImg); + return ResponseEntity.status(HttpStatus.OK).body(newReceiptId); + } + + + /** 여행 별 ì˜ìˆ˜ì¦ 리스트 ì „ì²´ 조회 API **/ + @GetMapping("/{scheduleId}/list") + public ResponseEntity getReceiptList(@PathVariable Long scheduleId) { + ScheduleReceiptResponse response = receiptService.getReceiptList(scheduleId); + return ResponseEntity.ok(response); + } + + + /** ì˜ìˆ˜ì¦ 별 ìƒì„¸ ë‚´ì—­ 조회 API **/ + @GetMapping("/detail/{receiptId}/list") + public ResponseEntity getReceiptDetail(@PathVariable Long receiptId) { + ReceiptResponse receiptResponse = receiptService.getReceiptDetail(receiptId); + return ResponseEntity.ok(receiptResponse); + } + + + /** ì˜ìˆ˜ì¦ìš© ì¼ì • 리스트 ëª©ë¡ ì¡°íšŒ API **/ + @GetMapping("/schedules/list") + public ResponseEntity> getScheduleListWithReceipt(@AuthenticationPrincipal CustomUserDetails userDetails) { + String memberUid = userDetails.getUsername(); + List scheduleListWithReceipts = receiptService.getScheduleListWithReceipt(memberUid); + return ResponseEntity.ok(scheduleListWithReceipts); + } + + + + /** ì˜ìˆ˜ì¦ 순서 변경 API **/ + @PutMapping("/order/{scheduleId}") + public ResponseEntity changeOrderReceipt(@PathVariable Long scheduleId, + @RequestBody List changeRequests) { + receiptService.changeOrderReceipt(scheduleId, changeRequests); + return ResponseEntity.ok().build(); + } + + + + /** test - ì˜ìˆ˜ì¦ 사진 저장 API **/ + @PostMapping("/{receiptId}/image") + public ResponseEntity saveReceiptImg(@PathVariable Long receiptId, + @RequestParam("receiptImg") MultipartFile receiptImg) { + receiptService.saveReceiptImg(receiptId, receiptImg); + return ResponseEntity.ok().build(); + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ChangeReceiptOrderRequest.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ChangeReceiptOrderRequest.java new file mode 100644 index 00000000..5fbc1424 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ChangeReceiptOrderRequest.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.receipt.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ChangeReceiptOrderRequest { + + private Long receiptId ; + private String purchaseDate ; + private int orderNum ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ReceiptDetailRequest.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ReceiptDetailRequest.java new file mode 100644 index 00000000..6b7983ba --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/ReceiptDetailRequest.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.receipt.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ReceiptDetailRequest { + + private String item; + + private int count; + + private double itemPrice; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/SaveReceiptRequest.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/SaveReceiptRequest.java new file mode 100644 index 00000000..e398b42b --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/request/SaveReceiptRequest.java @@ -0,0 +1,29 @@ +package com.isp.backend.domain.receipt.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class SaveReceiptRequest { + + private Long scheduleId ; + + private String storeName; + + private String storeType ; + + private double totalPrice ; + + private String purchaseDate ; + + private List receiptDetails; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptDetailResponse.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptDetailResponse.java new file mode 100644 index 00000000..9b1e3118 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptDetailResponse.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.receipt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ReceiptDetailResponse { + + private String item; + + private int count; + + private double itemPrice; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptListResponse.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptListResponse.java new file mode 100644 index 00000000..dc838dd4 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptListResponse.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.receipt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ReceiptListResponse { + + private Long receiptId; + + private String storeName; + + private String storeType ; + + private double price ; + + private int orderNum ; + + private String purchaseDate ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptResponse.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptResponse.java new file mode 100644 index 00000000..f11c3662 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ReceiptResponse.java @@ -0,0 +1,27 @@ +package com.isp.backend.domain.receipt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ReceiptResponse { + + private Long receiptId ; + + private String purchaseDate ; + + private int orderNum ; + + private String receiptImg ; + + private double totalPrice ; + + private List receiptDetailList; +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleListWithReceiptResponse.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleListWithReceiptResponse.java new file mode 100644 index 00000000..a64dc3db --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleListWithReceiptResponse.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.receipt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ScheduleListWithReceiptResponse { + + private String scheduleName; + + private String startDate; + + private String endDate; + + private String currencyName; + + private double totalReceiptsPrice ; + + private int receiptCount ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleReceiptResponse.java b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleReceiptResponse.java new file mode 100644 index 00000000..4cbc6041 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/dto/response/ScheduleReceiptResponse.java @@ -0,0 +1,28 @@ +package com.isp.backend.domain.receipt.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class ScheduleReceiptResponse { + + private String scheduleName; + + private String startDate; + + private String endDate; + + private String currencyName; // schedule table -> country tableì˜ currency_name 컬럼 + + private double totalReceiptsPrice ; + + private List receiptList; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/entity/Receipt.java b/backend/src/main/java/com/isp/backend/domain/receipt/entity/Receipt.java new file mode 100644 index 00000000..9a30367a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/entity/Receipt.java @@ -0,0 +1,41 @@ +package com.isp.backend.domain.receipt.entity; + +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "receipt") +public class Receipt extends BaseEntity { + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + private Schedule schedule; + + private String storeName; + + @Enumerated(EnumType.STRING) + private StoreType storeType; + + private double totalPrice; + + private String purchaseDate; + + private String receiptImg; + + private int orderNum ; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/entity/ReceiptDetail.java b/backend/src/main/java/com/isp/backend/domain/receipt/entity/ReceiptDetail.java new file mode 100644 index 00000000..c08b890a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/entity/ReceiptDetail.java @@ -0,0 +1,30 @@ +package com.isp.backend.domain.receipt.entity; + +import jakarta.persistence.*; +import lombok.*; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "receipt_detail") +public class ReceiptDetail { + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "receipt_id", nullable = false) + private Receipt receipt; + + private String item; + + private int count; + + private double itemPrice; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/entity/StoreType.java b/backend/src/main/java/com/isp/backend/domain/receipt/entity/StoreType.java new file mode 100644 index 00000000..28b3e2d0 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/entity/StoreType.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.receipt.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum StoreType { + + AIRPLANE, + HOTEL, + PLACE, + TRANSFER + +}; + + + diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/mapper/ReceiptMapper.java b/backend/src/main/java/com/isp/backend/domain/receipt/mapper/ReceiptMapper.java new file mode 100644 index 00000000..0aacc123 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/mapper/ReceiptMapper.java @@ -0,0 +1,107 @@ +package com.isp.backend.domain.receipt.mapper; + +import com.isp.backend.domain.receipt.dto.request.ReceiptDetailRequest; +import com.isp.backend.domain.receipt.dto.request.SaveReceiptRequest; +import com.isp.backend.domain.receipt.dto.response.ReceiptDetailResponse; +import com.isp.backend.domain.receipt.dto.response.ReceiptListResponse; +import com.isp.backend.domain.receipt.dto.response.ReceiptResponse; +import com.isp.backend.domain.receipt.dto.response.ScheduleListWithReceiptResponse; +import com.isp.backend.domain.receipt.entity.Receipt; +import com.isp.backend.domain.receipt.entity.ReceiptDetail; +import com.isp.backend.domain.receipt.entity.StoreType; +import com.isp.backend.domain.schedule.entity.Schedule; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +@RequiredArgsConstructor +public class ReceiptMapper { + + // ì˜ìˆ˜ì¦ 저장 + public Receipt toEntity(SaveReceiptRequest request, Schedule schedule, int orderNum) { + StoreType storeType = getStoreType(request.getStoreType()); + + return Receipt.builder() + .schedule(schedule) + .storeName(request.getStoreName()) + .storeType(storeType) + .totalPrice(request.getTotalPrice()) + .purchaseDate(request.getPurchaseDate()) + .orderNum(orderNum) + .build(); + } + + public ReceiptDetail toEntity(ReceiptDetailRequest request, Receipt receipt) { + return ReceiptDetail.builder() + .receipt(receipt) + .item(request.getItem()) + .count(request.getCount()) + .itemPrice(request.getItemPrice()) + .build(); + } + + + // 올바른 ì˜ìˆ˜ì¦ íƒ€ìž…ì„ ê°–ëŠ”ì§€ í™•ì¸ + public StoreType getStoreType(String storeType) { + try { + return StoreType.valueOf(storeType.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Invalid store type: " + storeType); + } + } + + + // ì˜ìˆ˜ì¦ ëª©ë¡ ì¡°íšŒ + public ReceiptListResponse toReceiptListResponse(Receipt receipt) { + return new ReceiptListResponse( + receipt.getId(), + receipt.getStoreName(), + receipt.getStoreType().name(), + receipt.getTotalPrice(), + receipt.getOrderNum(), + receipt.getPurchaseDate() + ); + } + + + // ì˜ìˆ˜ì¦ ìƒì„¸ 조회 + public ReceiptResponse toReceiptResponse(Receipt receipt, List receiptDetailResponses) { + return new ReceiptResponse( + receipt.getId(), + receipt.getPurchaseDate(), + receipt.getOrderNum(), + receipt.getReceiptImg(), + receipt.getTotalPrice(), + receiptDetailResponses + ); + } + + public ReceiptDetailResponse toReceiptDetailResponse(ReceiptDetail receiptDetail) { + return new ReceiptDetailResponse( + receiptDetail.getItem(), + receiptDetail.getCount(), + receiptDetail.getItemPrice() + ); + } + + + // ì˜ìˆ˜ì¦ ìš© ì¼ì • 리스트 ëª©ë¡ ì¡°íšŒ + public ScheduleListWithReceiptResponse toScheduleListWithReceiptResponse(Schedule schedule, double totalReceiptsPrice, int receiptCount) { + String currencyName = schedule.getCountry().getCurrencyName(); + + return new ScheduleListWithReceiptResponse( + schedule.getScheduleName(), + schedule.getStartDate(), + schedule.getEndDate(), + currencyName, + totalReceiptsPrice, + receiptCount + ); + } + + + +} + diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptDetailRepository.java b/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptDetailRepository.java new file mode 100644 index 00000000..8de4135a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptDetailRepository.java @@ -0,0 +1,14 @@ +package com.isp.backend.domain.receipt.repository; + +import com.isp.backend.domain.receipt.entity.Receipt; +import com.isp.backend.domain.receipt.entity.ReceiptDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReceiptDetailRepository extends JpaRepository { + void deleteAllByReceipt(Receipt receipt); + + List findByReceiptId(Long receiptId); + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptRepository.java b/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptRepository.java new file mode 100644 index 00000000..da3e18a5 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/repository/ReceiptRepository.java @@ -0,0 +1,29 @@ +package com.isp.backend.domain.receipt.repository; + +import com.isp.backend.domain.receipt.entity.Receipt; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; + +public interface ReceiptRepository extends JpaRepository { + + @Query("SELECT COALESCE(MAX(r.orderNum), 0) FROM Receipt r WHERE r.schedule.id = :scheduleId AND r.purchaseDate = :purchaseDate") + int findMaxOrderNumByScheduleIdAndPurchaseDate(@Param("scheduleId") Long scheduleId, @Param("purchaseDate") String purchaseDate); + + // ìŠ¤ì¼€ì¤„ì— í•´ë‹¹í•˜ëŠ” ì˜ìˆ˜ì¦ì„ purchaseDate 오름차순, orderNum 오름차순으로 정렬하여 반환 + List findByScheduleIdOrderByPurchaseDateAscOrderNumAsc(Long scheduleId); + + List findByScheduleId(Long scheduleId); + + // 스케줄 IDì— í•´ë‹¹í•˜ëŠ” ì˜ìˆ˜ì¦ì˜ totalPrice 합계를 구하는 쿼리 + @Query("SELECT SUM(r.totalPrice) FROM Receipt r WHERE r.schedule.id = :scheduleId") + Double sumTotalPriceByScheduleId(@Param("scheduleId") Long scheduleId); + + // 스케줄 IDì— í•´ë‹¹í•˜ëŠ” ì˜ìˆ˜ì¦ì˜ 개수를 구하는 쿼리 + @Query("SELECT COUNT(r) FROM Receipt r WHERE r.schedule.id = :scheduleId") + int countByScheduleId(@Param("scheduleId") Long scheduleId); + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/receipt/service/ReceiptService.java b/backend/src/main/java/com/isp/backend/domain/receipt/service/ReceiptService.java new file mode 100644 index 00000000..f634b7c7 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/receipt/service/ReceiptService.java @@ -0,0 +1,284 @@ +package com.isp.backend.domain.receipt.service; + +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.member.repository.MemberRepository; +import com.isp.backend.domain.receipt.dto.request.ChangeReceiptOrderRequest; +import com.isp.backend.domain.receipt.dto.request.ReceiptDetailRequest; +import com.isp.backend.domain.receipt.dto.request.SaveReceiptRequest; +import com.isp.backend.domain.receipt.dto.response.*; +import com.isp.backend.domain.receipt.entity.Receipt; +import com.isp.backend.domain.receipt.entity.ReceiptDetail; +import com.isp.backend.domain.receipt.mapper.ReceiptMapper; +import com.isp.backend.domain.receipt.repository.ReceiptDetailRepository; +import com.isp.backend.domain.receipt.repository.ReceiptRepository; +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.schedule.repository.ScheduleRepository; +import com.isp.backend.global.exception.Image.ImageAlreadyExistingException; +import com.isp.backend.global.exception.common.MemberNotFoundException; +import com.isp.backend.global.exception.receipt.ReceiptNotFoundException; +import com.isp.backend.global.exception.schedule.ScheduleNotFoundException; +import com.isp.backend.global.s3.constant.S3BucketDirectory; +import com.isp.backend.global.s3.service.S3ImageService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.util.*; +import java.util.stream.Collectors; + + +@Service +@RequiredArgsConstructor +public class ReceiptService { + private final ReceiptRepository receiptRepository; + private final ReceiptDetailRepository receiptDetailRepository; + private final MemberRepository memberRepository; + private final ScheduleRepository scheduleRepository; + private final ReceiptMapper receiptMapper; + private final S3ImageService s3ImageService; + + + /** ì˜ìˆ˜ì¦ 저장 API **/ + @Transactional + public Long saveReceipt(SaveReceiptRequest request,MultipartFile receiptImg) { + // ì¼ì • ì •ë³´ í™•ì¸ + Schedule findSchedule = validateSchedule(request.getScheduleId()); + + // ë™ì¼í•œ ë‚ ì§œì˜ ê°€ìž¥ í° orderNum 조회 + int maxOrderNum = receiptRepository.findMaxOrderNumByScheduleIdAndPurchaseDate( + request.getScheduleId(), request.getPurchaseDate() + ); + + // orderNum 설정 (해당 ë‚ ì§œì— ì˜ìˆ˜ì¦ì´ 없으면 1ë¡œ 설정, 있으면 maxOrderNum + 1) + int orderNum = maxOrderNum + 1; + + // ë°ì´í„° 변환 ë° ì €ìž¥ + Receipt receipt = receiptMapper.toEntity(request, findSchedule, orderNum); + + // ì˜ìˆ˜ì¦ 사진 저장 + if (receiptImg != null && !receiptImg.isEmpty()) { + String receiptImgUrl = s3ImageService.create(receiptImg, "RECEIPT" ); + receipt.setReceiptImg(receiptImgUrl); + } + + receiptRepository.save(receipt); + + for (ReceiptDetailRequest detailRequest : request.getReceiptDetails()) { + ReceiptDetail detail = receiptMapper.toEntity(detailRequest, receipt); + receiptDetailRepository.save(detail); + } + + return receipt.getId(); + } + + + + /** ì˜ìˆ˜ì¦ ì‚­ì œ 메소드 **/ + @Transactional + public void deleteReceipt(Long receiptId) { + // 유효한 ì˜ìˆ˜ì¦ í™•ì¸ + Receipt receipt = validateReceipt(receiptId); + + // ì˜ìˆ˜ì¦ì— ì—°ê²°ëœ ì„¸ë¶€ ë‚´ì—­ ì‚­ì œ + receiptDetailRepository.deleteAllByReceipt(receipt); + + // ì˜ìˆ˜ì¦ ì‚­ì œ + receiptRepository.delete(receipt); + } + + + + /** ì˜ìˆ˜ì¦ 수정 메소드 **/ + @Transactional + public Long updateReceipt(Long receiptId, SaveReceiptRequest request, MultipartFile receiptImg) { + // 유효한 ì˜ìˆ˜ì¦ í™•ì¸ + Receipt receipt = validateReceipt(receiptId); + + // ì˜ìˆ˜ì¦ ì •ë³´ ì—…ë°ì´íŠ¸ + receipt.setStoreName(request.getStoreName()); + receipt.setStoreType(receiptMapper.getStoreType(request.getStoreType())); + receipt.setTotalPrice(request.getTotalPrice()); + receipt.setPurchaseDate(request.getPurchaseDate()); + + // ì˜ìˆ˜ì¦ ì´ë¯¸ì§€ê°€ ìžˆì„ ê²½ìš° ì—…ë°ì´íŠ¸ + if (receiptImg != null && !receiptImg.isEmpty()) { + String receiptImgUrl = s3ImageService.create(receiptImg, "RECEIPT"); + receipt.setReceiptImg(receiptImgUrl); + } + + // 기존 ì˜ìˆ˜ì¦ 세부 ë‚´ì—­ ì‚­ì œ + receiptDetailRepository.deleteAllByReceipt(receipt); + + // 새 세부 ë‚´ì—­ 추가 + for (ReceiptDetailRequest detailRequest : request.getReceiptDetails()) { + ReceiptDetail detail = receiptMapper.toEntity(detailRequest, receipt); + receiptDetailRepository.save(detail); + } + + // ìˆ˜ì •ëœ ì˜ìˆ˜ì¦ ì •ë³´ 저장 + receiptRepository.save(receipt); + return receipt.getId(); + } + + + + /** 여행 별 ì˜ìˆ˜ì¦ 리스트 ì „ì²´ 조회 메소드 **/ + @Transactional(readOnly = true) + public ScheduleReceiptResponse getReceiptList(Long scheduleId) { + Schedule schedule = validateSchedule(scheduleId); + List receipts = receiptRepository.findByScheduleIdOrderByPurchaseDateAscOrderNumAsc(schedule.getId()); + + // ì˜ìˆ˜ì¦ì˜ 합계 구하기 + double totalReceiptsPrice = receipts.stream() + .mapToDouble(Receipt::getTotalPrice) + .sum(); + // ì˜ìˆ˜ì¦ë“¤ 매핑 + List receiptList = receipts.stream() + .map(receiptMapper::toReceiptListResponse) + .collect(Collectors.toList()); + + return new ScheduleReceiptResponse( + schedule.getScheduleName(), + schedule.getStartDate(), + schedule.getEndDate(), + schedule.getCountry().getCurrencyName(), + totalReceiptsPrice, + receiptList + ); + } + + + + /** ì˜ìˆ˜ì¦ 별 ìƒì„¸ ë‚´ì—­ 조회 메소드 **/ + @Transactional(readOnly = true) + public ReceiptResponse getReceiptDetail(Long receiptId) { + Receipt receipt = validateReceipt(receiptId); + + // ì˜ìˆ˜ì¦ì— ì—°ê´€ëœ ìƒì„¸ ë‚´ì—­ 리스트를 조회 + List receiptDetails = receiptDetailRepository.findByReceiptId(receiptId); + + // ReceiptDetailì„ ReceiptDetailResponseë¡œ 변환 + List receiptDetailResponses = receiptDetails.stream() + .map(receiptMapper::toReceiptDetailResponse) + .collect(Collectors.toList()); + + // ReceiptResponse DTO ìƒì„± ë° ë°˜í™˜ + return receiptMapper.toReceiptResponse(receipt, receiptDetailResponses); + + } + + + + + /** ì˜ìˆ˜ì¦ìš© ì¼ì • 리스트 ëª©ë¡ ì¡°íšŒ API **/ + @Transactional(readOnly = true) + public List getScheduleListWithReceipt(String uid) { + // 유저 ì •ë³´ í™•ì¸ + Member findMember = validateUserCheck(uid); + + // 해당 ìœ ì €ì˜ ëª¨ë“  ì¼ì • 불러오기 + List scheduleList = scheduleRepository.findSchedulesByMember(findMember); + + // ì¼ì • 리스트를 DTOë¡œ 변환 + return scheduleList.stream() + .map(schedule -> { + // sumTotalPriceByScheduleIdê°€ nullì„ ë°˜í™˜í•˜ë©´ 0.0ì„ ê¸°ë³¸ê°’ìœ¼ë¡œ 설정 + double totalReceiptsPrice = Optional.ofNullable(receiptRepository.sumTotalPriceByScheduleId(schedule.getId())) + .orElse(0.0); + int receiptCount = receiptRepository.countByScheduleId(schedule.getId()); + return receiptMapper.toScheduleListWithReceiptResponse(schedule, totalReceiptsPrice, receiptCount); + }) + .collect(Collectors.toList()); + + } + + + + + // ì˜ìˆ˜ì¦ 순서 변경 메서드 수정 예정 + /** 예외처리 ë° ì •í™•í•œ ë¡œì§ ë¶„ì„ í•„ìš” **/ + @Transactional + public void changeOrderReceipt(Long scheduleId, List changeRequests) { + // 해당 ìŠ¤ì¼€ì¤„ì— ì¡´ìž¬í•˜ëŠ” ì˜ìˆ˜ì¦ ì „ì²´ ëª©ë¡ ì¡°íšŒ + List existingReceipts = receiptRepository.findByScheduleId(scheduleId); + + // í´ë¼ì´ì–¸íŠ¸ê°€ 제공한 ì •ë³´ë¡œ ì´ë£¨ì–´ì§„ Map ìƒì„± (key: purchaseDate, receiptId, value: orderNum) + Map> providedReceiptsMap = changeRequests.stream() + .collect(Collectors.groupingBy( + ChangeReceiptOrderRequest::getPurchaseDate, + Collectors.toMap(ChangeReceiptOrderRequest::getReceiptId, ChangeReceiptOrderRequest::getOrderNum) + )); + + // ì œê³µëœ ëª¨ë“  receiptId와 날짜가 해당 ìŠ¤ì¼€ì¤„ì˜ ì˜ìˆ˜ì¦ì— ì¼ì¹˜í•˜ëŠ”지 í™•ì¸ + for (Receipt receipt : existingReceipts) { + Map dateSpecificReceipts = providedReceiptsMap.get(receipt.getPurchaseDate()); + if (dateSpecificReceipts == null || !dateSpecificReceipts.containsKey(receipt.getId())) { + throw new IllegalArgumentException("모든 ì˜ìˆ˜ì¦ ID와 날짜를 제공해야 합니다."); + } + } + + // 날짜별로 orderNum ê°’ì´ ì¤‘ë³µë˜ì§€ 않는지 í™•ì¸ + for (Map receiptMap : providedReceiptsMap.values()) { + Set orderNums = new HashSet<>(receiptMap.values()); + if (orderNums.size() != receiptMap.size()) { + throw new IllegalArgumentException("날짜별 orderNum ê°’ì´ ì¤‘ë³µë©ë‹ˆë‹¤."); + } + } + + // orderNum ì—…ë°ì´íŠ¸ + for (Receipt receipt : existingReceipts) { + int newOrderNum = providedReceiptsMap.get(receipt.getPurchaseDate()).get(receipt.getId()); + receipt.setOrderNum(newOrderNum); + receiptRepository.save(receipt); + } + } + + + + /** 유효한 유저 ì •ë³´ í™•ì¸ **/ + private Member validateUserCheck(String uid) { + return memberRepository.findByUid(uid) + .orElseThrow(MemberNotFoundException::new); + } + + + + /** 유효한 ì¼ì • í™•ì¸ ë©”ì†Œë“œ **/ + private Schedule validateSchedule(Long scheduleId) { + Schedule findSchedule = scheduleRepository.findByIdAndActivatedIsTrue(scheduleId) + .orElseThrow(ScheduleNotFoundException::new); + return findSchedule; + } + + + /** 유효한 ì˜ìˆ˜ì¦ í™•ì¸ ë©”ì„œë“œ**/ + private Receipt validateReceipt(Long receiptId){ + Receipt findReceipt = receiptRepository.findById(receiptId) + .orElseThrow(ReceiptNotFoundException::new); + return findReceipt; + } + + + /** test - ì˜ìˆ˜ì¦ ì´ë¯¸ì§€ 저장 API **/ + @Transactional + public void saveReceiptImg(Long receiptId, MultipartFile receiptImg) { + // ì˜ìˆ˜ì¦ ì •ë³´ í™•ì¸ + Receipt receipt = validateReceipt(receiptId); + + // ì˜ìˆ˜ì¦ 사진 urlì´ ì´ë¯¸ dbì— ìžˆëŠ”ì§€ 여부 í™•ì¸ + if (receipt.getReceiptImg() != null && !receipt.getReceiptImg().isEmpty()) { + throw new ImageAlreadyExistingException(); + } + + // ì˜ìˆ˜ì¦ 사진 저장 + if (receiptImg != null && !receiptImg.isEmpty()) { + String receiptImgUrl = s3ImageService.create(receiptImg, "RECEIPT" ); + receipt.setReceiptImg(receiptImgUrl); + receiptRepository.save(receipt); + } + + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/controller/ScheduleController.java b/backend/src/main/java/com/isp/backend/domain/schedule/controller/ScheduleController.java new file mode 100644 index 00000000..b903363d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/controller/ScheduleController.java @@ -0,0 +1,91 @@ +package com.isp.backend.domain.schedule.controller; + +import com.isp.backend.domain.schedule.dto.response.FastestScheduleResponse; +import com.isp.backend.domain.schedule.dto.response.LatestCreateResponse; +import com.isp.backend.domain.schedule.dto.response.ScheduleListResponse; +import com.isp.backend.domain.schedule.dto.request.ScheduleSaveRequest; +import com.isp.backend.domain.schedule.service.ScheduleService; +import com.isp.backend.global.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/schedules") +@RequiredArgsConstructor +public class ScheduleController { + + private final ScheduleService scheduleService; + + /** 여행 ì¼ì • 저장 API **/ + @PostMapping() + public ResponseEntity saveSchedule(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @RequestBody ScheduleSaveRequest requestDTO) { + scheduleService.saveSchedule(customUserDetails.getUsername(), requestDTO); + return ResponseEntity.ok().build(); + } + + + /** 여행 ì¼ì • ëª©ë¡ ì¡°íšŒ API **/ + @GetMapping() + public ResponseEntity> getScheduleList(@AuthenticationPrincipal CustomUserDetails userDetails) { + String memberUid = userDetails.getUsername(); + List scheduleList = scheduleService.getScheduleList(memberUid); + return ResponseEntity.ok(scheduleList); + } + + + /** 여행 ì¼ì • ìƒì„¸ 조회 API **/ + @GetMapping("/details/{scheduleId}") + public ResponseEntity getScheduleDetail(@AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long scheduleId) { + String memberUid = userDetails.getUsername(); + ScheduleSaveRequest scheduleDetail = scheduleService.getScheduleDetail(memberUid, scheduleId); + return ResponseEntity.ok(scheduleDetail); + } + + + /** 여행 ì¼ì • 수정 API **/ + @PutMapping("/{scheduleId}") + public ResponseEntity updateSchedule(@AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long scheduleId, + @RequestBody ScheduleSaveRequest requestDTO) { + ScheduleSaveRequest updatedSchedule = scheduleService.updateSchedule(userDetails.getUsername(), scheduleId, requestDTO); + return ResponseEntity.ok(updatedSchedule); + } + + + /** 여행 ì¼ì • ì‚­ì œ API **/ + @DeleteMapping("/{scheduleId}") + public ResponseEntity deleteSchedule(@AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable Long scheduleId) { + String memberUid = userDetails.getUsername(); + scheduleService.deleteSchedule(memberUid, scheduleId); + return ResponseEntity.ok().build(); + } + + + /** ë‚˜ì˜ ê°€ê¹Œìš´ ì¼ì • 조회 API **/ + @GetMapping("/dday") + public ResponseEntity getFastestSchedule(@AuthenticationPrincipal CustomUserDetails userDetails) { + String memberUid = userDetails.getUsername(); + FastestScheduleResponse fastestScheduleResponse = scheduleService.getFastestSchedule(memberUid); + return ResponseEntity.ok(fastestScheduleResponse); + } + + + /** ë‚´ê°€ 최근 ìƒì„±í•œ 5ê°œ ì¼ì • 조회 API **/ + @GetMapping("/latest") + public ResponseEntity> getLatestCreatedSchedules( + @AuthenticationPrincipal CustomUserDetails userDetails, + @RequestParam(value = "limit", defaultValue = "6") int limit) { // 출력할 세부 ì¼ì • 개수 ì¡°ì ˆ + String memberUid = userDetails.getUsername(); + List responses = scheduleService.getLatestCreatedSchedules(memberUid, limit); + return ResponseEntity.ok(responses); + } + + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/DailySchedule.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/DailySchedule.java new file mode 100644 index 00000000..d34f4565 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/DailySchedule.java @@ -0,0 +1,20 @@ +package com.isp.backend.domain.schedule.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class DailySchedule { + + private String date; // ì¼ì • 날짜 + + private List schedules; // 해당 ë‚ ì§œì˜ ì¼ì • ëª©ë¡ + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleDetailRequest.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleDetailRequest.java new file mode 100644 index 00000000..c5e82f7d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleDetailRequest.java @@ -0,0 +1,28 @@ +package com.isp.backend.domain.schedule.dto.request; + + +import com.isp.backend.domain.scheduleDetail.entity.ScheduleType; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleDetailRequest { + + private String todo; // í•  ì¼ + + private String place; // 장소 + + private ScheduleType type; // ì¼ì • 유형 + + private double budget; // 예산 + + private double latitude; // ìœ„ë„ + + private double longitude; // ê²½ë„ + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleRequest.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleRequest.java new file mode 100644 index 00000000..6834df4e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleRequest.java @@ -0,0 +1,27 @@ +package com.isp.backend.domain.schedule.dto.request; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.schedule.entity.Schedule; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleRequest { + private Country country; + private String purpose; + private String startDate; + private String endDate; + + public Schedule toEntity() { + return Schedule.builder() + .country(country) + .startDate(startDate) + .endDate(endDate) + .build(); + } +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleSaveRequest.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleSaveRequest.java new file mode 100644 index 00000000..ca76eac6 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/request/ScheduleSaveRequest.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.schedule.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleSaveRequest { + + private String scheduleName; // 여행 ì¼ì • ì´ë¦„ + + private String country; // 여행할 êµ­ê°€ + + private String startDate; // 여행 시작 날짜 + + private String endDate; // 여행 종료 날짜 + + private List dailySchedules; // 하루 ì¼ì • ëª©ë¡ + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/FastestScheduleResponse.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/FastestScheduleResponse.java new file mode 100644 index 00000000..6afbfe8f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/FastestScheduleResponse.java @@ -0,0 +1,24 @@ +package com.isp.backend.domain.schedule.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class FastestScheduleResponse { + + private Long id; + + private String scheduleName; // 여행지 ì´ë¦„ + + private String dday; + + private String country ; + + private String imageUrl; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/LatestCreateResponse.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/LatestCreateResponse.java new file mode 100644 index 00000000..d56c9509 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/LatestCreateResponse.java @@ -0,0 +1,26 @@ +package com.isp.backend.domain.schedule.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class LatestCreateResponse { + + private Long id; + + private String scheduleName; + + private String city; // 여행지 장소 + + private String imageUrl; // ì´ë¯¸ì§€ url + + private List plan ; // 여행 ì¼ì • 리스트 + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleListResponse.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleListResponse.java new file mode 100644 index 00000000..01164a4f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleListResponse.java @@ -0,0 +1,32 @@ +package com.isp.backend.domain.schedule.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleListResponse { + + private Long id; + + private String scheduleName; + + private String startDate; + + private String endDate; + + private double totalPrice; + + private String imageUrl; + + private String country; + + private double latitude ; + + private double longitude ; + + private String currencyName; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleResponse.java b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleResponse.java new file mode 100644 index 00000000..b3ce6237 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/dto/response/ScheduleResponse.java @@ -0,0 +1,13 @@ +package com.isp.backend.domain.schedule.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class ScheduleResponse { + private String name; + private String email; +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/entity/Schedule.java b/backend/src/main/java/com/isp/backend/domain/schedule/entity/Schedule.java new file mode 100644 index 00000000..2bd92bc8 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/entity/Schedule.java @@ -0,0 +1,53 @@ +package com.isp.backend.domain.schedule.entity; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.scheduleDetail.entity.ScheduleDetail; +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + + +@Getter +@AllArgsConstructor +@Entity +@Builder +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "schedule") +public class Schedule extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private String scheduleName; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "country_id") + private Country country; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id", nullable = false) + private Member member; + + @Builder.Default + @OneToMany(mappedBy = "schedule", cascade = CascadeType.ALL) + private List scheduleDetails = new ArrayList<>(); + + private String startDate; + + private String endDate; + + private double totalPrice; + + @Builder.Default + @Column(nullable = false) + private boolean activated = true; + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/entity/SharedSchedule.java b/backend/src/main/java/com/isp/backend/domain/schedule/entity/SharedSchedule.java new file mode 100644 index 00000000..2db55116 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/entity/SharedSchedule.java @@ -0,0 +1,28 @@ +package com.isp.backend.domain.schedule.entity; + +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "shared_schedule") +public class SharedSchedule extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedules_id", nullable = false) // 컬럼명 ì‚­ì œX + private Schedule schedule; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id", nullable = false) + private Member member; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/mapper/ScheduleMapper.java b/backend/src/main/java/com/isp/backend/domain/schedule/mapper/ScheduleMapper.java new file mode 100644 index 00000000..d912fa36 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/mapper/ScheduleMapper.java @@ -0,0 +1,204 @@ +package com.isp.backend.domain.schedule.mapper; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.dto.request.DailySchedule; +import com.isp.backend.domain.schedule.dto.request.ScheduleDetailRequest; +import com.isp.backend.domain.schedule.dto.response.FastestScheduleResponse; +import com.isp.backend.domain.schedule.dto.response.LatestCreateResponse; +import com.isp.backend.domain.schedule.dto.response.ScheduleListResponse; +import com.isp.backend.domain.schedule.dto.request.ScheduleSaveRequest; +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.scheduleDetail.entity.ScheduleDetail; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.temporal.ChronoUnit; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class ScheduleMapper { + + /** + * ì¼ì • 저장 + **/ +// 여행 ì¼ì • 요청 DTO -> 엔티티로 변환 + public Schedule toScheduleEntity(ScheduleSaveRequest scheduleSaveRequest, Member member, Country country) { + // 여행 ì¼ì • 엔티티 ìƒì„± + Schedule schedule = Schedule.builder() + .scheduleName(scheduleSaveRequest.getScheduleName()) + .country(country) + .startDate(scheduleSaveRequest.getStartDate()) + .endDate(scheduleSaveRequest.getEndDate()) + .member(member) + .build(); + + // 여행 ì¼ì • 세부 항목 리스트 ìƒì„± + List scheduleDetails = scheduleSaveRequest.getDailySchedules().stream() + .flatMap(dailySchedule -> { + AtomicInteger num = new AtomicInteger(1); // 날짜별 ì¼ì • 순서 ì¹´ìš´í„° + return dailySchedule.getSchedules().stream() + .map(scheduleDetailDTO -> toScheduleDetailEntity(scheduleDetailDTO, dailySchedule, schedule, num.getAndIncrement())); + }) + .collect(Collectors.toList()); + + // ì¼ì • 세부를 ì¼ì • ì—”í‹°í‹°ì— ì„¤ì • + schedule.setScheduleDetails(scheduleDetails); + + return schedule; + } + + // ì¼ì • 세부 DTO를 엔티티로 변환 + private ScheduleDetail toScheduleDetailEntity(ScheduleDetailRequest scheduleDetailDTO, DailySchedule dailySchedule, Schedule schedule, int num) { + ScheduleDetail scheduleDetail = ScheduleDetail.builder() + .todo(scheduleDetailDTO.getTodo()) + .place(scheduleDetailDTO.getPlace()) + .budget(scheduleDetailDTO.getBudget()) + .latitude(scheduleDetailDTO.getLatitude()) + .longitude(scheduleDetailDTO.getLongitude()) + .date(dailySchedule.getDate()) + .num(num) // ì¼ì • 순서 저장 + .schedule(schedule) + .build(); + + // ScheduleDetailDTOì—ì„œ ì§ì ‘ ScheduleTypeì„ ê°€ì ¸ì™€ì„œ 설정 + scheduleDetail.setScheduleType(scheduleDetailDTO.getType()); + + return scheduleDetail; + } + + + /** + * ì¼ì • ì „ì²´ 조회 + **/ + public ScheduleListResponse toScheduleListResponseDTO(Schedule schedule) { + return new ScheduleListResponse( + schedule.getId(), + schedule.getScheduleName(), + schedule.getStartDate(), + schedule.getEndDate(), + schedule.getTotalPrice(), + schedule.getCountry().getImageUrl(), + schedule.getCountry().getCity(), + schedule.getCountry().getLatitude(), + schedule.getCountry().getLongitude(), + schedule.getCountry().getCurrencyName() + ); + } + + + /** + * ì¼ì • ìƒì„¸ 조회 + **/ + // 엔티티 -> ScheduleSaveRequestDTO ë¡œ 변환 + public ScheduleSaveRequest toScheduleResponseDTO(Schedule schedule) { + // ì¼ì • 세부를 날짜별로 그룹화하고, 날짜를 기준으로 ì •ë ¬ + List sortedDailySchedules = schedule.getScheduleDetails().stream() + .sorted(Comparator.comparing(ScheduleDetail::getDate)) + .collect(Collectors.groupingBy(ScheduleDetail::getDate)) + .entrySet().stream() + .map(entry -> new DailySchedule( + entry.getKey(), + entry.getValue().stream() + .sorted(Comparator.comparingInt(ScheduleDetail::getNum)) + .map(this::toScheduleDetailDTO) + .collect(Collectors.toList()) + )) + .sorted(Comparator.comparing(DailySchedule::getDate)) + .collect(Collectors.toList()); + + return new ScheduleSaveRequest( + schedule.getScheduleName(), + schedule.getCountry().getCity(), // êµ­ê°€ì—ì„œ ë„ì‹œ 가져오기로 수정 + schedule.getStartDate(), + schedule.getEndDate(), + sortedDailySchedules + ); + } + + // ScheduleDetail 엔티티를 ScheduleDetailDTOë¡œ 변환하는 메서드 + private ScheduleDetailRequest toScheduleDetailDTO(ScheduleDetail scheduleDetail) { + ScheduleDetailRequest scheduleDetailDTO = new ScheduleDetailRequest( + scheduleDetail.getTodo(), + scheduleDetail.getPlace(), + scheduleDetail.getScheduleType(), + scheduleDetail.getBudget(), + scheduleDetail.getLatitude(), + scheduleDetail.getLongitude() + ); + return scheduleDetailDTO; + } + + + /** + * ì¼ì • 수정 + **/ + // ScheduleDetailDTO 목ë¡ì„ ScheduleDetail 엔티티 목ë¡ìœ¼ë¡œ 변환하는 메서드 + public List updateSchedulesEntity(ScheduleSaveRequest scheduleSaveRequest, Schedule schedule) { + return scheduleSaveRequest.getDailySchedules().stream() + .flatMap(dailySchedule -> { + AtomicInteger num = new AtomicInteger(1); // 날짜별 ì¼ì • 순서 ì¹´ìš´í„° + return dailySchedule.getSchedules().stream() + .map(scheduleDetail -> toScheduleDetailEntity(scheduleDetail, dailySchedule, schedule, num.getAndIncrement())); + }) + .collect(Collectors.toList()); + } + + + /** + * D-day + **/ + public FastestScheduleResponse toFastestScheduleDTO(Schedule schedule) { + FastestScheduleResponse response = new FastestScheduleResponse(); + response.setId(schedule.getId()); + response.setScheduleName(schedule.getScheduleName()); + response.setDday(calculateDday(schedule.getStartDate())); + response.setCountry(schedule.getCountry().getCity()); + response.setImageUrl(schedule.getCountry().getImageUrl()); + + return response; + } + + + /** + * D-day 계산 메서드 + **/ + private String calculateDday(String startDateString) { + LocalDate today = LocalDate.now(); + LocalDate startDate = LocalDate.parse(startDateString); + long dday = ChronoUnit.DAYS.between(today, startDate) + 1; // 오늘-시작ì¼ê¹Œì§€ì˜ 날짜 수 계산 + return Long.toString(dday); + } + + + + /** + * ìµœê·¼ì— ìƒì„±í•œ 여행 ì¼ì • top 5 + **/ + public LatestCreateResponse toLatestCreateResponse(Schedule schedule, int limit) { + List limitedPlan = schedule.getScheduleDetails().stream() + .sorted(Comparator.comparing(ScheduleDetail::getDate) + .thenComparing(ScheduleDetail::getNum)) + .map(ScheduleDetail::getTodo) + .limit(limit) // 최대 개수로 제한 + .collect(Collectors.toList()); + + return new LatestCreateResponse( + schedule.getId(), + schedule.getScheduleName(), + schedule.getCountry().getCity(), // êµ­ê°€ì—ì„œ ë„ì‹œ 가져오기로 수정 + schedule.getCountry().getImageUrl(), // ì´ë¯¸ì§€ URL 추가 (ì—”í‹°í‹°ì— ì´ í•„ë“œê°€ 있다고 가정) + limitedPlan + ); + } + + + +} + + diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/repository/ScheduleRepository.java b/backend/src/main/java/com/isp/backend/domain/schedule/repository/ScheduleRepository.java new file mode 100644 index 00000000..ddeaa42f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/repository/ScheduleRepository.java @@ -0,0 +1,23 @@ +package com.isp.backend.domain.schedule.repository; + +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.entity.Schedule; +import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; +public interface ScheduleRepository extends JpaRepository { + Optional findById(Long id); + Optional findByIdAndActivatedIsTrue(Long scheduleId); + + @Query("SELECT s FROM Schedule s LEFT JOIN FETCH s.country WHERE s.member = :member AND s.activated = true ORDER BY s.updatedAt DESC") + List findSchedulesByMember(@Param("member") Member member); + + @Query("SELECT s FROM Schedule s LEFT JOIN FETCH s.scheduleDetails WHERE s.member = :member ORDER BY s.id DESC") + List findTop5ByMemberOrderByIdDescWithDetails(@Param("member") Member member); + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/repository/SharedScheduleRepository.java b/backend/src/main/java/com/isp/backend/domain/schedule/repository/SharedScheduleRepository.java new file mode 100644 index 00000000..88a07e10 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/repository/SharedScheduleRepository.java @@ -0,0 +1,8 @@ +package com.isp.backend.domain.schedule.repository; + +import com.isp.backend.domain.schedule.entity.SharedSchedule; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface SharedScheduleRepository extends JpaRepository { + +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleService.java b/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleService.java new file mode 100644 index 00000000..c735b622 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleService.java @@ -0,0 +1,40 @@ +package com.isp.backend.domain.schedule.service; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.dto.response.FastestScheduleResponse; +import com.isp.backend.domain.schedule.dto.response.LatestCreateResponse; +import com.isp.backend.domain.schedule.dto.response.ScheduleListResponse; +import com.isp.backend.domain.schedule.dto.request.ScheduleSaveRequest; +import com.isp.backend.domain.schedule.entity.Schedule; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +public interface ScheduleService { + + void saveSchedule(String uid, ScheduleSaveRequest scheduleSaveRequest); + + List getScheduleList(String uid); + + ScheduleSaveRequest getScheduleDetail(String uid, Long scheduleId); + + void deleteSchedule(String uid, Long scheduleId); + + FastestScheduleResponse getFastestSchedule(String uid); + + ScheduleSaveRequest updateSchedule(String uid, Long scheduleId, ScheduleSaveRequest updateRequestDTO); + + Optional findClosestSchedule(List schedules, LocalDate today); + + void calculateTotalPrice(Schedule schedule); + + Member validateUserCheck(String uid); + + Schedule validateSchedule(Long scheduleId); + + Country validateCountry(String countryName); + + List getLatestCreatedSchedules(String uid, int limit); +} diff --git a/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleServiceImpl.java new file mode 100644 index 00000000..db840007 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/schedule/service/ScheduleServiceImpl.java @@ -0,0 +1,216 @@ +package com.isp.backend.domain.schedule.service; + +import com.isp.backend.domain.country.entity.Country; +import com.isp.backend.domain.country.repository.CountryRepository; +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.member.repository.MemberRepository; +import com.isp.backend.domain.schedule.dto.response.FastestScheduleResponse; +import com.isp.backend.domain.schedule.dto.response.LatestCreateResponse; +import com.isp.backend.domain.schedule.dto.response.ScheduleListResponse; +import com.isp.backend.domain.schedule.dto.request.ScheduleSaveRequest; +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.schedule.mapper.ScheduleMapper; +import com.isp.backend.domain.schedule.repository.ScheduleRepository; +import com.isp.backend.domain.scheduleDetail.entity.ScheduleDetail; +import com.isp.backend.domain.scheduleDetail.repository.ScheduleDetailRepository; +import com.isp.backend.global.exception.common.MemberNotFoundException; +import com.isp.backend.global.exception.schedule.CountryNotFoundException; +import com.isp.backend.global.exception.schedule.NotYourScheduleException; +import com.isp.backend.global.exception.schedule.ScheduleNotFoundException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +@Slf4j +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class ScheduleServiceImpl implements ScheduleService { + + private final ScheduleRepository scheduleRepository; + private final ScheduleDetailRepository scheduleDetailRepository; + private final CountryRepository countryRepository; + private final MemberRepository memberRepository; + private final ScheduleMapper scheduleMapper; + + + /** 여행 ì¼ì • 저장 **/ + @Transactional + @Override + public void saveSchedule(String uid, ScheduleSaveRequest scheduleSaveRequest) { + Member findMember = validateUserCheck(uid); + Country findCountry = validateCountry(scheduleSaveRequest.getCountry()); + // 여행 ì¼ì • 저장 + Schedule schedule = scheduleMapper.toScheduleEntity(scheduleSaveRequest, findMember, findCountry); + // 여행 ì „ì²´ ì¼ì • ì´ ê²½ë¹„ 저장 + calculateTotalPrice(schedule); + + scheduleRepository.save(schedule); + } + + + /** 여행 ì¼ì • ëª©ë¡ ì¡°íšŒ **/ + @Override + public List getScheduleList(String uid) { + Member findMember = validateUserCheck(uid); + // ë‚´ê°€ ì“´ ì¼ì • 불러오기 + List scheduleList = scheduleRepository.findSchedulesByMember(findMember); + + return scheduleList.stream() + .map(scheduleMapper::toScheduleListResponseDTO) + .collect(Collectors.toList()); + } + + + /** 여행 ì¼ì • ìƒì„¸ 조회 **/ + @Override + public ScheduleSaveRequest getScheduleDetail(String uid, Long scheduleId) { + Member findMember = validateUserCheck(uid); + Schedule findSchedule = validateSchedule(scheduleId); + + return scheduleMapper.toScheduleResponseDTO(findSchedule); + } + + + /** 여행 ì¼ì • 수정 **/ + @Transactional + @Override + public ScheduleSaveRequest updateSchedule(String uid, Long scheduleId, ScheduleSaveRequest updateRequestDTO) { + Member findMember = validateUserCheck(uid); + Country findCountry = validateCountry(updateRequestDTO.getCountry()); + Schedule findSchedule = validateSchedule(scheduleId); + + // ìžì‹ ì˜ 여행 ì¼ì •ì¸ì§€ í™•ì¸ + if (!findSchedule.getMember().equals(findMember)) { + throw new NotYourScheduleException(); + } + + scheduleDetailRepository.deleteBySchedule(findSchedule); + + // ê¸°ì¡´ì˜ ì¼ì • 엔티티를 수정 + findSchedule.setScheduleName(updateRequestDTO.getScheduleName()); + findSchedule.setCountry(findCountry); + findSchedule.setStartDate(updateRequestDTO.getStartDate()); + findSchedule.setEndDate(updateRequestDTO.getEndDate()); + + // 새로운 ì¼ì • 세부 ì •ë³´ 추가 + List scheduleDetails = scheduleMapper.updateSchedulesEntity(updateRequestDTO, findSchedule); + findSchedule.setScheduleDetails(scheduleDetails); + + // ì´ê²½ë¹„ 재 계산 후 저장 + calculateTotalPrice(findSchedule); + scheduleRepository.save(findSchedule); + + // ìˆ˜ì •ëœ ì¼ì •ì„ 반환 -- ì—†ì• ë„ ë¨ public void ë¡œ 추후 변경 가능 + return updateRequestDTO; + } + + + /** 여행 ì¼ì • ì‚­ì œ **/ + @Transactional + @Override + public void deleteSchedule(String uid, Long scheduleId) { + Member findMember = validateUserCheck(uid); + Schedule findSchedule = validateSchedule(scheduleId); + // ìžì‹ ì˜ 여행 ì¼ì •ì¸ì§€ í™•ì¸ + if (!findSchedule.getMember().equals(findMember)) { + throw new NotYourScheduleException(); + } + + // ScheduleDetail í…Œì´ë¸” ë°ì´í„° ì‚­ì œ + scheduleDetailRepository.deleteBySchedule(findSchedule); + findSchedule.setActivated(false); + + scheduleRepository.save(findSchedule); + } + + /** ë‚˜ì˜ ì—¬í–‰ D-day 출력 **/ + @Override + public FastestScheduleResponse getFastestSchedule(String uid) { + Member findMember = validateUserCheck(uid); + List schedules = scheduleRepository.findSchedulesByMember(findMember); + + // 가장 가까운 여행 ì¼ì • 찾기 + LocalDate today = LocalDate.now(); + Optional closestScheduleOptional = findClosestSchedule(schedules, today); + + if (closestScheduleOptional.isPresent()) { + Schedule closestSchedule = closestScheduleOptional.get(); + return scheduleMapper.toFastestScheduleDTO(closestSchedule); + } else { + throw new ScheduleNotFoundException(); + } + } + + + /** 가장 가까운 ì¼ì • 찾기 **/ + @Override + public Optional findClosestSchedule(List schedules, LocalDate today) { + return schedules.stream() + .filter(schedule -> LocalDate.parse(schedule.getStartDate()).isAfter(today)) // 오늘 ì´í›„ì˜ ì¼ì • + .min(Comparator.comparing(s -> LocalDate.parse(s.getStartDate()))); // 시작ì¼ì´ 가장 빠른 순으로 ì •ë ¬ + } + + + /** 여행 ì¼ì • ì´ ê²½ë¹„ 계산 **/ + @Override + public void calculateTotalPrice(Schedule schedule) { + double totalPrice = schedule.getScheduleDetails().stream() + .mapToDouble(ScheduleDetail::getBudget) + .sum(); + schedule.setTotalPrice(totalPrice); + } + + + /** 유저 ì •ë³´ í™•ì¸ **/ + @Override + public Member validateUserCheck(String uid) { + Member findMember = memberRepository.findByUid(uid) + .orElseThrow(MemberNotFoundException::new); + return findMember; + } + + + + /** 여행 ì¼ì • ê²€ì¦ **/ + @Override + public Schedule validateSchedule(Long scheduleId) { + Schedule findSchedule = scheduleRepository.findByIdAndActivatedIsTrue(scheduleId) + .orElseThrow(ScheduleNotFoundException::new); + return findSchedule; + } + + + /** 여행 êµ­ê°€ ê²€ì¦ **/ + @Override + public Country validateCountry(String countryName) { + Country findCountry = countryRepository.findIdByCity(countryName) + .orElseThrow(() -> new CountryNotFoundException()); + return findCountry; + } + + + /** ìµœê·¼ì— ìƒì„±í•œ 여행 ì¼ì • top 5**/ + @Override + public List getLatestCreatedSchedules(String uid, int limit) { + Member findMember = validateUserCheck(uid); + List topSchedules = scheduleRepository.findTop5ByMemberOrderByIdDescWithDetails(findMember); + + if (topSchedules.isEmpty()) { + throw new ScheduleNotFoundException(); + } + + List responses = new ArrayList<>(); + for (Schedule schedule : topSchedules) { + LatestCreateResponse response = scheduleMapper.toLatestCreateResponse(schedule, limit); + responses.add(response); + } + return responses; + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/controller/CheckListController.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/controller/CheckListController.java new file mode 100644 index 00000000..7aca4cfe --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/controller/CheckListController.java @@ -0,0 +1,63 @@ +package com.isp.backend.domain.scheduleDetail.controller; + +import com.isp.backend.domain.scheduleDetail.dto.request.CheckListRequest; +import com.isp.backend.domain.scheduleDetail.dto.response.CheckListResponse; +import com.isp.backend.domain.scheduleDetail.service.CheckListService; +import com.isp.backend.global.security.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.* ; + +import java.util.List; + +@RestController +@RequestMapping("/schedules") +@RequiredArgsConstructor +public class CheckListController { + + private final CheckListService checkListService; + + /** ì¼ì • ì²´í¬ë¦¬ìŠ¤íŠ¸ 저장 API **/ + @PostMapping("/{scheduleId}/checklists") + public ResponseEntity> addCheckLists(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long scheduleId, @RequestBody List checkListRequests) { + String memberUid = customUserDetails.getUsername(); + List checkListResponses = checkListService.addCheckLists(memberUid, scheduleId, checkListRequests); + return ResponseEntity.ok(checkListResponses); + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ 조회 API **/ + @GetMapping("/{scheduleId}/checklists") + public ResponseEntity> getCheckLists(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long scheduleId) { + String memberUid = customUserDetails.getUsername(); + List checkListResponses = checkListService.getCheckLists(memberUid, scheduleId); + return ResponseEntity.ok(checkListResponses); + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ ì‚­ì œ API **/ + @DeleteMapping("/{scheduleId}/checklists/{checkListId}") + public ResponseEntity> deleteCheckList(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long scheduleId, + @PathVariable Long checkListId) { + String memberUid = customUserDetails.getUsername(); + List checkListResponses = checkListService.deleteCheckList(memberUid, scheduleId, checkListId); + return ResponseEntity.ok(checkListResponses); + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ 수정 API **/ + @PutMapping("/{scheduleId}/checklists") + public ResponseEntity> updateCheckLists(@AuthenticationPrincipal CustomUserDetails customUserDetails, + @PathVariable Long scheduleId, + @RequestBody List checkListResponses) { + String memberUid = customUserDetails.getUsername(); + List updatedCheckListResponses = checkListService.updateCheckLists(memberUid, scheduleId, checkListResponses); + return ResponseEntity.ok(updatedCheckListResponses); + } + + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/request/CheckListRequest.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/request/CheckListRequest.java new file mode 100644 index 00000000..8c1731d9 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/request/CheckListRequest.java @@ -0,0 +1,18 @@ +package com.isp.backend.domain.scheduleDetail.dto.request; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class CheckListRequest { + + private String todo; + + private boolean check; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/response/CheckListResponse.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/response/CheckListResponse.java new file mode 100644 index 00000000..e029c062 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/dto/response/CheckListResponse.java @@ -0,0 +1,24 @@ +package com.isp.backend.domain.scheduleDetail.dto.response; + +import com.isp.backend.domain.scheduleDetail.entity.CheckList; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class CheckListResponse { + private Long id; + private String todo; + private boolean check; + + public CheckListResponse(CheckList checkList) { + this.id = checkList.getId(); + this.todo = checkList.getTodo(); + this.check = checkList.isChecked(); + } +} + diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/CheckList.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/CheckList.java new file mode 100644 index 00000000..f9e68f21 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/CheckList.java @@ -0,0 +1,31 @@ +package com.isp.backend.domain.scheduleDetail.entity; + +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.global.common.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@AllArgsConstructor +@Entity +@Builder +@Setter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "checkList") +public class CheckList extends BaseEntity { + + @Id + @Column(name = "id", unique = true, nullable = false) + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String todo ; + + @Column(nullable = false) + private boolean isChecked = false; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedule_id", nullable = false) + private Schedule schedule; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleDetail.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleDetail.java new file mode 100644 index 00000000..2851dc96 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleDetail.java @@ -0,0 +1,41 @@ +package com.isp.backend.domain.scheduleDetail.entity; + +import com.isp.backend.domain.schedule.entity.Schedule; +import jakarta.persistence.*; +import lombok.*; + +@AllArgsConstructor +@Entity +@Setter +@Builder +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "schedule_detail") +public class ScheduleDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String todo; + + private String date; + + private String place; + + private int num; + + @Enumerated(EnumType.STRING) + private ScheduleType scheduleType ; + + private double budget; + + private double latitude; + + private double longitude; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "schedules_id" , nullable = false) // 컬럼명 ì‚­ì œX + private Schedule schedule; + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleType.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleType.java new file mode 100644 index 00000000..0bafc8dc --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/entity/ScheduleType.java @@ -0,0 +1,14 @@ +package com.isp.backend.domain.scheduleDetail.entity; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ScheduleType { + + AIRPLANE, + HOTEL, + PLACE, + +}; diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/CheckListRepository.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/CheckListRepository.java new file mode 100644 index 00000000..14addd51 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/CheckListRepository.java @@ -0,0 +1,11 @@ +package com.isp.backend.domain.scheduleDetail.repository; + +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.scheduleDetail.entity.CheckList; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CheckListRepository extends JpaRepository { + List findBySchedule(Schedule schedule); +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/ScheduleDetailRepository.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/ScheduleDetailRepository.java new file mode 100644 index 00000000..8061c1b0 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/repository/ScheduleDetailRepository.java @@ -0,0 +1,10 @@ +package com.isp.backend.domain.scheduleDetail.repository; + +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.scheduleDetail.entity.ScheduleDetail; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScheduleDetailRepository extends JpaRepository { + + void deleteBySchedule(Schedule schedule); +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListService.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListService.java new file mode 100644 index 00000000..4c92d7f5 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListService.java @@ -0,0 +1,21 @@ +package com.isp.backend.domain.scheduleDetail.service; + +import com.isp.backend.domain.scheduleDetail.dto.request.CheckListRequest; +import com.isp.backend.domain.scheduleDetail.dto.response.CheckListResponse; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +public interface CheckListService { + @Transactional + List addCheckLists(String uid, Long scheduleId, List checkListRequests); + + @Transactional(readOnly = true) + List getCheckLists(String uid, Long scheduleId); + + @Transactional + List deleteCheckList(String uid, Long scheduleId, Long checkListId); + + @Transactional + List updateCheckLists(String uid, Long scheduleId, List checkListResponses); +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListServiceImpl.java b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListServiceImpl.java new file mode 100644 index 00000000..904ec9a9 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleDetail/service/CheckListServiceImpl.java @@ -0,0 +1,129 @@ +package com.isp.backend.domain.scheduleDetail.service; + +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.schedule.repository.ScheduleRepository; +import com.isp.backend.domain.schedule.service.ScheduleService; +import com.isp.backend.domain.scheduleDetail.dto.request.CheckListRequest; +import com.isp.backend.domain.scheduleDetail.dto.response.CheckListResponse; +import com.isp.backend.domain.scheduleDetail.entity.CheckList; +import com.isp.backend.domain.scheduleDetail.repository.CheckListRepository; +import com.isp.backend.global.exception.schedule.CheckListNotFoundException; +import com.isp.backend.global.exception.schedule.NotYourScheduleException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Transactional +@Service +public class CheckListServiceImpl implements CheckListService { + private final CheckListRepository checkListRepository; + private final ScheduleService scheduleService; + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ 추가 API **/ + @Override + @Transactional + public List addCheckLists(String uid, Long scheduleId, List checkListRequests) { + Member findMember = scheduleService.validateUserCheck(uid); + Schedule findSchedule = scheduleService.validateSchedule(scheduleId); + // ìžì‹ ì˜ 여행 ì¼ì •ì¸ì§€ í™•ì¸ + if (!findSchedule.getMember().equals(findMember)) { + throw new NotYourScheduleException(); + } + + List responses = new ArrayList<>(); + for (CheckListRequest request : checkListRequests) { + CheckList checkList = CheckList.builder() + .todo(request.getTodo()) + .isChecked(request.isCheck()) + .schedule(findSchedule) + .build(); + checkListRepository.save(checkList); + responses.add(new CheckListResponse(checkList)); + } + + return responses; + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ 조회 API **/ + @Override + @Transactional(readOnly = true) + public List getCheckLists(String uid, Long scheduleId) { + Member findMember = scheduleService.validateUserCheck(uid); + Schedule findSchedule = scheduleService.validateSchedule(scheduleId); + // ìžì‹ ì˜ 여행 ì¼ì •ì¸ì§€ í™•ì¸ + if (!findSchedule.getMember().equals(findMember)) { + throw new NotYourScheduleException(); + } + + List checkLists = checkListRepository.findBySchedule(findSchedule); + List responses = checkLists.stream() + .map(CheckListResponse::new) + .collect(Collectors.toList()); + + return responses; + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ ì‚­ì œ API **/ + @Override + @Transactional + public List deleteCheckList(String uid, Long scheduleId, Long checkListId) { + Member findMember = scheduleService.validateUserCheck(uid); + Schedule findSchedule = scheduleService.validateSchedule(scheduleId); + CheckList checkList = checkListRepository.findById(checkListId) + .orElseThrow(CheckListNotFoundException::new); + + // ìžì‹ ì˜ 여행 ì¼ì •ì¸ì§€ í™•ì¸ + ì²´í¬ë¦¬ìŠ¤íŠ¸ê°€ 해당 ìŠ¤ì¼€ì¤„ì— ì†í•˜ëŠ”지 í™•ì¸ + if (!findSchedule.getMember().equals(findMember) || !checkList.getSchedule().equals(findSchedule)) { + throw new NotYourScheduleException(); + } + + checkListRepository.delete(checkList); + + List checkLists = checkListRepository.findBySchedule(findSchedule); + List responses = checkLists.stream() + .map(CheckListResponse::new) + .collect(Collectors.toList()); + + return responses; + } + + + /** 여행 ì²´í¬ë¦¬ìŠ¤íŠ¸ 수정 API **/ + @Override + @Transactional + public List updateCheckLists(String uid, Long scheduleId, List checkListResponses) { + Member findMember = scheduleService.validateUserCheck(uid); + Schedule findSchedule = scheduleService.validateSchedule(scheduleId); + + if (!findSchedule.getMember().equals(findMember)) { + throw new NotYourScheduleException(); + } + + for (CheckListResponse response : checkListResponses) { + CheckList checkList = checkListRepository.findById(response.getId()) + .orElseThrow(CheckListNotFoundException::new); + if (!checkList.getSchedule().equals(findSchedule)) { + throw new NotYourScheduleException(); + } + checkList.setTodo(response.getTodo()); + checkList.setChecked(response.isCheck()); + } + + List updatedCheckLists = checkListRepository.findBySchedule(findSchedule); + List responses = updatedCheckLists.stream() + .map(CheckListResponse::new) + .collect(Collectors.toList()); + + return responses; + } + +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/controller/ScheduleImageController.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/controller/ScheduleImageController.java new file mode 100644 index 00000000..fb79ca5e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/controller/ScheduleImageController.java @@ -0,0 +1,24 @@ +package com.isp.backend.domain.scheduleImage.controller; + +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageRequest; +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageResponse; +import com.isp.backend.domain.scheduleImage.service.SaveImageService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/scheduleImages") +public class ScheduleImageController { + + private final SaveImageService saveImageService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public SaveScheduleImageResponse create(@RequestPart(value = "image", required = false) MultipartFile image, + @RequestPart(value = "saveScheduleImageRequest") SaveScheduleImageRequest request) { + return saveImageService.save(request, image); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageRequest.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageRequest.java new file mode 100644 index 00000000..852acc87 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageRequest.java @@ -0,0 +1,19 @@ +package com.isp.backend.domain.scheduleImage.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +public class SaveScheduleImageRequest { + private Long scheduleId; + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime saveDate; + +// public Long scheduleId() { +// return scheduleId; +// } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageResponse.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageResponse.java new file mode 100644 index 00000000..d6270049 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/dto/SaveScheduleImageResponse.java @@ -0,0 +1,17 @@ +package com.isp.backend.domain.scheduleImage.dto; + +import com.isp.backend.domain.scheduleImage.entity.ScheduleImage; +import lombok.Getter; + +@Getter +public class SaveScheduleImageResponse { + private Long id; + private Long scheduleId; + private String path; + + public SaveScheduleImageResponse(ScheduleImage scheduleImage) { + this.id = scheduleImage.getId(); + this.scheduleId = scheduleImage.getSchedule().getId(); + this.path = scheduleImage.getPath(); + } +} diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/entity/ScheduleImage.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/entity/ScheduleImage.java new file mode 100644 index 00000000..49264ecc --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/entity/ScheduleImage.java @@ -0,0 +1,27 @@ +package com.isp.backend.domain.scheduleImage.entity; + +import com.isp.backend.domain.schedule.entity.Schedule; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor +@Table(name = "schedule_image") +public class ScheduleImage { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "schedule_id") + private Schedule schedule; + private String path; + + public ScheduleImage(Schedule schedule, String path) { + this.schedule = schedule; + this.path = path; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageRepository.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageRepository.java new file mode 100644 index 00000000..bc05c3e5 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageRepository.java @@ -0,0 +1,7 @@ +package com.isp.backend.domain.scheduleImage.repository; + +import com.isp.backend.domain.scheduleImage.entity.ScheduleImage; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ScheduleImageRepository extends JpaRepository { +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageS3Repository.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageS3Repository.java new file mode 100644 index 00000000..991f460f --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/repository/ScheduleImageS3Repository.java @@ -0,0 +1,51 @@ +package com.isp.backend.domain.scheduleImage.repository; + +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageRequest; +import io.awspring.cloud.s3.S3Resource; +import io.awspring.cloud.s3.S3Template; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; + +@Repository +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class ScheduleImageS3Repository { + private static final String IMAGE = "HowAboutTrip-Backend-ScheduleImage/"; + private static final String SLASH = "/"; + + private final S3Template s3template; + private final String bucketName; + + public ScheduleImageS3Repository(S3Template s3template, + @Value("${spring.cloud.aws.s3.bucket}") String bucketName) { + this.s3template = s3template; + this.bucketName = bucketName; + } + + public String save(SaveScheduleImageRequest request, MultipartFile image) { + String path = IMAGE + request.getScheduleId() + SLASH + image.getOriginalFilename(); + S3Resource result = s3template.upload(bucketName, path, getInputStream(image)); + return getUrl(result); + } + + private InputStream getInputStream(MultipartFile image) { + try { + return image.getInputStream(); + } catch (IOException e) { + throw new RuntimeException("Error getting input stream from MultipartFile", e); + } + } + + private String getUrl(S3Resource s3Resource) { + try { + return s3Resource.getURL().toString(); + } catch (IOException e) { + throw new RuntimeException("Error getting URL from S3Resource", e); + } + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/SaveImageService.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/SaveImageService.java new file mode 100644 index 00000000..17f55751 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/SaveImageService.java @@ -0,0 +1,10 @@ +package com.isp.backend.domain.scheduleImage.service; + +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageRequest; +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageResponse; +import org.springframework.web.multipart.MultipartFile; + +public interface SaveImageService { + + SaveScheduleImageResponse save(SaveScheduleImageRequest request, MultipartFile image); +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/ScheduleImageService.java b/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/ScheduleImageService.java new file mode 100644 index 00000000..989eef97 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/domain/scheduleImage/service/ScheduleImageService.java @@ -0,0 +1,31 @@ +package com.isp.backend.domain.scheduleImage.service; + +import com.isp.backend.domain.schedule.entity.Schedule; +import com.isp.backend.domain.schedule.repository.ScheduleRepository; +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageRequest; +import com.isp.backend.domain.scheduleImage.dto.SaveScheduleImageResponse; +import com.isp.backend.domain.scheduleImage.entity.ScheduleImage; +import com.isp.backend.domain.scheduleImage.repository.ScheduleImageRepository; +import com.isp.backend.domain.scheduleImage.repository.ScheduleImageS3Repository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public class ScheduleImageService implements SaveImageService { + + private final ScheduleImageS3Repository scheduleImageS3Repository; + private final ScheduleRepository scheduleRepository; + private final ScheduleImageRepository scheduleImageRepository; + + @Override + public SaveScheduleImageResponse save(SaveScheduleImageRequest request, MultipartFile image) { + String imagePath = scheduleImageS3Repository.save(request, image); + Schedule schedule = scheduleRepository.findById(request.getScheduleId()).orElseThrow(IllegalArgumentException::new); + ScheduleImage scheduleImage = new ScheduleImage(schedule, imagePath); + + scheduleImageRepository.save(scheduleImage); + return new SaveScheduleImageResponse(scheduleImage); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/common/BaseEntity.java b/backend/src/main/java/com/isp/backend/global/common/BaseEntity.java new file mode 100644 index 00000000..6bcc8beb --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/common/BaseEntity.java @@ -0,0 +1,38 @@ +package com.isp.backend.global.common; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Getter +@Setter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + + @CreatedDate + @Column(name = "created_at", updatable = false) + private String createdAt; + + @LastModifiedDate + @Column(name = "updated_at") + private String updatedAt; + + + @PrePersist + public void onPrePersist() { + this.createdAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm")); + this.updatedAt = this.createdAt; + } + + @PreUpdate + public void onPreUpdate() { + this.updatedAt = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm")); + } +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/CustomException.java b/backend/src/main/java/com/isp/backend/global/exception/CustomException.java new file mode 100644 index 00000000..9ef09c26 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/CustomException.java @@ -0,0 +1,14 @@ +package com.isp.backend.global.exception; + +import lombok.Getter; + +@Getter +public class CustomException extends RuntimeException { + + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.errorCode = errorCode; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java b/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java new file mode 100644 index 00000000..60ebb2b0 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/ErrorCode.java @@ -0,0 +1,55 @@ +package com.isp.backend.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +@Getter +public enum ErrorCode { + + + // Common + MEMBER_NOT_FOUND(HttpStatus.UNAUTHORIZED, "U001", "사용ìžë¥¼ ì°¾ì„ ìˆ˜ 없습니다."), + MEMBER_NOT_ACTIVATED(HttpStatus.BAD_REQUEST, "U002", "사용ìžê°€ 활성화 ìƒíƒœê°€ 아닙니다."), + AUTHENTICATION_FAILED(HttpStatus.FORBIDDEN, "U003", "ê¶Œí•œì´ ì—†ëŠ” 요청입니다. 토í°ì„ 추가해주세요."), + ACCESS_TOKEN_IS_INVALID(HttpStatus.UNAUTHORIZED, "U004", "엑세스 토í°ì´ 유효하지 않습니다."), + REFRESH_TOKEN_IS_INVALID(HttpStatus.UNAUTHORIZED, "U005", "리프레시 토í°ì´ 유효하지 않습니다."), + + // Image + DIRECTORY_NAME_NOTFOUND(HttpStatus.NOT_FOUND,"I001","S3ì—ì„œ 해당 ë””ë ‰í† ë¦¬ì˜ ì´ë¦„ì„ ì°¾ì„ ìˆ˜ 없습니다."), + IMAGE_ALREADY_EXISTING(HttpStatus.BAD_REQUEST, "I002", "ì´ë¯¸ì§€ê°€ ì´ë¯¸ 저장ë˜ì–´ 있습니다."), + + // Schedule + COUNTRY_NOT_FOUND(HttpStatus.NOT_FOUND, "S001", "여행할 국가를 ì°¾ì„ ìˆ˜ 없습니다."), + SCHEDULE_NOT_FOUND(HttpStatus.NOT_FOUND, "S002", "여행 ì¼ì •ì„ ì°¾ì„ ìˆ˜ 없습니다."), + NOT_YOUR_SCHEDULE(HttpStatus.UNAUTHORIZED, "S003", "사용ìžì˜ 여행 ì¼ì •ì´ 아닙니다."), + IMAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "S004", "ì´ë¯¸ì§€ë¥¼ ì°¾ì„ ìˆ˜ 없습니다."), + IATA_CODE_NOT_FOUND(HttpStatus.NOT_FOUND, "S005", "해당 êµ­ê°€ì˜ ê³µí•­ 코드를 ì°¾ì„ ìˆ˜ 없습니다."), + CHECK_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, "S006", "ì²´í¬ë¦¬ìŠ¤íŠ¸ë¥¼ ì°¾ì„ ìˆ˜ 없습니다"), + + // Receipt + RECEIPT_NOT_FOUND(HttpStatus.NOT_FOUND, "R001", "헤딩 ì˜ìˆ˜ì¦ ID를 ì°¾ì„ ìˆ˜ 없습니다."), + + // Open API + AMADEUS_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F001", "아마ë°ìš°ìŠ¤ ìš”ì²­ì„ ê°€ì ¸ì˜¤ëŠ” 중 오류를 ë°œìƒí–ˆìŠµë‹ˆë‹¤."), + SKY_SCANNER_GENERATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F002", "스카ì´ìŠ¤ìºë„ˆ URLì„ ìƒì„±í•  수 없습니다."), + FLIGHT_NOT_FOUND(HttpStatus.NOT_FOUND, "F003", "해당 idì˜ í•­ê³µê¶Œì„ ì°¾ì„ ìˆ˜ 없습니다."), + NOT_YOUR_FLIGHT(HttpStatus.UNAUTHORIZED, "F004", "사용ìžì˜ í•­ê³µê¶Œì´ ì•„ë‹™ë‹ˆë‹¤"), + OPEN_WEATHER_SEARCH_FAILED(HttpStatus.NOT_FOUND,"F005", "날씨 ì •ë³´ íŒŒì‹±ì— ì‹¤íŒ¨í•˜ì˜€ìŠµë‹ˆë‹¤"), + EXCHANGE_RATE_SEARCH_FAILED(HttpStatus.INTERNAL_SERVER_ERROR,"F006", "환율 ìš”ì²­ì„ ê°€ì ¸ì˜¤ëŠ” 중 오류를 ë°œìƒí–ˆìŠµë‹ˆë‹¤."), + EXCHANGE_RATE_IS_FAILED(HttpStatus.BAD_REQUEST,"F007", "환율 DB를 ì €ìž¥í•˜ë˜ ì¤‘ 오류가 ë°œìƒí•˜ì˜€ìŠµë‹ˆë‹¤."); + + + private HttpStatus status; + private String code; + private String message; + + + ErrorCode(HttpStatus status, String code, String message) { + this.status = status; + this.code = code; + this.message = message; + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/ErrorResponse.java b/backend/src/main/java/com/isp/backend/global/exception/ErrorResponse.java new file mode 100644 index 00000000..07c04b7a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/ErrorResponse.java @@ -0,0 +1,35 @@ +package com.isp.backend.global.exception; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.http.HttpStatus; + +@Getter +@Setter +@NoArgsConstructor +public class ErrorResponse { + private String errorMessage; + private HttpStatus httpStatus; + private String code; + + + public ErrorResponse(HttpStatus status, String s) { + this.errorMessage = s; + this.httpStatus = status; + } + + public ErrorResponse(ErrorCode code) { + this.errorMessage = code.getMessage(); + this.httpStatus = code.getStatus(); + this.code = code.getCode(); + } + + @Override + public String toString() { + return "Error Message: " + errorMessage + "\n" + + "HTTP Status: " + httpStatus + "\n" + + "Error Code: " + code + "\n"; + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/GlobalExceptionHandler.java b/backend/src/main/java/com/isp/backend/global/exception/GlobalExceptionHandler.java new file mode 100644 index 00000000..8b21f520 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/GlobalExceptionHandler.java @@ -0,0 +1,24 @@ +package com.isp.backend.global.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleApiRequestException(Exception ex) { + ErrorResponse errorResponse = new ErrorResponse(HttpStatus.BAD_REQUEST, ex.getMessage()); + return new ResponseEntity<>(errorResponse, errorResponse.getHttpStatus()); + } + + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException e) { + ErrorCode errorCode = e.getErrorCode(); + ErrorResponse errorResponse = new ErrorResponse(errorCode); + return new ResponseEntity<>(errorResponse, errorCode.getStatus()); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/Image/DirectoryNameNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/Image/DirectoryNameNotFoundException.java new file mode 100644 index 00000000..48472e33 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/Image/DirectoryNameNotFoundException.java @@ -0,0 +1,13 @@ +package com.isp.backend.global.exception.Image; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + + +public class DirectoryNameNotFoundException extends CustomException { + + public DirectoryNameNotFoundException() { + super(ErrorCode.DIRECTORY_NAME_NOTFOUND); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/Image/ImageAlreadyExistingException.java b/backend/src/main/java/com/isp/backend/global/exception/Image/ImageAlreadyExistingException.java new file mode 100644 index 00000000..55c65e55 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/Image/ImageAlreadyExistingException.java @@ -0,0 +1,13 @@ +package com.isp.backend.global.exception.Image; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + + +public class ImageAlreadyExistingException extends CustomException { + + public ImageAlreadyExistingException() { + super(ErrorCode.IMAGE_ALREADY_EXISTING); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/common/AccessTokenInvalidException.java b/backend/src/main/java/com/isp/backend/global/exception/common/AccessTokenInvalidException.java new file mode 100644 index 00000000..f7e20f40 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/common/AccessTokenInvalidException.java @@ -0,0 +1,10 @@ +package com.isp.backend.global.exception.common; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class AccessTokenInvalidException extends CustomException { + public AccessTokenInvalidException() { + super(ErrorCode.ACCESS_TOKEN_IS_INVALID); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/common/AuthenticationFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/common/AuthenticationFailedException.java new file mode 100644 index 00000000..1e447b01 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/common/AuthenticationFailedException.java @@ -0,0 +1,10 @@ +package com.isp.backend.global.exception.common; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class AuthenticationFailedException extends CustomException { + + public AuthenticationFailedException() {super(ErrorCode.AUTHENTICATION_FAILED);} + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotActivatedException.java b/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotActivatedException.java new file mode 100644 index 00000000..d97d6f73 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotActivatedException.java @@ -0,0 +1,13 @@ +package com.isp.backend.global.exception.common; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class MemberNotActivatedException extends CustomException { + + public MemberNotActivatedException() { + super(ErrorCode.MEMBER_NOT_ACTIVATED); + } + +} + diff --git a/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotFoundException.java new file mode 100644 index 00000000..ca40fb63 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/common/MemberNotFoundException.java @@ -0,0 +1,13 @@ +package com.isp.backend.global.exception.common; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class MemberNotFoundException extends CustomException { + + public MemberNotFoundException() { + super(ErrorCode.MEMBER_NOT_FOUND); + } + +} + diff --git a/backend/src/main/java/com/isp/backend/global/exception/common/RefreshTokenInvalidException.java b/backend/src/main/java/com/isp/backend/global/exception/common/RefreshTokenInvalidException.java new file mode 100644 index 00000000..e8183a8e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/common/RefreshTokenInvalidException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.common; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class RefreshTokenInvalidException extends CustomException { + + public RefreshTokenInvalidException() { + super(ErrorCode.REFRESH_TOKEN_IS_INVALID); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/AmadeusSearchFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/AmadeusSearchFailedException.java new file mode 100644 index 00000000..bb8e78a1 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/AmadeusSearchFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class AmadeusSearchFailedException extends CustomException { + + public AmadeusSearchFailedException() { + super(ErrorCode.AMADEUS_SEARCH_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java new file mode 100644 index 00000000..084a7b6c --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateIsFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ExchangeRateIsFailedException extends CustomException { + + public ExchangeRateIsFailedException() { + super(ErrorCode.EXCHANGE_RATE_IS_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java new file mode 100644 index 00000000..8704f299 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/ExchangeRateSearchFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ExchangeRateSearchFailedException extends CustomException { + + public ExchangeRateSearchFailedException() { + super(ErrorCode.EXCHANGE_RATE_SEARCH_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/FlightNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/FlightNotFoundException.java new file mode 100644 index 00000000..ae02bae3 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/FlightNotFoundException.java @@ -0,0 +1,10 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class FlightNotFoundException extends CustomException { + + public FlightNotFoundException() {super(ErrorCode.FLIGHT_NOT_FOUND);} + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/NotYourFlightException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/NotYourFlightException.java new file mode 100644 index 00000000..7db101b2 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/NotYourFlightException.java @@ -0,0 +1,14 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class NotYourFlightException extends CustomException { + + public NotYourFlightException() { + super(ErrorCode.NOT_YOUR_FLIGHT); + } + +} + + diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/OpenWeatherSearchFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/OpenWeatherSearchFailedException.java new file mode 100644 index 00000000..957fec55 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/OpenWeatherSearchFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class OpenWeatherSearchFailedException extends CustomException { + + public OpenWeatherSearchFailedException() { + super(ErrorCode.OPEN_WEATHER_SEARCH_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/openApi/SkyScannerGenerateFailedException.java b/backend/src/main/java/com/isp/backend/global/exception/openApi/SkyScannerGenerateFailedException.java new file mode 100644 index 00000000..766f1021 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/openApi/SkyScannerGenerateFailedException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.openApi; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class SkyScannerGenerateFailedException extends CustomException { + + public SkyScannerGenerateFailedException() { + super(ErrorCode.SKY_SCANNER_GENERATE_FAILED); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/exception/receipt/ReceiptNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/receipt/ReceiptNotFoundException.java new file mode 100644 index 00000000..3a66b89b --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/receipt/ReceiptNotFoundException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.receipt; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ReceiptNotFoundException extends CustomException { + + public ReceiptNotFoundException() { + super(ErrorCode.RECEIPT_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/CheckListNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/CheckListNotFoundException.java new file mode 100644 index 00000000..ba6899ab --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/CheckListNotFoundException.java @@ -0,0 +1,15 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +import java.util.function.Supplier; + + +public class CheckListNotFoundException extends CustomException { + + public CheckListNotFoundException() { + super(ErrorCode.CHECK_LIST_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/CountryNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/CountryNotFoundException.java new file mode 100644 index 00000000..dae540ec --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/CountryNotFoundException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class CountryNotFoundException extends CustomException { + + public CountryNotFoundException() { + super(ErrorCode.COUNTRY_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/IataCodeNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/IataCodeNotFoundException.java new file mode 100644 index 00000000..e849f994 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/IataCodeNotFoundException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class IataCodeNotFoundException extends CustomException { + + public IataCodeNotFoundException() { + super(ErrorCode.IATA_CODE_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/ImageNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/ImageNotFoundException.java new file mode 100644 index 00000000..554374ff --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/ImageNotFoundException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ImageNotFoundException extends CustomException { + + public ImageNotFoundException() { + super(ErrorCode.IMAGE_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/NotYourScheduleException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/NotYourScheduleException.java new file mode 100644 index 00000000..01406b1a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/NotYourScheduleException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class NotYourScheduleException extends CustomException { + + public NotYourScheduleException() { + super(ErrorCode.NOT_YOUR_SCHEDULE); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/exception/schedule/ScheduleNotFoundException.java b/backend/src/main/java/com/isp/backend/global/exception/schedule/ScheduleNotFoundException.java new file mode 100644 index 00000000..729a986d --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/exception/schedule/ScheduleNotFoundException.java @@ -0,0 +1,12 @@ +package com.isp.backend.global.exception.schedule; + +import com.isp.backend.global.exception.CustomException; +import com.isp.backend.global.exception.ErrorCode; + +public class ScheduleNotFoundException extends CustomException { + + public ScheduleNotFoundException() { + super(ErrorCode.SCHEDULE_NOT_FOUND); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/jwt/JwtAccessDeniedHandler.java b/backend/src/main/java/com/isp/backend/global/jwt/JwtAccessDeniedHandler.java new file mode 100644 index 00000000..d561f67e --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/jwt/JwtAccessDeniedHandler.java @@ -0,0 +1,22 @@ +package com.isp.backend.global.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +public class JwtAccessDeniedHandler implements AccessDeniedHandler { + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { + // ì¸ê°€ë˜ì§€ ì•Šì€(ê¶Œí•œì´ ì—†ëŠ”) ìš”ì²­ì´ ë“¤ì–´ì™”ì„ ë•Œì˜ ì²˜ë¦¬ë¥¼ 담당 - `handle` 메서드는 예외 처리 ë° ë¡œê¹…ì„ ìˆ˜í–‰í•œë‹¤. + log.error("Forbidden Request : {}", request.getRequestURI()); + response.sendError(HttpServletResponse.SC_FORBIDDEN); + } +} diff --git a/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationEntryPoint.java b/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationEntryPoint.java new file mode 100644 index 00000000..1ca1530a --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationEntryPoint.java @@ -0,0 +1,22 @@ +package com.isp.backend.global.jwt; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +@Slf4j +@Component +public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { + // ì¸ì¦ë˜ì§€ ì•Šì€ ìš”ì²­ì´ ë“¤ì–´ì™”ì„ ë•Œì˜ ì²˜ë¦¬ë¥¼ 담당 - `commence` 메서드는 예외 처리 ë° ë¡œê¹…ì„ ìˆ˜í–‰í•œë‹¤. + log.error("ì¸ì¦ë˜ì§€ ì•Šì€ ìš”ì²­ìž…ë‹ˆë‹¤. : {}", request.getRequestURI()); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + } +} diff --git a/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationFilter.java b/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationFilter.java new file mode 100644 index 00000000..2061acb4 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/jwt/JwtAuthenticationFilter.java @@ -0,0 +1,56 @@ +package com.isp.backend.global.jwt; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + // HTTP 요청ì—ì„œ JWT Tokenì„ ì¶”ì¶œí•˜ê³ , 해당 토í°ì´ 유효한 경우 ìŠ¤í”„ë§ ì‹œíë¦¬í‹°ì˜ Authentication ê°ì²´ 설정 + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String TOKEN_PREFIX = "Bearer "; + + private final TokenProvider tokenProvider; + + // í•„í„°ë§ ìž‘ì—… 수행 - 토í°ì„ 추출해 유효성 검사 + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String token = resolveToken(request); + + if (token != null && tokenProvider.validateAccessToken(token)) { + Authentication authentication = tokenProvider.getAuthentication(token); + SecurityContextHolder.getContext().setAuthentication(authentication); + } else { + log.warn("Invalid or missing token"); + } + + filterChain.doFilter(request, response); + } + + // requestì—ì„œ Token 추출 + private String resolveToken(HttpServletRequest request) { + + String bearerToken = request.getHeader(AUTHORIZATION_HEADER); + + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { + return bearerToken.replace(TOKEN_PREFIX, ""); + } + + return null; + } +} diff --git a/backend/src/main/java/com/isp/backend/global/jwt/TokenProvider.java b/backend/src/main/java/com/isp/backend/global/jwt/TokenProvider.java new file mode 100644 index 00000000..08762c69 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/jwt/TokenProvider.java @@ -0,0 +1,170 @@ +package com.isp.backend.global.jwt; + +import com.isp.backend.global.security.CustomUserDetailsService; +import io.jsonwebtoken.*; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; + +@Slf4j +@Component +public class TokenProvider implements InitializingBean { + + private final CustomUserDetailsService customUserDetailsService; + + private final String secret; + private final Long accessExpirationTime; + private final Long refreshExpirationTime; + private Key key; + + + public TokenProvider(CustomUserDetailsService customUserDetailsService, + @Value("${jwt.secret}") String secret) { + this.customUserDetailsService = customUserDetailsService; + this.secret = secret; + this.accessExpirationTime = 3 * 60 * 60 * 1000L; // 3 hours + this.refreshExpirationTime = 15 * 24 * 60 * 60 * 1000L; // 15 days + + } + + @Override + public void afterPropertiesSet() throws Exception { + // jwt ì›ì‹œ 키를 디코딩하고 jwt ì„œëª…í•˜ëŠ”ë° ì‚¬ìš© + byte[] keyBytes = Decoders.BASE64.decode(secret); + this.key = Keys.hmacShaKeyFor(keyBytes); + } + + // Access Token ìƒì„± 메서드 + public String createAccessToken(String uid) { + + Claims claims = Jwts.claims() + .setSubject(uid); + claims.put("token_type", "accessToken"); + + Date expirationTime = getExpirationTime(accessExpirationTime); + + String accessToken = Jwts.builder() + .setClaims(claims) + .setExpiration(expirationTime) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + return accessToken; + + } + + // Refresh Token ìƒì„± 메서드 + public String createRefreshToken(String uid) { + + Claims claims = Jwts.claims() + .setSubject(uid); + claims.put("token_type", "refreshToken"); + + + Date expirationTime = getExpirationTime(refreshExpirationTime); + + String refreshToken = Jwts.builder() + .setClaims(claims) + .setExpiration(expirationTime) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + + return refreshToken; + } + + + public Authentication getAuthentication(String token) { + String uid = parseClaims(token).getSubject(); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(uid); + + Collection authorities = Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + + return new UsernamePasswordAuthenticationToken(userDetails, token, authorities); + } + + + // Access Token ê²€ì¦ + public boolean validateAccessToken(String accessToken) { + try { + Claims claims = parseClaims(accessToken); + + // í† í° íƒ€ìž… í™•ì¸ + String tokenType = (String) claims.get("token_type"); + if (!"accessToken".equals(tokenType)) { + return false; + } + // 만료 날짜 í™•ì¸ + return !claims.getExpiration().before(new Date()); + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.warn("ìž˜ëª»ëœ JWT 서명입니다.", e); + } catch (ExpiredJwtException e) { + log.warn("ë§Œë£Œëœ JWT입니다.", e); + } catch (UnsupportedJwtException e) { + log.warn("지ì›ë˜ì§€ 않는 JWT입니다.", e); + } catch (IllegalArgumentException e) { + log.warn("ìž˜ëª»ëœ JWT입니다.", e); + } + + return false; + } + + // Refresh Token ê²€ì¦ + public boolean validateRefreshToken(String refreshToken) { + try { + Claims claims = parseClaims(refreshToken); + + // í† í° íƒ€ìž… í™•ì¸ + String tokenType = (String) claims.get("token_type"); + if (!"refreshToken".equals(tokenType)) { + return false; + } + + // 만료 날짜 í™•ì¸ + return !claims.getExpiration().before(new Date()); + + } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { + log.warn("ìž˜ëª»ëœ JWT 서명입니다.", e); + } catch (ExpiredJwtException e) { + log.warn("ë§Œë£Œëœ JWT입니다.", e); + } catch (UnsupportedJwtException e) { + log.warn("지ì›ë˜ì§€ 않는 JWT입니다.", e); + } catch (IllegalArgumentException e) { + log.warn("ìž˜ëª»ëœ JWT입니다.", e); + } + + return false; + } + + // 토í°ì—ì„œ uid 가져오기 + public String getUid(String token) { + return parseClaims(token).getSubject(); + } + + // JWT 키 í•´ë… + private Claims parseClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(key) + .build() + .parseClaimsJws(token) + .getBody(); + } + + // í† í° ë§Œë£Œ 시간 가져오기 + private Date getExpirationTime(Long expirationTime) { + return new Date((new Date()).getTime() + expirationTime); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/s3/constant/S3BucketDirectory.java b/backend/src/main/java/com/isp/backend/global/s3/constant/S3BucketDirectory.java new file mode 100644 index 00000000..ee28bbbf --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/s3/constant/S3BucketDirectory.java @@ -0,0 +1,40 @@ +package com.isp.backend.global.s3.constant; + +import com.isp.backend.global.exception.Image.DirectoryNameNotFoundException; +import lombok.Getter; + +@Getter +public enum S3BucketDirectory { + IMAGE("HowAboutTrip-Backend-Image/"), // ë‚˜ë¼ ì‚¬ì§„ + PHOTO("HowAboutTrip-Backend-Photo/"), // 여행 중 í¬í†  + RECEIPT("HowAboutTrip-Backend-Receipt/"), // 여행 ì˜ìˆ˜ì¦ + WEATHER("HowAboutTrip-Backend-Weather/"), ; // 날씨 ì•„ì´ì½˜ + + private final String directory; + + S3BucketDirectory(String directory) { + this.directory = directory; + } + + + // ìž…ë ¥ëœ s3 ë””ë ‰í† ë¦¬ëª…ì´ ìœ íš¨í•œì§€ í™•ì¸ + public static boolean isValidDirectory(String directory) { + for (S3BucketDirectory bucketDirectory : values()) { + if (bucketDirectory.name().equalsIgnoreCase(directory)) { + return true; + } + } + return false; + } + + // 디렉터리 ì´ë¦„ì— ëŒ€ì‘하는 실제 디렉터리 경로를 반환 + public static String getDirectoryByName(String name) { + for (S3BucketDirectory bucketDirectory : values()) { + if (bucketDirectory.name().equalsIgnoreCase(name)) { + return bucketDirectory.getDirectory(); + } + } + throw new DirectoryNameNotFoundException(); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/s3/controller/S3ImageController.java b/backend/src/main/java/com/isp/backend/global/s3/controller/S3ImageController.java new file mode 100644 index 00000000..398f15fe --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/s3/controller/S3ImageController.java @@ -0,0 +1,25 @@ +package com.isp.backend.global.s3.controller; + +import com.isp.backend.global.s3.service.S3ImageService; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@RequestMapping("/images") +public class S3ImageController { + S3ImageService s3ImageService; + + @PostMapping + @ResponseStatus(HttpStatus.CREATED) + public String create(@RequestParam("file") MultipartFile file, + @RequestParam("directory") String directory) { + return s3ImageService.create(file, directory); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/s3/repository/S3ImageRepository.java b/backend/src/main/java/com/isp/backend/global/s3/repository/S3ImageRepository.java new file mode 100644 index 00000000..67b4b494 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/s3/repository/S3ImageRepository.java @@ -0,0 +1,56 @@ +package com.isp.backend.global.s3.repository; + +import com.isp.backend.global.s3.constant.S3BucketDirectory; +import io.awspring.cloud.s3.S3Resource; +import io.awspring.cloud.s3.S3Template; +import lombok.AccessLevel; +import lombok.experimental.FieldDefaults; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Repository; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +@Repository +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +public class S3ImageRepository { + S3Template s3template; + String bucketName; + + public S3ImageRepository(S3Template s3template, @Value("${spring.cloud.aws.s3.bucket}") String bucketName) { + this.s3template = s3template; + this.bucketName = bucketName; + } + + public String save(MultipartFile file, String directory) { + String uniqueFileName = UUID.randomUUID() + "-" + file.getOriginalFilename() ; + String key = directory + uniqueFileName; + final S3Resource result = s3template.upload(bucketName, key, getInputStream(file)); + return getUrl(result); + } + + public String find(String directory, String filename) { + String key = directory + filename; + final S3Resource result = s3template.download(bucketName, key); + return getUrl(result); + } + + private InputStream getInputStream(@RequestParam("file") MultipartFile file) { + try { + return file.getInputStream(); + } catch (IOException e) { + throw new RuntimeException(); + } + } + + private String getUrl(S3Resource s3Resource) { + try { + return s3Resource.getURL().toString(); + } catch (IOException e) { + throw new RuntimeException(); + } + } +} diff --git a/backend/src/main/java/com/isp/backend/global/s3/service/S3ImageService.java b/backend/src/main/java/com/isp/backend/global/s3/service/S3ImageService.java new file mode 100644 index 00000000..b3a44d87 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/s3/service/S3ImageService.java @@ -0,0 +1,36 @@ +package com.isp.backend.global.s3.service; + +import com.isp.backend.global.exception.Image.DirectoryNameNotFoundException; +import com.isp.backend.global.s3.constant.S3BucketDirectory; +import com.isp.backend.global.s3.repository.S3ImageRepository; +import lombok.AccessLevel; +import lombok.RequiredArgsConstructor; +import lombok.experimental.FieldDefaults; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) +@RequiredArgsConstructor +public class S3ImageService { + S3ImageRepository s3ImageRepository; + + public String create(MultipartFile file, String directory) { + String mappedDirectory = mapDirectory(directory); + return s3ImageRepository.save(file, mappedDirectory); + } + + public String get(String directory, String fileName) { + return s3ImageRepository.find(directory, fileName); + } + + private String mapDirectory(String directory) { + if (S3BucketDirectory.isValidDirectory(directory)) { + return S3BucketDirectory.getDirectoryByName(directory); + } else { + throw new DirectoryNameNotFoundException(); + } + } + + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/security/AmadeusConfig.java b/backend/src/main/java/com/isp/backend/global/security/AmadeusConfig.java new file mode 100644 index 00000000..493b59c8 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/security/AmadeusConfig.java @@ -0,0 +1,24 @@ +package com.isp.backend.global.security; + +import com.amadeus.Amadeus; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class AmadeusConfig { + + @Value("${api-key.amadeus.accessKey}") + private String apiKey; + + @Value("${api-key.amadeus.secretKey}") + private String apiSecret; + + @Bean + public Amadeus getAmadeus(){ + return Amadeus + .builder(apiKey, apiSecret) + .build(); + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/security/CustomUserDetails.java b/backend/src/main/java/com/isp/backend/global/security/CustomUserDetails.java new file mode 100644 index 00000000..7eda83dc --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/security/CustomUserDetails.java @@ -0,0 +1,56 @@ +package com.isp.backend.global.security; + +import com.isp.backend.domain.member.entity.Member; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@RequiredArgsConstructor +public class CustomUserDetails implements UserDetails { + + private final Member member; + + public Member getMemeber() { + return member; + } + + @Override + public String getUsername() { //유저 ì´ë©”ì¼ ê°€ì ¸ì˜¤ê¸° + return member.getUid(); + } + + @Override + public String getPassword() { + return null; // 패스워드가 없으므로 null 반환 + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } + + @Override + public Collection getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")); + } + +} diff --git a/backend/src/main/java/com/isp/backend/global/security/CustomUserDetailsService.java b/backend/src/main/java/com/isp/backend/global/security/CustomUserDetailsService.java new file mode 100644 index 00000000..2dff7f95 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/security/CustomUserDetailsService.java @@ -0,0 +1,31 @@ +package com.isp.backend.global.security; + +import com.isp.backend.domain.member.entity.Member; +import com.isp.backend.domain.member.repository.MemberRepository; +import com.isp.backend.global.exception.common.MemberNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + + private final MemberRepository memberRepository; + + @Override + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + + // 유저 찾기 + Optional memberOptional = memberRepository.findByUid(username); + Member member = memberOptional.orElseThrow(() -> new MemberNotFoundException()); + + return new CustomUserDetails(member); + + } + +} \ No newline at end of file diff --git a/backend/src/main/java/com/isp/backend/global/security/WebSecurityConfig.java b/backend/src/main/java/com/isp/backend/global/security/WebSecurityConfig.java new file mode 100644 index 00000000..9033bb57 --- /dev/null +++ b/backend/src/main/java/com/isp/backend/global/security/WebSecurityConfig.java @@ -0,0 +1,82 @@ +package com.isp.backend.global.security; + +import com.isp.backend.global.jwt.JwtAccessDeniedHandler; +import com.isp.backend.global.jwt.JwtAuthenticationEntryPoint; +import com.isp.backend.global.jwt.JwtAuthenticationFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer; +import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +@RequiredArgsConstructor +@Configuration +@EnableWebSecurity +public class WebSecurityConfig { + + private static final String[] AUTH_WHITE_LIST = { + "/members/login", + "/members/refresh", + "/members/test", + "/gpt/schedules", + "/error", + "/h2-console/**" + }; + private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private final JwtAccessDeniedHandler jwtAccessDeniedHandler; + private final JwtAuthenticationFilter jwtAuthenticationFilter; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // 들어오는 ìš”ì²­ì„ ì²˜ë¦¬, 애플리케ì´ì…˜ì„ 보안해주는 í•„í„° + + http + .cors(corsConfigurer -> corsConfigurer.configurationSource(corsConfigurationSource())) // cors 구성 + .csrf(CsrfConfigurer::disable) // csrf 보호 비활성화 + .formLogin(AbstractHttpConfigurer::disable) // í¼ ê¸°ë°˜ login 비활성화 + .httpBasic(HttpBasicConfigurer::disable) // HTTP basic ì¸ì¦ 비활성화 + .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 세션 ìƒì„± X + + .authorizeHttpRequests(authorizeRequests -> + authorizeRequests + .requestMatchers(AUTH_WHITE_LIST).permitAll() // AUTH_WHITE_LISTì— í¬í•¨ëœ URLì€ ì¸ì¦ ì—†ì´ ì ‘ê·¼ 가능 + .anyRequest().authenticated() // 나머지 모든 ìš”ì²­ì€ ì¸ì¦ í•„ìš” + ) + + .exceptionHandling(exceptionHandlingConfigurer -> exceptionHandlingConfigurer // 예외처리 구성 - ì¸ì¦ 진입 ì  + 엑세스 거부 헨들러 지정 + .authenticationEntryPoint(jwtAuthenticationEntryPoint) + .accessDeniedHandler(jwtAccessDeniedHandler)); + + http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // JWT 기반 ì¸ì¦ 처리 + + return http.build(); + } + + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + + CorsConfiguration configuration = new CorsConfiguration(); + + configuration.setAllowCredentials(true); // 쿠키 ë° ìžê²© ì¦ëª… 허용 + configuration.addAllowedOrigin("*"); // 모든 IPì— ì‘답 허용 + configuration.addAllowedMethod("*"); // 모든 Method ì‘답 허용 + configuration.addAllowedHeader("*"); // 모든 í—¤ë” ì‘ë‹µì— í—ˆìš© + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } + + +} \ No newline at end of file diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml new file mode 100644 index 00000000..c430f506 --- /dev/null +++ b/backend/src/main/resources/application.yml @@ -0,0 +1,42 @@ +spring: + jpa: + database: MYSQL + show-sql: true + database-platform: org.hibernate.dialect.MySQL8Dialect + hibernate: + ddl-auto: update + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USERNAME} + password: ${DB_PASSWORD} + servlet: + multipart: + max-file-size: 20MB + max-request-size: 20MB + cloud: + aws: + credentials: + access-key: ${AWS_ACCESS_KEY_ID} + secret-key: ${AWS_SECRET_ACCESS_KEY} + region: + static: ${AWS_S3_REGION} + s3: + bucket: ${AWS_S3_BUCKET_NAME} + stack: + auto: false + +server: + servlet: + context-path: /api + +jwt: + secret: ${JWT_SECRET_KEY} + +api-key: + gpt-trip: ${GPT_API_KEY} + amadeus: + accessKey: ${AMADEUS_ACCESS_KEY} + secretKey: ${AMADEUS_SECRET_KEY} + open-weather: ${OPEN_WEATHER_API_KEY} + exchange-rate: ${EXCHANGE_RATE_API_KEY} \ No newline at end of file diff --git a/backend/src/main/resources/weather_mapping.json b/backend/src/main/resources/weather_mapping.json new file mode 100644 index 00000000..3bdbe225 --- /dev/null +++ b/backend/src/main/resources/weather_mapping.json @@ -0,0 +1,57 @@ +{ + "200": "천둥번개와 약한 비", + "201": "비를 ë™ë°˜í•œ 천둥번개", + "202": "ê°•í•œ 비를 ë™ë°˜í•œ 천둥번개", + "210": "약한 천둥번개", + "211": "천둥번개", + "212": "ê°•í•œ 천둥번개", + "221": "불규칙ì ì¸ 천둥번개", + "230": "약한 ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 천둥번개", + "231": "ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 천둥번개", + "232": "ê°•í•œ ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 천둥번개", + "300": "가벼운 ì´ìŠ¬ë¹„", + "301": "ì´ìŠ¬ë¹„", + "302": "ê°•í•œ ì´ìŠ¬ë¹„", + "310": "가벼운 ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 비", + "311": "ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 비", + "312": "ê°•í•œ ì´ìŠ¬ë¹„를 ë™ë°˜í•œ 비", + "313": "소나기와 ì´ìŠ¬ë¹„", + "314": "ê°•í•œ 소나기와 ì´ìŠ¬ë¹„", + "321": "소나기 ì´ìŠ¬ë¹„", + "500": "약한 비", + "501": "보통 비", + "502": "ê°•í•œ 비", + "503": "매우 ê°•í•œ 비", + "504": "극한 비", + "511": "얼어 붙는 비", + "520": "약한 소나기 비", + "521": "소나기 비", + "522": "ê°•í•œ 소나기 비", + "531": "불규칙ì ì¸ 소나기 비", + "600": "가벼운 눈", + "601": "눈", + "602": "ê°•í•œ 눈", + "611": "진눈깨비", + "612": "가벼운 소나기 진눈깨비", + "613": "소나기 진눈깨비", + "615": "가벼운 비와 눈", + "616": "비와 눈", + "620": "가벼운 소나기 눈", + "621": "소나기 눈", + "622": "ê°•í•œ 소나기 눈", + "701": "ì˜…ì€ ì•ˆê°œ", + "711": "안개", + "721": "연무", + "731": "모래/먼지 바람", + "741": "안개", + "751": "모래", + "761": "먼지", + "762": "화산재", + "771": "ëŒí’", + "781": "토네ì´ë„", + "800": "맑ìŒ", + "801": "ì¡°ê¸ˆì˜ êµ¬ë¦„: 11-25%", + "802": "í©ì–´ì§„ 구름: 25-50%", + "803": "부서진 구름: 51-84%", + "804": "í린 구름: 85-100%" +} diff --git a/backend/src/test/java/com/isp/backend/BackendApplicationTests.java b/backend/src/test/java/com/isp/backend/BackendApplicationTests.java new file mode 100644 index 00000000..124d4849 --- /dev/null +++ b/backend/src/test/java/com/isp/backend/BackendApplicationTests.java @@ -0,0 +1,11 @@ +package com.isp.backend; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest(classes = BackendApplicationTests.class) +class BackendApplicationTests { + @Test + void contextLoads() { + } +} diff --git "a/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\232\224\354\225\275 \352\263\204\355\232\215\354\204\234.hwp" "b/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\232\224\354\225\275 \352\263\204\355\232\215\354\204\234.hwp" new file mode 100644 index 00000000..b9eb1a54 Binary files /dev/null and "b/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\232\224\354\225\275 \352\263\204\355\232\215\354\204\234.hwp" differ diff --git "a/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\240\234\354\225\210\354\204\234.pptx" "b/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\240\234\354\225\210\354\204\234.pptx" new file mode 100644 index 00000000..d7512b02 Binary files /dev/null and "b/docs/[\354\227\254\355\226\211\354\226\264\353\225\214] \354\240\234\354\225\210\354\204\234.pptx" differ