From fdc58b428b2e248187c0953c49c00403edd91ad0 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Thu, 11 May 2023 17:04:17 +1000 Subject: [PATCH 01/29] Rename main folders and write sql backend adaptor --- .github/workflows/deploy_backend.yml | 6 +- .github/workflows/deploy_frontend.yml | 6 +- .gitignore | 3 +- backend/.gitignore | 2 + backend/Cargo.lock | 3014 ++++++++++++++ backend/Cargo.toml | 15 + backend/adaptors/sql/Cargo.toml | 14 + backend/adaptors/sql/src/entity/event.rs | 42 + backend/adaptors/sql/src/entity/mod.rs | 7 + backend/adaptors/sql/src/entity/person.rs | 48 + backend/adaptors/sql/src/entity/prelude.rs | 5 + backend/adaptors/sql/src/entity/stats.rs | 27 + backend/adaptors/sql/src/lib.rs | 152 + backend/adaptors/sql/src/migration/mod.rs | 12 + .../sql/src/migration/setup_tables.rs | 120 + backend/data/Cargo.toml | 9 + backend/data/src/adaptor.rs | 37 + backend/data/src/event.rs | 16 + backend/data/src/lib.rs | 4 + backend/data/src/person.rs | 8 + backend/data/src/stats.rs | 4 + backend/src/main.rs | 33 + .../manifest.json | 0 .../popup.html | 0 .../res/icon128.png | Bin .../res/icon16.png | Bin .../res/icon32.png | Bin .../res/icon48.png | Bin crabfit-backend/.eslintrc.js | 50 - crabfit-backend/.gcloudignore | 12 - crabfit-backend/.gitignore | 8 - crabfit-backend/app.yaml | 7 - crabfit-backend/cron.yaml | 9 - crabfit-backend/index.js | 61 - crabfit-backend/package.json | 34 - crabfit-backend/res/adjectives.json | 201 - crabfit-backend/res/crabs.json | 47 - crabfit-backend/routes/createEvent.js | 84 - crabfit-backend/routes/createPerson.js | 65 - crabfit-backend/routes/getEvent.js | 29 - crabfit-backend/routes/getPeople.js | 20 - crabfit-backend/routes/index.js | 10 - crabfit-backend/routes/login.js | 35 - crabfit-backend/routes/stats.js | 29 - crabfit-backend/routes/taskCleanup.js | 48 - crabfit-backend/routes/taskRemoveOrphans.js | 48 - crabfit-backend/routes/updatePerson.js | 40 - crabfit-backend/swagger.yaml | 245 -- crabfit-backend/yarn.lock | 3686 ----------------- {crabfit-frontend => frontend}/.eslintrc.js | 0 {crabfit-frontend => frontend}/.gcloudignore | 0 {crabfit-frontend => frontend}/.gitignore | 0 {crabfit-frontend => frontend}/app.yaml | 0 {crabfit-frontend => frontend}/index.html | 0 {crabfit-frontend => frontend}/jsconfig.json | 0 {crabfit-frontend => frontend}/package.json | 0 .../public/.well-known/assetlinks.json | 0 .../microsoft-identity-association.json | 0 .../public/favicon.ico | Bin .../public/fonts/karla-italic-variable.ttf | Bin .../public/fonts/karla-variable.ttf | Bin .../public/fonts/molot.woff | Bin .../public/fonts/molot.woff2 | Bin .../public/fonts/samuraibob.woff | Bin .../public/fonts/samuraibob.woff2 | Bin .../public/i18n/de/common.json | 0 .../public/i18n/de/event.json | 0 .../public/i18n/de/help.json | 0 .../public/i18n/de/home.json | 0 .../public/i18n/de/privacy.json | 0 .../public/i18n/en-GB/common.json | 0 .../public/i18n/en-GB/event.json | 0 .../public/i18n/en-GB/help.json | 0 .../public/i18n/en-GB/home.json | 0 .../public/i18n/en-GB/privacy.json | 0 .../public/i18n/en/common.json | 0 .../public/i18n/en/event.json | 0 .../public/i18n/en/help.json | 0 .../public/i18n/en/home.json | 0 .../public/i18n/en/privacy.json | 0 .../public/i18n/es/common.json | 0 .../public/i18n/es/event.json | 0 .../public/i18n/es/help.json | 0 .../public/i18n/es/home.json | 0 .../public/i18n/es/privacy.json | 0 .../public/i18n/fr/common.json | 0 .../public/i18n/fr/event.json | 0 .../public/i18n/fr/help.json | 0 .../public/i18n/fr/home.json | 0 .../public/i18n/fr/privacy.json | 0 .../public/i18n/hi/common.json | 0 .../public/i18n/hi/event.json | 0 .../public/i18n/hi/help.json | 0 .../public/i18n/hi/home.json | 0 .../public/i18n/hi/privacy.json | 0 .../public/i18n/id/common.json | 0 .../public/i18n/id/event.json | 0 .../public/i18n/id/help.json | 0 .../public/i18n/id/home.json | 0 .../public/i18n/id/privacy.json | 0 .../public/i18n/it/common.json | 0 .../public/i18n/it/event.json | 0 .../public/i18n/it/help.json | 0 .../public/i18n/it/home.json | 0 .../public/i18n/it/privacy.json | 0 .../public/i18n/ja/common.json | 0 .../public/i18n/ja/event.json | 0 .../public/i18n/ja/home.json | 0 .../public/i18n/ja/privacy.json | 0 .../public/i18n/ko/common.json | 0 .../public/i18n/ko/event.json | 0 .../public/i18n/ko/help.json | 0 .../public/i18n/ko/home.json | 0 .../public/i18n/ko/privacy.json | 0 .../public/i18n/pl/common.json | 0 .../public/i18n/pl/home.json | 0 .../public/i18n/pl/privacy.json | 0 .../public/i18n/pt-BR/common.json | 0 .../public/i18n/pt-BR/event.json | 0 .../public/i18n/pt-BR/help.json | 0 .../public/i18n/pt-BR/home.json | 0 .../public/i18n/pt-BR/privacy.json | 0 .../public/i18n/pt_PT/common.json | 0 .../public/i18n/pt_PT/event.json | 0 .../public/i18n/pt_PT/help.json | 0 .../public/i18n/pt_PT/home.json | 0 .../public/i18n/pt_PT/privacy.json | 0 .../public/i18n/ru/common.json | 0 .../public/i18n/ru/event.json | 0 .../public/i18n/ru/help.json | 0 .../public/i18n/ru/home.json | 0 .../public/i18n/ru/privacy.json | 0 .../public/index.css | 0 .../public/logo-icon.png | Bin .../public/logo192.png | Bin .../public/logo512.png | Bin .../public/manifest.json | 0 .../public/robots.txt | 0 .../public/sitemap.xml | 0 {crabfit-frontend => frontend}/public/sw.js | 0 {crabfit-frontend => frontend}/src/App.jsx | 0 .../AvailabilityEditor/AvailabilityEditor.jsx | 0 .../AvailabilityEditor.styles.js | 0 .../AvailabilityViewer/AvailabilityViewer.jsx | 0 .../AvailabilityViewer.styles.js | 0 .../src/components/Button/Button.jsx | 0 .../src/components/Button/Button.styles.js | 0 .../CalendarField/CalendarField.jsx | 0 .../CalendarField/CalendarField.styles.js | 0 .../src/components/Center/Center.js | 0 .../src/components/Donate/Donate.jsx | 0 .../src/components/Donate/Donate.styles.js | 0 .../src/components/Egg/Egg.jsx | 0 .../src/components/Egg/Egg.styles.js | 0 .../src/components/Error/Error.jsx | 0 .../src/components/Error/Error.styles.js | 0 .../src/components/Footer/Footer.jsx | 0 .../src/components/Footer/Footer.styles.js | 0 .../GoogleCalendar/GoogleCalendar.jsx | 0 .../GoogleCalendar/GoogleCalendar.styles.js | 0 .../src/components/Legend/Legend.jsx | 0 .../src/components/Legend/Legend.styles.js | 0 .../src/components/Loading/Loading.jsx | 0 .../src/components/Loading/Loading.styles.js | 0 .../src/components/Logo/Logo.jsx | 0 .../src/components/Logo/Logo.styles.js | 0 .../OutlookCalendar/OutlookCalendar.jsx | 0 .../src/components/Recents/Recents.jsx | 0 .../src/components/Recents/Recents.styles.js | 0 .../components/SelectField/SelectField.jsx | 0 .../SelectField/SelectField.styles.js | 0 .../src/components/Settings/Settings.jsx | 0 .../components/Settings/Settings.styles.js | 0 .../src/components/TextField/TextField.jsx | 0 .../components/TextField/TextField.styles.js | 0 .../TimeRangeField/TimeRangeField.jsx | 0 .../TimeRangeField/TimeRangeField.styles.js | 0 .../components/ToggleField/ToggleField.jsx | 0 .../ToggleField/ToggleField.styles.js | 0 .../TranslateDialog/TranslateDialog.jsx | 0 .../TranslateDialog/TranslateDialog.styles.js | 0 .../src/components/index.js | 0 .../src/i18n/index.js | 0 .../src/i18n/locales.js | 0 {crabfit-frontend => frontend}/src/index.jsx | 0 .../src/pages/Create/Create.jsx | 0 .../src/pages/Create/Create.styles.js | 0 .../src/pages/Event/Event.jsx | 0 .../src/pages/Event/Event.styles.js | 0 .../src/pages/Help/Help.jsx | 0 .../src/pages/Help/Help.styles.js | 0 .../src/pages/Home/Home.jsx | 0 .../src/pages/Home/Home.styles.js | 0 .../src/pages/Privacy/Privacy.jsx | 0 .../src/pages/Privacy/Privacy.styles.js | 0 .../src/pages/index.js | 0 .../src/res/google.svg | 0 .../src/res/logo.svg | 0 .../src/res/outlook.svg | 0 .../src/res/paypal.svg | 0 .../src/res/timezones.json | 0 .../src/res/video_thumb.jpg | Bin .../src/services/index.js | 0 .../src/stores/index.js | 0 .../src/stores/localeUpdateStore.js | 0 .../src/stores/recentsStore.js | 0 .../src/stores/settingsStore.js | 0 .../src/stores/translateStore.js | 0 .../src/stores/twaStore.js | 0 .../src/utils/index.js | 0 {crabfit-frontend => frontend}/vite.config.js | 0 {crabfit-frontend => frontend}/yarn.lock | 0 212 files changed, 3577 insertions(+), 4775 deletions(-) create mode 100644 backend/.gitignore create mode 100644 backend/Cargo.lock create mode 100644 backend/Cargo.toml create mode 100644 backend/adaptors/sql/Cargo.toml create mode 100644 backend/adaptors/sql/src/entity/event.rs create mode 100644 backend/adaptors/sql/src/entity/mod.rs create mode 100644 backend/adaptors/sql/src/entity/person.rs create mode 100644 backend/adaptors/sql/src/entity/prelude.rs create mode 100644 backend/adaptors/sql/src/entity/stats.rs create mode 100644 backend/adaptors/sql/src/lib.rs create mode 100644 backend/adaptors/sql/src/migration/mod.rs create mode 100644 backend/adaptors/sql/src/migration/setup_tables.rs create mode 100644 backend/data/Cargo.toml create mode 100644 backend/data/src/adaptor.rs create mode 100644 backend/data/src/event.rs create mode 100644 backend/data/src/lib.rs create mode 100644 backend/data/src/person.rs create mode 100644 backend/data/src/stats.rs create mode 100644 backend/src/main.rs rename {crabfit-browser-extension => browser-extension}/manifest.json (100%) rename {crabfit-browser-extension => browser-extension}/popup.html (100%) rename {crabfit-browser-extension => browser-extension}/res/icon128.png (100%) rename {crabfit-browser-extension => browser-extension}/res/icon16.png (100%) rename {crabfit-browser-extension => browser-extension}/res/icon32.png (100%) rename {crabfit-browser-extension => browser-extension}/res/icon48.png (100%) delete mode 100644 crabfit-backend/.eslintrc.js delete mode 100644 crabfit-backend/.gcloudignore delete mode 100644 crabfit-backend/.gitignore delete mode 100644 crabfit-backend/app.yaml delete mode 100644 crabfit-backend/cron.yaml delete mode 100644 crabfit-backend/index.js delete mode 100644 crabfit-backend/package.json delete mode 100644 crabfit-backend/res/adjectives.json delete mode 100644 crabfit-backend/res/crabs.json delete mode 100644 crabfit-backend/routes/createEvent.js delete mode 100644 crabfit-backend/routes/createPerson.js delete mode 100644 crabfit-backend/routes/getEvent.js delete mode 100644 crabfit-backend/routes/getPeople.js delete mode 100644 crabfit-backend/routes/index.js delete mode 100644 crabfit-backend/routes/login.js delete mode 100644 crabfit-backend/routes/stats.js delete mode 100644 crabfit-backend/routes/taskCleanup.js delete mode 100644 crabfit-backend/routes/taskRemoveOrphans.js delete mode 100644 crabfit-backend/routes/updatePerson.js delete mode 100644 crabfit-backend/swagger.yaml delete mode 100644 crabfit-backend/yarn.lock rename {crabfit-frontend => frontend}/.eslintrc.js (100%) rename {crabfit-frontend => frontend}/.gcloudignore (100%) rename {crabfit-frontend => frontend}/.gitignore (100%) rename {crabfit-frontend => frontend}/app.yaml (100%) rename {crabfit-frontend => frontend}/index.html (100%) rename {crabfit-frontend => frontend}/jsconfig.json (100%) rename {crabfit-frontend => frontend}/package.json (100%) rename {crabfit-frontend => frontend}/public/.well-known/assetlinks.json (100%) rename {crabfit-frontend => frontend}/public/.well-known/microsoft-identity-association.json (100%) rename {crabfit-frontend => frontend}/public/favicon.ico (100%) rename {crabfit-frontend => frontend}/public/fonts/karla-italic-variable.ttf (100%) rename {crabfit-frontend => frontend}/public/fonts/karla-variable.ttf (100%) rename {crabfit-frontend => frontend}/public/fonts/molot.woff (100%) rename {crabfit-frontend => frontend}/public/fonts/molot.woff2 (100%) rename {crabfit-frontend => frontend}/public/fonts/samuraibob.woff (100%) rename {crabfit-frontend => frontend}/public/fonts/samuraibob.woff2 (100%) rename {crabfit-frontend => frontend}/public/i18n/de/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/de/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/de/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/de/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/de/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en-GB/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en-GB/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en-GB/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en-GB/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en-GB/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/en/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/es/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/es/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/es/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/es/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/es/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/fr/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/fr/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/fr/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/fr/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/fr/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/hi/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/hi/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/hi/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/hi/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/hi/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/id/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/id/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/id/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/id/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/id/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/it/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/it/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/it/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/it/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/it/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ja/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ja/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ja/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ja/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ko/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ko/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ko/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ko/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ko/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pl/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pl/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pl/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt-BR/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt-BR/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt-BR/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt-BR/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt-BR/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt_PT/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt_PT/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt_PT/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt_PT/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/pt_PT/privacy.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ru/common.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ru/event.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ru/help.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ru/home.json (100%) rename {crabfit-frontend => frontend}/public/i18n/ru/privacy.json (100%) rename {crabfit-frontend => frontend}/public/index.css (100%) rename {crabfit-frontend => frontend}/public/logo-icon.png (100%) rename {crabfit-frontend => frontend}/public/logo192.png (100%) rename {crabfit-frontend => frontend}/public/logo512.png (100%) rename {crabfit-frontend => frontend}/public/manifest.json (100%) rename {crabfit-frontend => frontend}/public/robots.txt (100%) rename {crabfit-frontend => frontend}/public/sitemap.xml (100%) rename {crabfit-frontend => frontend}/public/sw.js (100%) rename {crabfit-frontend => frontend}/src/App.jsx (100%) rename {crabfit-frontend => frontend}/src/components/AvailabilityEditor/AvailabilityEditor.jsx (100%) rename {crabfit-frontend => frontend}/src/components/AvailabilityEditor/AvailabilityEditor.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/AvailabilityViewer/AvailabilityViewer.jsx (100%) rename {crabfit-frontend => frontend}/src/components/AvailabilityViewer/AvailabilityViewer.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Button/Button.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Button/Button.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/CalendarField/CalendarField.jsx (100%) rename {crabfit-frontend => frontend}/src/components/CalendarField/CalendarField.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Center/Center.js (100%) rename {crabfit-frontend => frontend}/src/components/Donate/Donate.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Donate/Donate.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Egg/Egg.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Egg/Egg.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Error/Error.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Error/Error.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Footer/Footer.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Footer/Footer.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/GoogleCalendar/GoogleCalendar.jsx (100%) rename {crabfit-frontend => frontend}/src/components/GoogleCalendar/GoogleCalendar.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Legend/Legend.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Legend/Legend.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Loading/Loading.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Loading/Loading.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Logo/Logo.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Logo/Logo.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/OutlookCalendar/OutlookCalendar.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Recents/Recents.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Recents/Recents.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/SelectField/SelectField.jsx (100%) rename {crabfit-frontend => frontend}/src/components/SelectField/SelectField.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/Settings/Settings.jsx (100%) rename {crabfit-frontend => frontend}/src/components/Settings/Settings.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/TextField/TextField.jsx (100%) rename {crabfit-frontend => frontend}/src/components/TextField/TextField.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/TimeRangeField/TimeRangeField.jsx (100%) rename {crabfit-frontend => frontend}/src/components/TimeRangeField/TimeRangeField.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/ToggleField/ToggleField.jsx (100%) rename {crabfit-frontend => frontend}/src/components/ToggleField/ToggleField.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/TranslateDialog/TranslateDialog.jsx (100%) rename {crabfit-frontend => frontend}/src/components/TranslateDialog/TranslateDialog.styles.js (100%) rename {crabfit-frontend => frontend}/src/components/index.js (100%) rename {crabfit-frontend => frontend}/src/i18n/index.js (100%) rename {crabfit-frontend => frontend}/src/i18n/locales.js (100%) rename {crabfit-frontend => frontend}/src/index.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Create/Create.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Create/Create.styles.js (100%) rename {crabfit-frontend => frontend}/src/pages/Event/Event.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Event/Event.styles.js (100%) rename {crabfit-frontend => frontend}/src/pages/Help/Help.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Help/Help.styles.js (100%) rename {crabfit-frontend => frontend}/src/pages/Home/Home.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Home/Home.styles.js (100%) rename {crabfit-frontend => frontend}/src/pages/Privacy/Privacy.jsx (100%) rename {crabfit-frontend => frontend}/src/pages/Privacy/Privacy.styles.js (100%) rename {crabfit-frontend => frontend}/src/pages/index.js (100%) rename {crabfit-frontend => frontend}/src/res/google.svg (100%) rename {crabfit-frontend => frontend}/src/res/logo.svg (100%) rename {crabfit-frontend => frontend}/src/res/outlook.svg (100%) rename {crabfit-frontend => frontend}/src/res/paypal.svg (100%) rename {crabfit-frontend => frontend}/src/res/timezones.json (100%) rename {crabfit-frontend => frontend}/src/res/video_thumb.jpg (100%) rename {crabfit-frontend => frontend}/src/services/index.js (100%) rename {crabfit-frontend => frontend}/src/stores/index.js (100%) rename {crabfit-frontend => frontend}/src/stores/localeUpdateStore.js (100%) rename {crabfit-frontend => frontend}/src/stores/recentsStore.js (100%) rename {crabfit-frontend => frontend}/src/stores/settingsStore.js (100%) rename {crabfit-frontend => frontend}/src/stores/translateStore.js (100%) rename {crabfit-frontend => frontend}/src/stores/twaStore.js (100%) rename {crabfit-frontend => frontend}/src/utils/index.js (100%) rename {crabfit-frontend => frontend}/vite.config.js (100%) rename {crabfit-frontend => frontend}/yarn.lock (100%) diff --git a/.github/workflows/deploy_backend.yml b/.github/workflows/deploy_backend.yml index 951c2e2..e9e34c5 100644 --- a/.github/workflows/deploy_backend.yml +++ b/.github/workflows/deploy_backend.yml @@ -3,7 +3,7 @@ name: Deploy Backend on: push: branches: ['main'] - paths: ['crabfit-backend/**'] + paths: ['backend/**'] jobs: deploy: @@ -11,7 +11,7 @@ jobs: defaults: run: - working-directory: crabfit-backend + working-directory: backend permissions: contents: read @@ -33,5 +33,5 @@ jobs: - id: deploy uses: google-github-actions/deploy-appengine@v0 with: - working_directory: crabfit-backend + working_directory: backend version: v1 diff --git a/.github/workflows/deploy_frontend.yml b/.github/workflows/deploy_frontend.yml index 6ba43c7..e495b49 100644 --- a/.github/workflows/deploy_frontend.yml +++ b/.github/workflows/deploy_frontend.yml @@ -3,7 +3,7 @@ name: Deploy Frontend on: push: branches: ['main'] - paths: ['crabfit-frontend/**'] + paths: ['frontend/**'] jobs: deploy: @@ -11,7 +11,7 @@ jobs: defaults: run: - working-directory: crabfit-frontend + working-directory: frontend permissions: contents: read @@ -33,5 +33,5 @@ jobs: - id: deploy uses: google-github-actions/deploy-appengine@v0 with: - working_directory: crabfit-frontend + working_directory: frontend version: v1 diff --git a/.gitignore b/.gitignore index 6e56161..fb6ff20 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /graphics .DS_Store -/crabfit-browser-extension/*.zip +/browser-extension/*.zip +js-backend diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..c5dd462 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,2 @@ +target +.env diff --git a/backend/Cargo.lock b/backend/Cargo.lock new file mode 100644 index 0000000..6bb802a --- /dev/null +++ b/backend/Cargo.lock @@ -0,0 +1,3014 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa3dc5f2a8564f07759c008b9109dc0d39de92a88d5588b8a5036d286383afb" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", + "tokio", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-stream" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + +[[package]] +name = "async-trait" +version = "0.1.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "atoi" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8175979259124331c1d7bf6586ee7e0da434155e4b2d48ec2c8386281d8df39" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "bae" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bigdecimal" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "log", +] + +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown 0.13.2", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "bumpalo" +version = "3.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" + +[[package]] +name = "bytecheck" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "serde", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "textwrap", +] + +[[package]] +name = "clap_derive" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + +[[package]] +name = "concurrent-queue" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crabfit_backend" +version = "1.1.0" +dependencies = [ + "axum", + "data", + "dotenv", + "serde", + "sql-adaptor", + "tokio", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-bigint" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c6a1d5fa1de37e071642dfa44ec552ca5b299adb128fab16138e24b548fd21" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.15", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "data" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", +] + +[[package]] +name = "der" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" +dependencies = [ + "const-oid", + "crypto-bigint", + "pem-rfc7468", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "errno" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + +[[package]] +name = "fastrand" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" +dependencies = [ + "instant", +] + +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "pin-project", + "spin 0.9.8", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-lite" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" + +[[package]] +name = "futures-util" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "hashlink" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +dependencies = [ + "hashbrown 0.12.3", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "httpdate" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "hyper" +version = "0.14.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "io-lifetimes" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +dependencies = [ + "hermit-abi 0.3.1", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin 0.5.2", +] + +[[package]] +name = "libc" +version = "0.2.144" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "libsqlite3-sys" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", + "value-bag", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "md-5" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +dependencies = [ + "digest", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.45.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2399c9463abc5f909349d8aa9ba080e0b88b3ce2885389b60b993f39b1a56905" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "openssl" +version = "0.10.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01b8574602df80f7b85fdfc5392fa884a4e3b3f4f35402c070ab34c3d3f78d56" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e17f59264b2809d77ae94f0e1ebabc434773f370d6ca667bd223ea10e06cc7e" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "os_str_bytes" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" + +[[package]] +name = "ouroboros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parking" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + +[[package]] +name = "pem-rfc7468" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01de5d978f34aa4b2296576379fcc416034702fd94117c56ffd8a1a767cefb30" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78f66c04ccc83dd4486fd46c33896f4e17b24a7a3a6400dedc48ed0ddd72320" +dependencies = [ + "der", + "pkcs8", + "zeroize", +] + +[[package]] +name = "pkcs8" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" +dependencies = [ + "der", + "spki", + "zeroize", +] + +[[package]] +name = "pkg-config" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" + +[[package]] +name = "polling" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.48.0", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "quote" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +dependencies = [ + "regex-syntax 0.7.1", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" + +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" +dependencies = [ + "bytecheck", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rsa" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf22754c49613d2b3b119f0e5d46e34a2c628a937e3024b8762de4e7d8c710b" +dependencies = [ + "byteorder", + "digest", + "num-bigint-dig", + "num-integer", + "num-iter", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "smallvec", + "subtle", + "zeroize", +] + +[[package]] +name = "rust_decimal" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +dependencies = [ + "arrayvec", + "borsh", + "bytecheck", + "byteorder", + "bytes", + "num-traits", + "rand", + "rkyv", + "serde", + "serde_json", +] + +[[package]] +name = "rustix" +version = "0.37.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys 0.48.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + +[[package]] +name = "sea-orm" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fade86e8d41fd1a4721f84cb834f4ca2783f973cc30e6212b7fafc134f169214" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "sea-strum", + "serde", + "serde_json", + "sqlx", + "thiserror", + "time 0.3.21", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-cli" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbf34a2caf70c2e3be9bb1e674e9540f6dfd7c8f40f6f05daf3b9740e476005" +dependencies = [ + "chrono", + "clap", + "dotenvy", + "regex", + "sea-schema", + "tracing", + "tracing-subscriber", + "url", +] + +[[package]] +name = "sea-orm-macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28936f26d62234ff0be16f80115dbdeb3237fe9c25cf18fbcd1e3b3592360f20" +dependencies = [ + "bae", + "heck 0.3.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sea-orm-migration" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278d3adfd0832b6ffc17d3cfbc574d3695a5c1b38814e0bc8ac238d33f3d87cf" +dependencies = [ + "async-trait", + "clap", + "dotenvy", + "futures", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "sea-query" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd34be05fdde9ec79231414bdd44ba1aa9c57349190076699e90721cb5eb59b" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time 0.3.21", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03548c63aec07afd4fd190923e0160d2f2fc92def27470b54154cf232da6203b" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time 0.3.21", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", + "thiserror", +] + +[[package]] +name = "sea-schema" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb2940bb5a10bc6cd05b450ce6cd3993e27fddd7eface2becb97fc5af3a040e" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sea-strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391d06a6007842cfe79ac6f7f53911b76dfd69fc9a6769f1cf6569d12ce20e1b" +dependencies = [ + "sea-strum_macros", +] + +[[package]] +name = "sea-strum_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.162" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "serde_json" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + +[[package]] +name = "slab" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sql-adaptor" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "chrono", + "data", + "sea-orm", + "sea-orm-migration", + "serde", + "serde_json", +] + +[[package]] +name = "sqlformat" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlx" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +dependencies = [ + "sqlx-core", + "sqlx-macros", +] + +[[package]] +name = "sqlx-core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +dependencies = [ + "ahash 0.7.6", + "atoi", + "base64", + "bigdecimal", + "bitflags", + "byteorder", + "bytes", + "chrono", + "crossbeam-queue", + "digest", + "dirs", + "dotenvy", + "either", + "event-listener", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "generic-array", + "hashlink", + "hex", + "hkdf", + "hmac", + "indexmap", + "itoa", + "libc", + "libsqlite3-sys", + "log", + "md-5", + "memchr", + "num-bigint", + "once_cell", + "paste", + "percent-encoding", + "rand", + "rsa", + "rust_decimal", + "serde", + "serde_json", + "sha1", + "sha2", + "smallvec", + "sqlformat", + "sqlx-rt", + "stringprep", + "thiserror", + "time 0.3.21", + "tokio-stream", + "url", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +dependencies = [ + "dotenvy", + "either", + "heck 0.4.1", + "once_cell", + "proc-macro2", + "quote", + "serde_json", + "sqlx-core", + "sqlx-rt", + "syn 1.0.109", + "url", +] + +[[package]] +name = "sqlx-rt" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +dependencies = [ + "native-tls", + "once_cell", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "stringprep" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "tempfile" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall 0.3.5", + "rustix", + "windows-sys 0.45.0", +] + +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + +[[package]] +name = "time" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" +dependencies = [ + "itoa", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + +[[package]] +name = "time-macros" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +dependencies = [ + "autocfg", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "uuid" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" +dependencies = [ + "serde", +] + +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "whoami" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "zeroize" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..fefc872 --- /dev/null +++ b/backend/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "crabfit_backend" +version = "1.1.0" +edition = "2021" + +[workspace] +members = ["data", "adaptors/*"] + +[dependencies] +axum = "0.6.18" +serde = { version = "1.0.162", features = ["derive"] } +tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] } +data = { path = "data" } +sql-adaptor = { path = "adaptors/sql" } +dotenv = "0.15.0" diff --git a/backend/adaptors/sql/Cargo.toml b/backend/adaptors/sql/Cargo.toml new file mode 100644 index 0000000..3fb6ca3 --- /dev/null +++ b/backend/adaptors/sql/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "sql-adaptor" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.68" +data = { path = "../../data" } +sea-orm = { version = "0.11.3", features = [ "macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "runtime-tokio-native-tls" ] } +serde = { version = "1.0.162", features = [ "derive" ] } +async-std = { version = "1", features = ["attributes", "tokio1"] } +sea-orm-migration = "0.11.0" +serde_json = "1.0.96" +chrono = "0.4.24" diff --git a/backend/adaptors/sql/src/entity/event.rs b/backend/adaptors/sql/src/entity/event.rs new file mode 100644 index 0000000..5d3ccd7 --- /dev/null +++ b/backend/adaptors/sql/src/entity/event.rs @@ -0,0 +1,42 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +use chrono::{DateTime as ChronoDateTime, Utc}; +use data::event::Event; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "event")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + pub created_at: DateTime, + pub times: Json, + pub timezone: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::person::Entity")] + Person, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Person.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +impl From for Event { + fn from(value: Model) -> Self { + Self { + id: value.id, + name: value.name, + created_at: ChronoDateTime::::from_utc(value.created_at, Utc), + times: serde_json::from_value(value.times).unwrap_or(vec![]), + timezone: value.timezone, + } + } +} diff --git a/backend/adaptors/sql/src/entity/mod.rs b/backend/adaptors/sql/src/entity/mod.rs new file mode 100644 index 0000000..f5e4734 --- /dev/null +++ b/backend/adaptors/sql/src/entity/mod.rs @@ -0,0 +1,7 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +pub mod prelude; + +pub mod event; +pub mod person; +pub mod stats; diff --git a/backend/adaptors/sql/src/entity/person.rs b/backend/adaptors/sql/src/entity/person.rs new file mode 100644 index 0000000..82a2664 --- /dev/null +++ b/backend/adaptors/sql/src/entity/person.rs @@ -0,0 +1,48 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +use chrono::{DateTime as ChronoDateTime, Utc}; +use data::person::Person; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "person")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub name: String, + pub password_hash: Option, + pub created_at: DateTime, + pub availability: Json, + #[sea_orm(primary_key, auto_increment = false)] + pub event_id: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::event::Entity", + from = "Column::EventId", + to = "super::event::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Event, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Event.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} + +impl From for Person { + fn from(value: Model) -> Self { + Self { + name: value.name, + password_hash: value.password_hash, + created_at: ChronoDateTime::::from_utc(value.created_at, Utc), + availability: serde_json::from_value(value.availability).unwrap_or(vec![]), + } + } +} diff --git a/backend/adaptors/sql/src/entity/prelude.rs b/backend/adaptors/sql/src/entity/prelude.rs new file mode 100644 index 0000000..5854e6b --- /dev/null +++ b/backend/adaptors/sql/src/entity/prelude.rs @@ -0,0 +1,5 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +pub use super::event::Entity as Event; +pub use super::person::Entity as Person; +pub use super::stats::Entity as Stats; diff --git a/backend/adaptors/sql/src/entity/stats.rs b/backend/adaptors/sql/src/entity/stats.rs new file mode 100644 index 0000000..0d8a8c3 --- /dev/null +++ b/backend/adaptors/sql/src/entity/stats.rs @@ -0,0 +1,27 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 + +use data::stats::Stats; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "stats")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub event_count: i32, + pub person_count: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} + +impl From for Stats { + fn from(value: Model) -> Self { + Self { + event_count: value.event_count, + person_count: value.person_count, + } + } +} diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs new file mode 100644 index 0000000..f396c1b --- /dev/null +++ b/backend/adaptors/sql/src/lib.rs @@ -0,0 +1,152 @@ +use std::{env, error::Error}; + +use async_trait::async_trait; +use data::{ + adaptor::Adaptor, + event::{Event, EventDeletion}, + person::Person, + stats::Stats, +}; +use entity::{event, person, stats}; +use migration::{Migrator, MigratorTrait}; +use sea_orm::{ + ActiveModelTrait, + ActiveValue::{NotSet, Set}, + ColumnTrait, Database, DatabaseConnection, DbErr, EntityTrait, ModelTrait, QueryFilter, + TransactionTrait, TryIntoModel, +}; +use serde_json::json; + +mod entity; +mod migration; + +pub struct PostgresAdaptor { + db: DatabaseConnection, +} + +#[async_trait] +impl Adaptor for PostgresAdaptor { + async fn new() -> Self { + let connection_string = env::var("DATABASE_URL").unwrap(); + + // Connect to the database + let db = Database::connect(&connection_string).await.unwrap(); + println!("Connected to database at {}", connection_string); + + // Setup tables + Migrator::up(&db, None).await.unwrap(); + + Self { db } + } + + async fn get_stats(&self) -> Result> { + Ok(get_stats_row(&self.db).await?.try_into_model()?.into()) + } + + async fn increment_stat_event_count(&self) -> Result> { + let mut current_stats = get_stats_row(&self.db).await?; + current_stats.event_count = Set(current_stats.event_count.unwrap() + 1); + + Ok(current_stats.save(&self.db).await?.event_count.unwrap()) + } + + async fn increment_stat_person_count(&self) -> Result> { + let mut current_stats = get_stats_row(&self.db).await?; + current_stats.person_count = Set(current_stats.person_count.unwrap() + 1); + + Ok(current_stats.save(&self.db).await?.person_count.unwrap()) + } + + async fn get_people(&self, event_id: String) -> Result>, Box> { + // TODO: optimize into one query + let event_row = event::Entity::find_by_id(event_id).one(&self.db).await?; + + Ok(match event_row { + Some(event) => Some( + event + .find_related(person::Entity) + .all(&self.db) + .await? + .into_iter() + .map(|model| model.into()) + .collect(), + ), + None => None, + }) + } + + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result> { + Ok(person::ActiveModel { + name: Set(person.name), + password_hash: Set(person.password_hash), + created_at: Set(person.created_at.naive_utc()), + availability: Set(serde_json::to_value(person.availability).unwrap_or(json!([]))), + event_id: Set(event_id), + } + .save(&self.db) + .await? + .try_into_model()? + .into()) + } + + async fn get_event(&self, id: String) -> Result, Box> { + Ok(event::Entity::find_by_id(id) + .one(&self.db) + .await? + .map(|model| model.into())) + } + + async fn create_event(&self, event: Event) -> Result> { + Ok(event::ActiveModel { + id: Set(event.id), + name: Set(event.name), + created_at: Set(event.created_at.naive_utc()), + times: Set(serde_json::to_value(event.times).unwrap_or(json!([]))), + timezone: Set(event.timezone), + } + .save(&self.db) + .await? + .try_into_model()? + .into()) + } + + async fn delete_event(&self, id: String) -> Result> { + let event_id = id.clone(); + let person_count = self + .db + .transaction::<_, u64, DbErr>(|t| { + Box::pin(async move { + // Delete people + let people_delete_result = person::Entity::delete_many() + .filter(person::Column::EventId.eq(&event_id)) + .exec(t) + .await?; + + // Delete event + event::Entity::delete_by_id(event_id).exec(t).await?; + + Ok(people_delete_result.rows_affected) + }) + }) + .await?; + + Ok(EventDeletion { id, person_count }) + } +} + +// Get the current stats as an ActiveModel +async fn get_stats_row(db: &DatabaseConnection) -> Result { + let current_stats = stats::Entity::find().one(db).await?; + Ok(match current_stats { + Some(model) => model.into(), + None => stats::ActiveModel { + id: NotSet, + event_count: Set(0), + person_count: Set(0), + }, + }) +} diff --git a/backend/adaptors/sql/src/migration/mod.rs b/backend/adaptors/sql/src/migration/mod.rs new file mode 100644 index 0000000..08be57d --- /dev/null +++ b/backend/adaptors/sql/src/migration/mod.rs @@ -0,0 +1,12 @@ +pub use sea_orm_migration::prelude::*; + +mod setup_tables; + +pub struct Migrator; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![Box::new(setup_tables::Migration)] + } +} diff --git a/backend/adaptors/sql/src/migration/setup_tables.rs b/backend/adaptors/sql/src/migration/setup_tables.rs new file mode 100644 index 0000000..5ce5478 --- /dev/null +++ b/backend/adaptors/sql/src/migration/setup_tables.rs @@ -0,0 +1,120 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + print!("Setting up database..."); + + // Stats table + manager + .create_table( + Table::create() + .table(Stats::Table) + .if_not_exists() + .col( + ColumnDef::new(Stats::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(Stats::EventCount).integer().not_null()) + .col(ColumnDef::new(Stats::PersonCount).integer().not_null()) + .to_owned(), + ) + .await?; + + // Events table + manager + .create_table( + Table::create() + .table(Event::Table) + .if_not_exists() + .col(ColumnDef::new(Event::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Event::Name).string().not_null()) + .col(ColumnDef::new(Event::CreatedAt).timestamp().not_null()) + .col(ColumnDef::new(Event::Times).json().not_null()) + .col(ColumnDef::new(Event::Timezone).string().not_null()) + .to_owned(), + ) + .await?; + + // People table + manager + .create_table( + Table::create() + .table(Person::Table) + .if_not_exists() + .col(ColumnDef::new(Person::Name).string().not_null()) + .col(ColumnDef::new(Person::PasswordHash).string()) + .col(ColumnDef::new(Person::CreatedAt).timestamp().not_null()) + .col(ColumnDef::new(Person::Availability).json().not_null()) + .col(ColumnDef::new(Person::EventId).string().not_null()) + .primary_key(Index::create().col(Person::EventId).col(Person::Name)) + .to_owned(), + ) + .await?; + + // Relation + manager + .create_foreign_key( + ForeignKey::create() + .name("FK_person_event") + .from(Person::Table, Person::EventId) + .to(Event::Table, Event::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + .to_owned(), + ) + .await?; + + println!(" done"); + Ok(()) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Stats::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Person::Table).to_owned()) + .await?; + manager + .drop_table(Table::drop().table(Event::Table).to_owned()) + .await?; + + Ok(()) + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +enum Stats { + Table, + Id, + EventCount, + PersonCount, +} + +#[derive(Iden)] +enum Event { + Table, + Id, + Name, + CreatedAt, + Times, + Timezone, +} + +#[derive(Iden)] +enum Person { + Table, + Name, + PasswordHash, + CreatedAt, + Availability, + EventId, +} diff --git a/backend/data/Cargo.toml b/backend/data/Cargo.toml new file mode 100644 index 0000000..1a3a21f --- /dev/null +++ b/backend/data/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "data" +description = "Structs and traits for the data storage and transfer of Crab Fit" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.68" +chrono = "0.4.24" diff --git a/backend/data/src/adaptor.rs b/backend/data/src/adaptor.rs new file mode 100644 index 0000000..9064453 --- /dev/null +++ b/backend/data/src/adaptor.rs @@ -0,0 +1,37 @@ +use std::error::Error; + +use async_trait::async_trait; + +use crate::{ + event::{Event, EventDeletion}, + person::Person, + stats::Stats, +}; + +/// Data storage adaptor, all methods on an adaptor can return an error if +/// something goes wrong, or potentially None if the data requested was not found. +#[async_trait] +pub trait Adaptor { + /// Creates a new adaptor and performs all setup required + /// + /// # Panics + /// If an error occurs while setting up the adaptor + async fn new() -> Self; + + async fn get_stats(&self) -> Result>; + async fn increment_stat_event_count(&self) -> Result>; + async fn increment_stat_person_count(&self) -> Result>; + + async fn get_people(&self, event_id: String) -> Result>, Box>; + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result>; + + async fn get_event(&self, id: String) -> Result, Box>; + async fn create_event(&self, event: Event) -> Result>; + + /// Delete an event as well as all related people + async fn delete_event(&self, id: String) -> Result>; +} diff --git a/backend/data/src/event.rs b/backend/data/src/event.rs new file mode 100644 index 0000000..09129ee --- /dev/null +++ b/backend/data/src/event.rs @@ -0,0 +1,16 @@ +use chrono::{DateTime, Utc}; + +pub struct Event { + pub id: String, + pub name: String, + pub created_at: DateTime, + pub times: Vec, + pub timezone: String, +} + +/// Info about a deleted event +pub struct EventDeletion { + pub id: String, + /// The amount of people that were in this event that were also deleted + pub person_count: u64, +} diff --git a/backend/data/src/lib.rs b/backend/data/src/lib.rs new file mode 100644 index 0000000..ae9caf1 --- /dev/null +++ b/backend/data/src/lib.rs @@ -0,0 +1,4 @@ +pub mod adaptor; +pub mod event; +pub mod person; +pub mod stats; diff --git a/backend/data/src/person.rs b/backend/data/src/person.rs new file mode 100644 index 0000000..8642cd4 --- /dev/null +++ b/backend/data/src/person.rs @@ -0,0 +1,8 @@ +use chrono::{DateTime, Utc}; + +pub struct Person { + pub name: String, + pub password_hash: Option, + pub created_at: DateTime, + pub availability: Vec, +} diff --git a/backend/data/src/stats.rs b/backend/data/src/stats.rs new file mode 100644 index 0000000..3f4843d --- /dev/null +++ b/backend/data/src/stats.rs @@ -0,0 +1,4 @@ +pub struct Stats { + pub event_count: i32, + pub person_count: i32, +} diff --git a/backend/src/main.rs b/backend/src/main.rs new file mode 100644 index 0000000..5ff6304 --- /dev/null +++ b/backend/src/main.rs @@ -0,0 +1,33 @@ +use std::net::SocketAddr; + +use axum::{routing::get, Router, Server}; +use data::adaptor::Adaptor; +use sql_adaptor::PostgresAdaptor; + +#[cfg(debug_assertions)] +const MODE: &str = "debug"; + +#[cfg(not(debug_assertions))] +const MODE: &str = "release"; + +#[tokio::main] +async fn main() { + // Load env + dotenv::dotenv().ok(); + + PostgresAdaptor::new().await; + + let app = Router::new().route("/", get(get_root)); + + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + + println!("Crab Fit API listening at http://{} in {} mode", addr, MODE); + Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +async fn get_root() -> String { + format!("Crab Fit API v{}", env!("CARGO_PKG_VERSION")) +} diff --git a/crabfit-browser-extension/manifest.json b/browser-extension/manifest.json similarity index 100% rename from crabfit-browser-extension/manifest.json rename to browser-extension/manifest.json diff --git a/crabfit-browser-extension/popup.html b/browser-extension/popup.html similarity index 100% rename from crabfit-browser-extension/popup.html rename to browser-extension/popup.html diff --git a/crabfit-browser-extension/res/icon128.png b/browser-extension/res/icon128.png similarity index 100% rename from crabfit-browser-extension/res/icon128.png rename to browser-extension/res/icon128.png diff --git a/crabfit-browser-extension/res/icon16.png b/browser-extension/res/icon16.png similarity index 100% rename from crabfit-browser-extension/res/icon16.png rename to browser-extension/res/icon16.png diff --git a/crabfit-browser-extension/res/icon32.png b/browser-extension/res/icon32.png similarity index 100% rename from crabfit-browser-extension/res/icon32.png rename to browser-extension/res/icon32.png diff --git a/crabfit-browser-extension/res/icon48.png b/browser-extension/res/icon48.png similarity index 100% rename from crabfit-browser-extension/res/icon48.png rename to browser-extension/res/icon48.png diff --git a/crabfit-backend/.eslintrc.js b/crabfit-backend/.eslintrc.js deleted file mode 100644 index 5a31f19..0000000 --- a/crabfit-backend/.eslintrc.js +++ /dev/null @@ -1,50 +0,0 @@ -module.exports = { - 'env': { - 'es2021': true, - 'node': true - }, - 'extends': 'eslint:recommended', - 'overrides': [ - ], - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module' - }, - 'rules': { - 'indent': [ - 'error', - 2 - ], - 'linebreak-style': [ - 'error', - 'unix' - ], - 'quotes': [ - 'error', - 'single' - ], - 'semi': [ - 'error', - 'never' - ], - 'eqeqeq': 2, - 'no-return-await': 1, - 'no-var': 2, - 'prefer-const': 1, - 'yoda': 2, - 'no-trailing-spaces': 1, - 'eol-last': [1, 'always'], - 'no-unused-vars': [ - 1, - { - 'args': 'all', - 'argsIgnorePattern': '^_', - 'ignoreRestSiblings': true - }, - ], - 'arrow-parens': [ - 'error', - 'as-needed' - ], - } -} diff --git a/crabfit-backend/.gcloudignore b/crabfit-backend/.gcloudignore deleted file mode 100644 index 92f6f4d..0000000 --- a/crabfit-backend/.gcloudignore +++ /dev/null @@ -1,12 +0,0 @@ -.gcloudignore - -.git -.gitignore - -.env - -node_modules/ -.parcel-cache -res -routes -swagger.yaml diff --git a/crabfit-backend/.gitignore b/crabfit-backend/.gitignore deleted file mode 100644 index 7d68da3..0000000 --- a/crabfit-backend/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -dist -.parcel-cache -.env - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/crabfit-backend/app.yaml b/crabfit-backend/app.yaml deleted file mode 100644 index 885ea77..0000000 --- a/crabfit-backend/app.yaml +++ /dev/null @@ -1,7 +0,0 @@ -runtime: nodejs16 -service: api -handlers: - - url: /.* - secure: always - redirect_http_response_code: 301 - script: auto diff --git a/crabfit-backend/cron.yaml b/crabfit-backend/cron.yaml deleted file mode 100644 index 9dd6fcd..0000000 --- a/crabfit-backend/cron.yaml +++ /dev/null @@ -1,9 +0,0 @@ -cron: - - description: "clean up old events" - url: /tasks/cleanup - schedule: every monday 09:00 - target: api - - description: "remove people with an event id that no longer exists" - url: /tasks/removeOrphans - schedule: 1st wednesday of month 09:00 - target: api diff --git a/crabfit-backend/index.js b/crabfit-backend/index.js deleted file mode 100644 index 5dc5b39..0000000 --- a/crabfit-backend/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import { config } from 'dotenv' -import { Datastore } from '@google-cloud/datastore' -import express from 'express' -import cors from 'cors' - -import packageJson from './package.json' - -import { - stats, - getEvent, - createEvent, - getPeople, - createPerson, - login, - updatePerson, - taskCleanup, - taskRemoveOrphans, -} from './routes' - -config() - -const app = express() -const port = 8080 -const corsOptions = { - origin: process.env.NODE_ENV === 'production' ? 'https://crab.fit' : 'http://localhost:5173', -} - -const datastore = new Datastore({ - keyFilename: process.env.GOOGLE_APPLICATION_CREDENTIALS, -}) - -app.use(express.json()) -app.use((req, _res, next) => { - req.datastore = datastore - req.types = { - event: process.env.NODE_ENV === 'production' ? 'Event' : 'DevEvent', - person: process.env.NODE_ENV === 'production' ? 'Person' : 'DevPerson', - stats: process.env.NODE_ENV === 'production' ? 'Stats' : 'DevStats', - } - next() -}) -app.options('*', cors(corsOptions)) -app.use(cors(corsOptions)) - -app.get('/', (_req, res) => res.send(`Crabfit API v${packageJson.version}`)) - -app.get('/stats', stats) -app.get('/event/:eventId', getEvent) -app.post('/event', createEvent) -app.get('/event/:eventId/people', getPeople) -app.post('/event/:eventId/people', createPerson) -app.post('/event/:eventId/people/:personName', login) -app.patch('/event/:eventId/people/:personName', updatePerson) - -// Tasks -app.get('/tasks/cleanup', taskCleanup) -app.get('/tasks/removeOrphans', taskRemoveOrphans) - -app.listen(port, () => { - console.log(`Crabfit API listening at http://localhost:${port} in ${process.env.NODE_ENV === 'production' ? 'prod' : 'dev'} mode`) -}) diff --git a/crabfit-backend/package.json b/crabfit-backend/package.json deleted file mode 100644 index cf3f3fd..0000000 --- a/crabfit-backend/package.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "crabfit-backend", - "version": "2.0.0", - "description": "API for Crabfit", - "author": "Ben Grant", - "license": "GPL-3.0-only", - "private": true, - "source": "index.js", - "main": "dist/index.js", - "engines": { - "node": ">=12.0.0" - }, - "scripts": { - "build:dev": "NODE_ENV=development parcel build --no-cache", - "dev": "rm -rf .parcel-cache dist && NODE_ENV=development nodemon --exec \"yarn build:dev && yarn start\" --watch routes --watch res --watch index.js", - "build": "parcel build", - "start": "node ./dist/index.js", - "lint": "eslint index.js ./routes" - }, - "dependencies": { - "@google-cloud/datastore": "^7.0.0", - "bcrypt": "^5.0.1", - "cors": "^2.8.5", - "dayjs": "^1.11.5", - "dotenv": "^16.0.1", - "express": "^4.18.1", - "punycode": "^2.1.1" - }, - "devDependencies": { - "eslint": "^8.22.0", - "nodemon": "^2.0.19", - "parcel": "^2.7.0" - } -} diff --git a/crabfit-backend/res/adjectives.json b/crabfit-backend/res/adjectives.json deleted file mode 100644 index 3edd6f5..0000000 --- a/crabfit-backend/res/adjectives.json +++ /dev/null @@ -1,201 +0,0 @@ -[ - "adorable", - "adventurous", - "aggressive", - "agreeable", - "alert", - "alive", - "amused", - "angry", - "annoyed", - "annoying", - "anxious", - "arrogant", - "ashamed", - "attractive", - "average", - "beautiful", - "better", - "bewildered", - "blue", - "blushing", - "bored", - "brainy", - "brave", - "breakable", - "bright", - "busy", - "calm", - "careful", - "cautious", - "charming", - "cheerful", - "clean", - "clear", - "clever", - "cloudy", - "clumsy", - "colorful", - "comfortable", - "concerned", - "confused", - "cooperative", - "courageous", - "crazy", - "creepy", - "crowded", - "curious", - "cute", - "dangerous", - "dark", - "defiant", - "delightful", - "depressed", - "determined", - "different", - "difficult", - "disgusted", - "distinct", - "disturbed", - "dizzy", - "doubtful", - "drab", - "dull", - "eager", - "easy", - "elated", - "elegant", - "embarrassed", - "enchanting", - "encouraging", - "energetic", - "enthusiastic", - "envious", - "evil", - "excited", - "expensive", - "exuberant", - "fair", - "faithful", - "famous", - "fancy", - "fantastic", - "fierce", - "fine", - "foolish", - "fragile", - "frail", - "frantic", - "friendly", - "frightened", - "funny", - "gentle", - "gifted", - "glamorous", - "gleaming", - "glorious", - "good", - "gorgeous", - "graceful", - "grumpy", - "handsome", - "happy", - "healthy", - "helpful", - "hilarious", - "homely", - "hungry", - "important", - "impossible", - "inexpensive", - "innocent", - "inquisitive", - "itchy", - "jealous", - "jittery", - "jolly", - "joyous", - "kind", - "lazy", - "light", - "lively", - "lonely", - "long", - "lovely", - "lucky", - "magnificent", - "misty", - "modern", - "motionless", - "muddy", - "mushy", - "mysterious", - "naughty", - "nervous", - "nice", - "nutty", - "obedient", - "obnoxious", - "odd", - "old-fashioned", - "open", - "outrageous", - "outstanding", - "panicky", - "perfect", - "plain", - "pleasant", - "poised", - "powerful", - "precious", - "prickly", - "proud", - "puzzled", - "quaint", - "real", - "relieved", - "scary", - "selfish", - "shiny", - "shy", - "silly", - "sleepy", - "smiling", - "smoggy", - "sparkling", - "splendid", - "spotless", - "stormy", - "strange", - "successful", - "super", - "talented", - "tame", - "tasty", - "tender", - "tense", - "terrible", - "thankful", - "thoughtful", - "thoughtless", - "tired", - "tough", - "uninterested", - "unsightly", - "unusual", - "upset", - "uptight", - "vast", - "victorious", - "vivacious", - "wandering", - "weary", - "wicked", - "wide-eyed", - "wild", - "witty", - "worried", - "worrisome", - "zany", - "zealous" -] diff --git a/crabfit-backend/res/crabs.json b/crabfit-backend/res/crabs.json deleted file mode 100644 index a0563b6..0000000 --- a/crabfit-backend/res/crabs.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - "American Horseshoe", - "Atlantic Ghost", - "Baja Elbow", - "Big Claw Purple Hermit", - "Coldwater Mole", - "Cuata Swim", - "Deepwater Frog", - "Dwarf Teardrop", - "Elegant Hermit", - "Flat Spider", - "Ghost", - "Globe Purse", - "Green", - "Halloween", - "Harbor Spider", - "Inflated Spider", - "Left Clawed Hermit", - "Lumpy Claw", - "Magnificent Hermit", - "Mexican Spider", - "Mouthless Land", - "Northern Lemon Rock", - "Pacific Arrow", - "Pacific Mole", - "Paco Box", - "Panamic Spider", - "Purple Shore", - "Red Rock", - "Red Swim", - "Red-leg Hermit", - "Robust Swim", - "Rough Swim", - "Sand Swim", - "Sally Lightfoot", - "Shamed-face Box", - "Shamed-face Heart Box", - "Shell", - "Small Arched Box", - "Southern Kelp", - "Spotted Box", - "Striated Mole", - "Striped Shore", - "Tropical Mole", - "Walking Rock", - "Yellow Shore" -] diff --git a/crabfit-backend/routes/createEvent.js b/crabfit-backend/routes/createEvent.js deleted file mode 100644 index bf901f9..0000000 --- a/crabfit-backend/routes/createEvent.js +++ /dev/null @@ -1,84 +0,0 @@ -import dayjs from 'dayjs' -import punycode from 'punycode/' - -import adjectives from '../res/adjectives.json' -import crabs from '../res/crabs.json' - -const capitalize = string => string.charAt(0).toUpperCase() + string.slice(1) - -// Generate a random name based on an adjective and a crab species -const generateName = () => - `${capitalize(adjectives[Math.floor(Math.random() * adjectives.length)])} ${crabs[Math.floor(Math.random() * crabs.length)]} Crab` - -// Generate a slug for the crab fit -const generateId = name => { - let id = punycode.encode(name.trim().toLowerCase()).trim().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-') - if (id.replace(/-/g, '') === '') { - id = generateName().trim().toLowerCase().replace(/[^A-Za-z0-9 ]/g, '').replace(/\s+/g, '-') - } - const number = Math.floor(100000 + Math.random() * 900000) - return `${id}-${number}` -} - -const createEvent = async (req, res) => { - const { event } = req.body - - try { - const name = event.name.trim() === '' ? generateName() : event.name.trim() - let eventId = generateId(name) - const currentTime = dayjs().unix() - - // Check if the event ID already exists, and if so generate a new one - let eventResult - do { - const query = req.datastore.createQuery(req.types.event) - .select('__key__') - .filter('__key__', req.datastore.key([req.types.event, eventId])) - - eventResult = (await req.datastore.runQuery(query))[0][0] - - if (eventResult !== undefined) { - eventId = generateId(name) - } - } while (eventResult !== undefined) - - const entity = { - key: req.datastore.key([req.types.event, eventId]), - data: { - name: name, - created: currentTime, - times: event.times, - timezone: event.timezone, - }, - } - - await req.datastore.insert(entity) - - res.status(201).send({ - id: eventId, - name: name, - created: currentTime, - times: event.times, - timezone: event.timezone, - }) - - // Update stats - const eventCountResult = (await req.datastore.get(req.datastore.key([req.types.stats, 'eventCount'])))[0] || null - if (eventCountResult) { - await req.datastore.upsert({ - ...eventCountResult, - value: eventCountResult.value + 1, - }) - } else { - await req.datastore.insert({ - key: req.datastore.key([req.types.stats, 'eventCount']), - data: { value: 1 }, - }) - } - } catch (e) { - console.error(e) - res.status(400).send({ error: 'An error occurred while creating the event' }) - } -} - -export default createEvent diff --git a/crabfit-backend/routes/createPerson.js b/crabfit-backend/routes/createPerson.js deleted file mode 100644 index 5a437d2..0000000 --- a/crabfit-backend/routes/createPerson.js +++ /dev/null @@ -1,65 +0,0 @@ -import dayjs from 'dayjs' -import bcrypt from 'bcrypt' - -const createPerson = async (req, res) => { - const { eventId } = req.params - const { person } = req.body - - try { - const event = (await req.datastore.get(req.datastore.key([req.types.event, eventId])))[0] - const query = req.datastore.createQuery(req.types.person) - .filter('eventId', eventId) - .filter('name', person.name) - const personResult = (await req.datastore.runQuery(query))[0][0] - - if (event) { - if (person && personResult === undefined) { - const currentTime = dayjs().unix() - - // If password - let hash = null - if (person.password) { - hash = await bcrypt.hash(person.password, 10) - } - - const entity = { - key: req.datastore.key(req.types.person), - data: { - name: person.name.trim(), - password: hash, - eventId: eventId, - created: currentTime, - availability: [], - }, - } - - await req.datastore.insert(entity) - - res.status(201).send({ success: 'Created' }) - - // Update stats - const personCountResult = (await req.datastore.get(req.datastore.key([req.types.stats, 'personCount'])))[0] || null - if (personCountResult) { - await req.datastore.upsert({ - ...personCountResult, - value: personCountResult.value + 1, - }) - } else { - await req.datastore.insert({ - key: req.datastore.key([req.types.stats, 'personCount']), - data: { value: 1 }, - }) - } - } else { - res.status(400).send({ error: 'Unable to create person' }) - } - } else { - res.status(404).send({ error: 'Event does not exist' }) - } - } catch (e) { - console.error(e) - res.status(400).send({ error: 'An error occurred while creating the person' }) - } -} - -export default createPerson diff --git a/crabfit-backend/routes/getEvent.js b/crabfit-backend/routes/getEvent.js deleted file mode 100644 index 991a71a..0000000 --- a/crabfit-backend/routes/getEvent.js +++ /dev/null @@ -1,29 +0,0 @@ -import dayjs from 'dayjs' - -const getEvent = async (req, res) => { - const { eventId } = req.params - - try { - const event = (await req.datastore.get(req.datastore.key([req.types.event, eventId])))[0] - - if (event) { - res.send({ - id: eventId, - ...event, - }) - - // Update last visited time - await req.datastore.upsert({ - ...event, - visited: dayjs().unix() - }) - } else { - res.status(404).send({ error: 'Event not found' }) - } - } catch (e) { - console.error(e) - res.status(404).send({ error: 'Event not found' }) - } -} - -export default getEvent diff --git a/crabfit-backend/routes/getPeople.js b/crabfit-backend/routes/getPeople.js deleted file mode 100644 index 0a8c041..0000000 --- a/crabfit-backend/routes/getPeople.js +++ /dev/null @@ -1,20 +0,0 @@ -const getPeople = async (req, res) => { - const { eventId } = req.params - - try { - const query = req.datastore.createQuery(req.types.person).filter('eventId', eventId) - let people = (await req.datastore.runQuery(query))[0] - people = people.map(person => ({ - name: person.name, - availability: person.availability, - created: person.created, - })) - - res.send({ people }) - } catch (e) { - console.error(e) - res.status(404).send({ error: 'Person not found' }) - } -} - -export default getPeople diff --git a/crabfit-backend/routes/index.js b/crabfit-backend/routes/index.js deleted file mode 100644 index 3e65553..0000000 --- a/crabfit-backend/routes/index.js +++ /dev/null @@ -1,10 +0,0 @@ -export { default as stats } from './stats' -export { default as getEvent } from './getEvent' -export { default as createEvent } from './createEvent' -export { default as getPeople } from './getPeople' -export { default as createPerson } from './createPerson' -export { default as login } from './login' -export { default as updatePerson } from './updatePerson' - -export { default as taskCleanup } from './taskCleanup' -export { default as taskRemoveOrphans } from './taskRemoveOrphans' diff --git a/crabfit-backend/routes/login.js b/crabfit-backend/routes/login.js deleted file mode 100644 index 059b8de..0000000 --- a/crabfit-backend/routes/login.js +++ /dev/null @@ -1,35 +0,0 @@ -import bcrypt from 'bcrypt' - -const login = async (req, res) => { - const { eventId, personName } = req.params - const { person } = req.body - - try { - const query = req.datastore.createQuery(req.types.person) - .filter('eventId', eventId) - .filter('name', personName) - const personResult = (await req.datastore.runQuery(query))[0][0] - - if (personResult) { - if (personResult.password) { - const passwordsMatch = person && person.password && await bcrypt.compare(person.password, personResult.password) - if (!passwordsMatch) { - return res.status(401).send({ error: 'Incorrect password' }) - } - } - - res.send({ - name: personName, - availability: personResult.availability, - created: personResult.created, - }) - } else { - res.status(404).send({ error: 'Person does not exist' }) - } - } catch (e) { - console.error(e) - res.status(400).send({ error: 'An error occurred' }) - } -} - -export default login diff --git a/crabfit-backend/routes/stats.js b/crabfit-backend/routes/stats.js deleted file mode 100644 index f73d25b..0000000 --- a/crabfit-backend/routes/stats.js +++ /dev/null @@ -1,29 +0,0 @@ -import packageJson from '../package.json' - -const stats = async (req, res) => { - let eventCount = null - let personCount = null - - try { - const eventResult = (await req.datastore.get(req.datastore.key([req.types.stats, 'eventCount'])))[0] || null - const personResult = (await req.datastore.get(req.datastore.key([req.types.stats, 'personCount'])))[0] || null - - if (eventResult) { - eventCount = eventResult.value - } - if (personResult) { - personCount = personResult.value - } - - } catch (e) { - console.error(e) - } - - res.send({ - eventCount, - personCount, - version: packageJson.version, - }) -} - -export default stats diff --git a/crabfit-backend/routes/taskCleanup.js b/crabfit-backend/routes/taskCleanup.js deleted file mode 100644 index b4feac5..0000000 --- a/crabfit-backend/routes/taskCleanup.js +++ /dev/null @@ -1,48 +0,0 @@ -import dayjs from 'dayjs' - -const taskCleanup = async (req, res) => { - if (req.header('X-Appengine-Cron') === undefined) { - return res.status(400).send({ error: 'This task can only be run from a cron job' }) - } - - const threeMonthsAgo = dayjs().subtract(3, 'month').unix() - - console.log(`Running cleanup task at ${dayjs().format('h:mma D MMM YYYY')}`) - - try { - // Fetch events that haven't been visited in over 3 months - const eventQuery = req.datastore.createQuery(req.types.event).filter('visited', '<', threeMonthsAgo) - const oldEvents = (await req.datastore.runQuery(eventQuery))[0] - - if (oldEvents && oldEvents.length > 0) { - const oldEventIds = oldEvents.map(e => e[req.datastore.KEY].name) - console.log(`Found ${oldEventIds.length} events to remove`) - - // Fetch availabilities linked to the events discovered - let peopleDiscovered = 0 - await Promise.all(oldEventIds.map(async eventId => { - const peopleQuery = req.datastore.createQuery(req.types.person).filter('eventId', eventId) - const oldPeople = (await req.datastore.runQuery(peopleQuery))[0] - - if (oldPeople && oldPeople.length > 0) { - peopleDiscovered += oldPeople.length - await req.datastore.delete(oldPeople.map(person => person[req.datastore.KEY])) - } - })) - - await req.datastore.delete(oldEvents.map(event => event[req.datastore.KEY])) - - console.log(`Cleanup successful: ${oldEventIds.length} events and ${peopleDiscovered} people removed`) - - res.sendStatus(200) - } else { - console.log('Found 0 events to remove, ending cleanup') - res.sendStatus(404) - } - } catch (e) { - console.error(e) - res.sendStatus(404) - } -} - -export default taskCleanup diff --git a/crabfit-backend/routes/taskRemoveOrphans.js b/crabfit-backend/routes/taskRemoveOrphans.js deleted file mode 100644 index 95e4994..0000000 --- a/crabfit-backend/routes/taskRemoveOrphans.js +++ /dev/null @@ -1,48 +0,0 @@ -import dayjs from 'dayjs' - -const taskRemoveOrphans = async (req, res) => { - if (req.header('X-Appengine-Cron') === undefined) { - return res.status(400).send({ error: 'This task can only be run from a cron job' }) - } - - const threeMonthsAgo = dayjs().subtract(3, 'month').unix() - - console.log(`Running orphan removal task at ${dayjs().format('h:mma D MMM YYYY')}`) - - try { - // Fetch people that are older than 3 months - const peopleQuery = req.datastore.createQuery(req.types.person).filter('created', '<', threeMonthsAgo) - const oldPeople = (await req.datastore.runQuery(peopleQuery))[0] - - if (oldPeople && oldPeople.length > 0) { - console.log(`Found ${oldPeople.length} people older than 3 months, checking for events`) - - // Fetch events linked to the people discovered - let peopleWithoutEvents = 0 - await Promise.all(oldPeople.map(async person => { - const event = (await req.datastore.get(req.datastore.key([req.types.event, person.eventId])))[0] - - if (!event) { - peopleWithoutEvents++ - await req.datastore.delete(person[req.datastore.KEY]) - } - })) - - if (peopleWithoutEvents > 0) { - console.log(`Orphan removal successful: ${peopleWithoutEvents} people removed`) - res.sendStatus(200) - } else { - console.log('Found 0 people without events, ending orphan removal') - res.sendStatus(404) - } - } else { - console.log('Found 0 people older than 3 months, ending orphan removal') - res.sendStatus(404) - } - } catch (e) { - console.error(e) - res.sendStatus(404) - } -} - -export default taskRemoveOrphans diff --git a/crabfit-backend/routes/updatePerson.js b/crabfit-backend/routes/updatePerson.js deleted file mode 100644 index 921bb91..0000000 --- a/crabfit-backend/routes/updatePerson.js +++ /dev/null @@ -1,40 +0,0 @@ -import bcrypt from 'bcrypt' - -const updatePerson = async (req, res) => { - const { eventId, personName } = req.params - const { person } = req.body - - try { - const query = req.datastore.createQuery(req.types.person) - .filter('eventId', eventId) - .filter('name', personName) - const personResult = (await req.datastore.runQuery(query))[0][0] - - if (personResult) { - if (person && person.availability) { - if (personResult.password) { - const passwordsMatch = person.password && await bcrypt.compare(person.password, personResult.password) - if (!passwordsMatch) { - return res.status(401).send({ error: 'Incorrect password' }) - } - } - - await req.datastore.upsert({ - ...personResult, - availability: person.availability, - }) - - res.status(200).send({ success: 'Updated' }) - } else { - res.status(400).send({ error: 'Availability must be set' }) - } - } else { - res.status(404).send({ error: 'Person not found' }) - } - } catch (e) { - console.error(e) - res.status(400).send('An error occurred') - } -} - -export default updatePerson diff --git a/crabfit-backend/swagger.yaml b/crabfit-backend/swagger.yaml deleted file mode 100644 index 103e7e8..0000000 --- a/crabfit-backend/swagger.yaml +++ /dev/null @@ -1,245 +0,0 @@ -swagger: "2.0" -info: - title: "Crab Fit" - description: "Compare and align schedules to find a time that works for everyone" - version: "1.0.0" -host: "api-dot-crabfit.appspot.com" -x-google-endpoints: -- name: "api-dot-crabfit.appspot.com" - allowCors: true -schemes: - - "https" -produces: - - "application/json" -definitions: - Event: - type: "object" - properties: - id: - type: "string" - name: - type: "string" - timezone: - type: "string" - created: - type: "integer" - times: - type: "array" - items: - type: "string" - Person: - type: "object" - properties: - name: - type: "string" - availability: - type: "array" - items: - type: "string" - created: - type: "integer" -paths: - "/stats": - get: - summary: "Return stats for crabfit" - operationId: "getStats" - responses: - 200: - description: "OK" - schema: - type: "object" - properties: - eventCount: - type: "integer" - personCount: - type: "integer" - version: - type: "string" - "/event/{eventId}": - get: - summary: "Return an event details" - operationId: "getEvent" - parameters: - - in: "path" - name: "eventId" - required: true - type: "string" - description: "The ID of the event" - responses: - 200: - description: "OK" - schema: - $ref: '#/definitions/Event' - 404: - description: "Not found" - "/event": - post: - summary: "Create a new event" - operationId: "postEvent" - parameters: - - in: "body" - name: "event" - required: true - schema: - type: "object" - properties: - name: - type: "string" - timezone: - type: "string" - times: - type: "array" - items: - type: "string" - description: "New event details" - responses: - 201: - description: "Created" - schema: - $ref: '#/definitions/Event' - 400: - description: "Invalid data" - "/event/{eventId}/people": - get: - summary: "Get availabilities for an event" - operationId: "getPeople" - parameters: - - in: "path" - name: "eventId" - required: true - type: "string" - description: "The ID of the event" - responses: - 200: - description: "OK" - schema: - type: "object" - properties: - people: - type: "array" - items: - $ref: "#/definitions/Person" - 404: - description: "Not found" - post: - summary: "Add a new person to the event" - operationId: "postPeople" - parameters: - - in: "path" - name: "eventId" - required: true - type: "string" - description: "The ID of the event" - - in: "body" - name: "person" - required: true - schema: - type: "object" - properties: - name: - type: "string" - password: - type: "string" - description: "New person details" - responses: - 201: - description: "Created" - 404: - description: "Not found" - 400: - description: "Invalid data" - "/event/{eventId}/people/{personName}": - post: - summary: "Login as this person" - operationId: "getPerson" - parameters: - - in: "path" - name: "eventId" - required: true - type: "string" - description: "The ID of the event" - - in: "path" - name: "personName" - required: true - type: "string" - description: "The name of the person" - - in: "body" - name: "person" - required: false - schema: - type: "object" - properties: - password: - type: "string" - description: "Login details" - responses: - 200: - description: "OK" - schema: - $ref: "#/definitions/Person" - 401: - description: "Incorrect password" - 404: - description: "Not found" - patch: - summary: "Update this person's availabilities" - operationId: "patchPerson" - parameters: - - in: "path" - name: "eventId" - required: true - type: "string" - description: "The ID of the event" - - in: "path" - name: "personName" - required: true - type: "string" - description: "The name of the person" - - in: "body" - name: "person" - required: true - schema: - type: "object" - properties: - password: - type: "string" - availability: - type: "array" - items: - type: "string" - description: "Updated person details" - responses: - 200: - description: "OK" - 401: - description: "Incorrect password" - 404: - description: "Not found" - 400: - description: "Invalid data" - "/tasks/cleanup": - get: - summary: "Delete events inactive for more than 3 months" - operationId: "taskCleanup" - tags: - - tasks - responses: - 200: - description: "OK" - 404: - description: "Not found" - 400: - description: "Not called from a cron job" - "/tasks/removeOrphans": - get: - summary: "Deletes people if the event they were created under no longer exists" - operationId: "taskRemoveOrphans" - tags: - - tasks - responses: - 200: - description: "OK" - 404: - description: "Not found" - 400: - description: "Not called from a cron job" diff --git a/crabfit-backend/yarn.lock b/crabfit-backend/yarn.lock deleted file mode 100644 index 31fff37..0000000 --- a/crabfit-backend/yarn.lock +++ /dev/null @@ -1,3686 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== - dependencies: - "@babel/highlight" "^7.18.6" - -"@babel/helper-validator-identifier@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" - integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== - -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== - dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.9.4": - version "7.18.11" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.11.tgz#68bb07ab3d380affa9a3f96728df07969645d2d9" - integrity sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ== - -"@eslint/eslintrc@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.3.0.tgz#29f92c30bb3e771e4a2048c95fa6855392dfac4f" - integrity sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw== - dependencies: - ajv "^6.12.4" - debug "^4.3.2" - espree "^9.3.2" - globals "^13.15.0" - ignore "^5.2.0" - import-fresh "^3.2.1" - js-yaml "^4.1.0" - minimatch "^3.1.2" - strip-json-comments "^3.1.1" - -"@google-cloud/datastore@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@google-cloud/datastore/-/datastore-7.0.0.tgz#e026db7d12c773230abf9e6b391a6196ef3fb81b" - integrity sha512-CbvqzqWFtwHqH3EWMWHqKzKiM9dV+/Ga0e1oSr7rO2KA6QBHGvUEBbLxRWirHnt6Xz2drjzIVjVE1tuOENHPlQ== - dependencies: - "@google-cloud/promisify" "^2.0.0" - arrify "^2.0.1" - concat-stream "^2.0.0" - extend "^3.0.2" - google-gax "^3.0.1" - is "^3.3.0" - split-array-stream "^2.0.0" - stream-events "^1.0.5" - -"@google-cloud/promisify@^2.0.0": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@google-cloud/promisify/-/promisify-2.0.3.tgz#f934b5cdc939e3c7039ff62b9caaf59a9d89e3a8" - integrity sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw== - -"@grpc/grpc-js@~1.6.0": - version "1.6.10" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.6.10.tgz#b6584c59ef90aa76d878ac92c21785e602f247ff" - integrity sha512-XTX5z/P5kH802MDoVm/rqOil0UwYEOEjf9+NPgfmm5UINIxDzwYaXfVR6z8svCBG8Hlbu/FzkXqhP8J5xaWzSQ== - dependencies: - "@grpc/proto-loader" "^0.7.0" - "@types/node" ">=12.12.47" - -"@grpc/proto-loader@^0.7.0": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.2.tgz#fa63178853afe1473c50cff89fe572f7c8b20154" - integrity sha512-jCdyLIT/tdQ1zhrbTQnJNK5nbDf0GoBpy5jVNywBzzMDF+Vs6uEaHnfz46dMtDxkvwrF2hzk5Z67goliceH0sA== - dependencies: - "@types/long" "^4.0.1" - lodash.camelcase "^4.3.0" - long "^4.0.0" - protobufjs "^7.0.0" - yargs "^16.2.0" - -"@humanwhocodes/config-array@^0.10.4": - version "0.10.4" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.4.tgz#01e7366e57d2ad104feea63e72248f22015c520c" - integrity sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw== - dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.4" - -"@humanwhocodes/gitignore-to-minimatch@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz#316b0a63b91c10e53f242efb4ace5c3b34e8728d" - integrity sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA== - -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== - -"@jridgewell/gen-mapping@^0.3.0": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" - integrity sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A== - dependencies: - "@jridgewell/set-array" "^1.0.1" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/set-array@^1.0.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" - integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== - -"@jridgewell/source-map@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.2.tgz#f45351aaed4527a298512ec72f81040c998580fb" - integrity sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw== - dependencies: - "@jridgewell/gen-mapping" "^0.3.0" - "@jridgewell/trace-mapping" "^0.3.9" - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@^0.3.9": - version "0.3.15" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz#aba35c48a38d3fd84b37e66c9c0423f9744f9774" - integrity sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@lezer/common@^0.15.0", "@lezer/common@^0.15.7": - version "0.15.12" - resolved "https://registry.yarnpkg.com/@lezer/common/-/common-0.15.12.tgz#2f21aec551dd5fd7d24eb069f90f54d5bc6ee5e9" - integrity sha512-edfwCxNLnzq5pBA/yaIhwJ3U3Kz8VAUOTRg0hhxaizaI1N+qxV7EXDv/kLCkLeq2RzSFvxexlaj5Mzfn2kY0Ig== - -"@lezer/lr@^0.15.4": - version "0.15.8" - resolved "https://registry.yarnpkg.com/@lezer/lr/-/lr-0.15.8.tgz#1564a911e62b0a0f75ca63794a6aa8c5dc63db21" - integrity sha512-bM6oE6VQZ6hIFxDNKk8bKPa14hqFrV07J/vHGOeiAbJReIaQXmkVb6xQu4MR+JBTLa5arGRyAAjJe1qaQt3Uvg== - dependencies: - "@lezer/common" "^0.15.0" - -"@lmdb/lmdb-darwin-arm64@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-2.5.2.tgz#bc66fa43286b5c082e8fee0eacc17995806b6fbe" - integrity sha512-+F8ioQIUN68B4UFiIBYu0QQvgb9FmlKw2ctQMSBfW2QBrZIxz9vD9jCGqTCPqZBRbPHAS/vG1zSXnKqnS2ch/A== - -"@lmdb/lmdb-darwin-x64@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-2.5.2.tgz#89d8390041bce6bab24a82a20392be22faf54ffc" - integrity sha512-KvPH56KRLLx4KSfKBx0m1r7GGGUMXm0jrKmNE7plbHlesZMuPJICtn07HYgQhj1LNsK7Yqwuvnqh1QxhJnF1EA== - -"@lmdb/lmdb-linux-arm64@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-2.5.2.tgz#14fe4c96c2bb1285f93797f45915fa35ee047268" - integrity sha512-aLl89VHL/wjhievEOlPocoefUyWdvzVrcQ/MHQYZm2JfV1jUsrbr/ZfkPPUFvZBf+VSE+Q0clWs9l29PCX1hTQ== - -"@lmdb/lmdb-linux-arm@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-2.5.2.tgz#05bde4573ab10cf21827339fe687148f2590cfa1" - integrity sha512-5kQAP21hAkfW5Bl+e0P57dV4dGYnkNIpR7f/GAh6QHlgXx+vp/teVj4PGRZaKAvt0GX6++N6hF8NnGElLDuIDw== - -"@lmdb/lmdb-linux-x64@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-2.5.2.tgz#d2f85afd857d2c33d2caa5b057944574edafcfee" - integrity sha512-xUdUfwDJLGjOUPH3BuPBt0NlIrR7f/QHKgu3GZIXswMMIihAekj2i97oI0iWG5Bok/b+OBjHPfa8IU9velnP/Q== - -"@lmdb/lmdb-win32-x64@2.5.2": - version "2.5.2" - resolved "https://registry.yarnpkg.com/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-2.5.2.tgz#28f643fbc0bec30b07fbe95b137879b6b4d1c9c5" - integrity sha512-zrBczSbXKxEyK2ijtbRdICDygRqWSRPpZMN5dD1T8VMEW5RIhIbwFWw2phDRXuBQdVDpSjalCIUMWMV2h3JaZA== - -"@mapbox/node-pre-gyp@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.0.tgz#2b809e701da0f6729b47fe78ad4b9dc187a7d2e5" - integrity sha512-mEaiD1CURETR/dBIiJAwz0M0Q0mH3gCW4pPMaIlNt97mdzYUVeqGcTJSamgJpS6Tg4tBHDrOJpjdh5fJTLnyNQ== - dependencies: - detect-libc "^1.0.3" - http-proxy-agent "^4.0.1" - mkdirp "^1.0.4" - node-fetch "^2.6.1" - nopt "^5.0.0" - npmlog "^4.1.2" - rimraf "^3.0.2" - semver "^7.3.4" - tar "^6.1.0" - -"@mischnic/json-sourcemap@^0.1.0": - version "0.1.0" - resolved "https://registry.yarnpkg.com/@mischnic/json-sourcemap/-/json-sourcemap-0.1.0.tgz#38af657be4108140a548638267d02a2ea3336507" - integrity sha512-dQb3QnfNqmQNYA4nFSN/uLaByIic58gOXq4Y4XqLOWmOrw73KmJPt/HLyG0wvn1bnR6mBKs/Uwvkh+Hns1T0XA== - dependencies: - "@lezer/common" "^0.15.7" - "@lezer/lr" "^0.15.4" - json5 "^2.2.1" - -"@msgpackr-extract/msgpackr-extract-darwin-arm64@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-2.1.2.tgz#9571b87be3a3f2c46de05585470bc4f3af2f6f00" - integrity sha512-TyVLn3S/+ikMDsh0gbKv2YydKClN8HaJDDpONlaZR+LVJmsxLFUgA+O7zu59h9+f9gX1aj/ahw9wqa6rosmrYQ== - -"@msgpackr-extract/msgpackr-extract-darwin-x64@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-2.1.2.tgz#bfbc6936ede2955218f5621a675679a5fe8e6f4c" - integrity sha512-YPXtcVkhmVNoMGlqp81ZHW4dMxK09msWgnxtsDpSiZwTzUBG2N+No2bsr7WMtBKCVJMSD6mbAl7YhKUqkp/Few== - -"@msgpackr-extract/msgpackr-extract-linux-arm64@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-2.1.2.tgz#22555e28382af2922e7450634c8a2f240bb9eb82" - integrity sha512-vHZ2JiOWF2+DN9lzltGbhtQNzDo8fKFGrf37UJrgqxU0yvtERrzUugnfnX1wmVfFhSsF8OxrfqiNOUc5hko1Zg== - -"@msgpackr-extract/msgpackr-extract-linux-arm@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-2.1.2.tgz#ffb6ae1beea7ac572b6be6bf2a8e8162ebdd8be7" - integrity sha512-42R4MAFeIeNn+L98qwxAt360bwzX2Kf0ZQkBBucJ2Ircza3asoY4CDbgiu9VWklq8gWJVSJSJBwDI+c/THiWkA== - -"@msgpackr-extract/msgpackr-extract-linux-x64@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-2.1.2.tgz#7caf62eebbfb1345de40f75e89666b3d4194755f" - integrity sha512-RjRoRxg7Q3kPAdUSC5EUUPlwfMkIVhmaRTIe+cqHbKrGZ4M6TyCA/b5qMaukQ/1CHWrqYY2FbKOAU8Hg0pQFzg== - -"@msgpackr-extract/msgpackr-extract-win32-x64@2.1.2": - version "2.1.2" - resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-2.1.2.tgz#f2d8b9ddd8d191205ed26ce54aba3dfc5ae3e7c9" - integrity sha512-rIZVR48zA8hGkHIK7ED6+ZiXsjRCcAVBJbm8o89OKAMTmEAQ2QvoOxoiu3w2isAaWwzgtQIOFIqHwvZDyLKCvw== - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@parcel/bundler-default@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/bundler-default/-/bundler-default-2.7.0.tgz#17d94be7185f29773aa21454063cbba3cdc03d49" - integrity sha512-PU5MtWWhc+dYI9x8mguYnm9yiG6TkI7niRpxgJgtqAyGHuEyNXVBQQ0X+qyOF4D9LdankBf8uNN18g31IET2Zg== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/cache@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/cache/-/cache-2.7.0.tgz#cc4b99685c7ff0fc20fbc321f4b6850d6e0c6811" - integrity sha512-JlXNoZXcWzLKdDlfeF3dIj5Vtel5T9vtdBN72PJ+cjC4qNHk4Uwvc5sfOBELuibGN0bVu2bwY9nUgSwCiB1iIA== - dependencies: - "@parcel/fs" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/utils" "2.7.0" - lmdb "2.5.2" - -"@parcel/codeframe@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/codeframe/-/codeframe-2.7.0.tgz#b6e4ad6100938edbed1b6c72b37f609e1abaf931" - integrity sha512-UTKx0jejJmmO1dwTHSJuRgrO8N6PMlkxRT6sew8N6NC3Bgv6pu0EbO+RtlWt/jCvzcdLOPdIoTzj4MMZvgcMYg== - dependencies: - chalk "^4.1.0" - -"@parcel/compressor-raw@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/compressor-raw/-/compressor-raw-2.7.0.tgz#3f8677e6371ef099cd9e4bd2a899884dc8eb571b" - integrity sha512-SCXwnOOQT6EmpusBsYWNQ/RFri+2JnKuE0gMSf2dROl2xbererX45FYzeDplWALCKAdjMNDpFwU+FyMYoVZSCQ== - dependencies: - "@parcel/plugin" "2.7.0" - -"@parcel/config-default@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/config-default/-/config-default-2.7.0.tgz#2f6cec9c185b89e40f343549295cad21295621a8" - integrity sha512-ZzsLr97AYrz8c9k6qn3DlqPzifi3vbP7q3ynUrAFxmt0L4+K0H9N508ZkORYmCgaFjLIQ8Y3eWpwCJ0AewPNIg== - dependencies: - "@parcel/bundler-default" "2.7.0" - "@parcel/compressor-raw" "2.7.0" - "@parcel/namer-default" "2.7.0" - "@parcel/optimizer-css" "2.7.0" - "@parcel/optimizer-htmlnano" "2.7.0" - "@parcel/optimizer-image" "2.7.0" - "@parcel/optimizer-svgo" "2.7.0" - "@parcel/optimizer-terser" "2.7.0" - "@parcel/packager-css" "2.7.0" - "@parcel/packager-html" "2.7.0" - "@parcel/packager-js" "2.7.0" - "@parcel/packager-raw" "2.7.0" - "@parcel/packager-svg" "2.7.0" - "@parcel/reporter-dev-server" "2.7.0" - "@parcel/resolver-default" "2.7.0" - "@parcel/runtime-browser-hmr" "2.7.0" - "@parcel/runtime-js" "2.7.0" - "@parcel/runtime-react-refresh" "2.7.0" - "@parcel/runtime-service-worker" "2.7.0" - "@parcel/transformer-babel" "2.7.0" - "@parcel/transformer-css" "2.7.0" - "@parcel/transformer-html" "2.7.0" - "@parcel/transformer-image" "2.7.0" - "@parcel/transformer-js" "2.7.0" - "@parcel/transformer-json" "2.7.0" - "@parcel/transformer-postcss" "2.7.0" - "@parcel/transformer-posthtml" "2.7.0" - "@parcel/transformer-raw" "2.7.0" - "@parcel/transformer-react-refresh-wrap" "2.7.0" - "@parcel/transformer-svg" "2.7.0" - -"@parcel/core@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/core/-/core-2.7.0.tgz#3310f48230bd618735f199f159f37e6b45ed2710" - integrity sha512-7yKZUdh314Q/kU/9+27ZYTfcnXS6VYHuG+iiUlIohnvUUybxLqVJhdMU9Q+z2QcPka1IdJWz4K4Xx0y6/4goyg== - dependencies: - "@mischnic/json-sourcemap" "^0.1.0" - "@parcel/cache" "2.7.0" - "@parcel/diagnostic" "2.7.0" - "@parcel/events" "2.7.0" - "@parcel/fs" "2.7.0" - "@parcel/graph" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/package-manager" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - "@parcel/workers" "2.7.0" - abortcontroller-polyfill "^1.1.9" - base-x "^3.0.8" - browserslist "^4.6.6" - clone "^2.1.1" - dotenv "^7.0.0" - dotenv-expand "^5.1.0" - json5 "^2.2.0" - msgpackr "^1.5.4" - nullthrows "^1.1.1" - semver "^5.7.1" - -"@parcel/css-darwin-arm64@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-darwin-arm64/-/css-darwin-arm64-1.12.2.tgz#4215585dac699f0f75015f5b47254867ac1221d3" - integrity sha512-6VvsoYSltBiUh/uyfPzQ+I3DiTFN7tmRv6zm1LH98J7GGCDDhbYEtbQjjCs15ex6fVn1ORZK0JO+mMlsg1JwTA== - -"@parcel/css-darwin-x64@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-darwin-x64/-/css-darwin-x64-1.12.2.tgz#eeb4e04c512580bd531b5ffa9c34456e9799fdb9" - integrity sha512-3J0/LrDvt5vevOisnrE0q5mEcuiAY+K7OZwIv84SAnrbjlL5sshmIaaNzL869kb4thza+RClEj0mS5XTm1IUEw== - -"@parcel/css-linux-arm-gnueabihf@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm-gnueabihf/-/css-linux-arm-gnueabihf-1.12.2.tgz#ccd813bbc9b9d845fb8f6ed9c7c22c745cda007b" - integrity sha512-OsX7I3dhBvnxEbAH++08RFe7yhjRp33ulzrCvJTMOP9YkxEEJ8qId3sNzJBHIVQzHyTlPTnBRHbSDhU3TFe/eQ== - -"@parcel/css-linux-arm64-gnu@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-gnu/-/css-linux-arm64-gnu-1.12.2.tgz#7959fbcbd38c9b9c2c24c6c2def4ec2df370b705" - integrity sha512-R1Kqw+1Rsru9Q4+qvUEC6B8P21bpqhuF9rv8GmBmmnF1i2hMZ1JiY+uh/ej8IaRV0O3fAHeQGIyGBWx6qWDpcw== - -"@parcel/css-linux-arm64-musl@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-arm64-musl/-/css-linux-arm64-musl-1.12.2.tgz#ffc3fc62db9b8a19f8be61028abbcb7c44d90fa6" - integrity sha512-nwixgM4SEgPUQata9aAiJW0A5Q9ms+xim1tXT1i+91kOei4Fu2Wr2OuofMk+mlhbgmGKCTcu4gzMPReGxUhuRA== - -"@parcel/css-linux-x64-gnu@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-gnu/-/css-linux-x64-gnu-1.12.2.tgz#15619756ba62558243ae996e257b1cca90f534eb" - integrity sha512-cJYVMHnQSGhDwQByyvjFZppjMBNlgxXl/R4cX5DwrQE0QZmK/42BYnMp92rvoprEG6LRyRoiGtCjyfYTPWajog== - -"@parcel/css-linux-x64-musl@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-linux-x64-musl/-/css-linux-x64-musl-1.12.2.tgz#de61e2bdec54609f7b681acfbd04e9fb57a5ef02" - integrity sha512-u9zdO/d831/74Tf+TdPUfaIuB9v6FD4Xz8UdWUDOXgQqaOlnJ9fAsAM39EkoWlMxPPljY3f4ay6irSe1a4XgSA== - -"@parcel/css-win32-x64-msvc@1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css-win32-x64-msvc/-/css-win32-x64-msvc-1.12.2.tgz#086586fce31d1e05340c2e31efc32d40aa9ee05a" - integrity sha512-kCAKr3vKqvPUv9oXBG3pGZQz5il3sEk35dpmTXFa/7eDNKR5XyLpiJs8JwWJTFfuUqroymDSXA1bCcjvNEYcAg== - -"@parcel/css@^1.12.2": - version "1.12.2" - resolved "https://registry.yarnpkg.com/@parcel/css/-/css-1.12.2.tgz#63eacc9fcdf58e4d9639db34271834394705b7b2" - integrity sha512-Sa0PvZu5u877CupQA8IjEATqjJFynBfA7LxbcyutFe2LDCRSqB5Bm08jKFScyaz56qjZNIxZxXk2SApNkOvoAA== - dependencies: - detect-libc "^1.0.3" - optionalDependencies: - "@parcel/css-darwin-arm64" "1.12.2" - "@parcel/css-darwin-x64" "1.12.2" - "@parcel/css-linux-arm-gnueabihf" "1.12.2" - "@parcel/css-linux-arm64-gnu" "1.12.2" - "@parcel/css-linux-arm64-musl" "1.12.2" - "@parcel/css-linux-x64-gnu" "1.12.2" - "@parcel/css-linux-x64-musl" "1.12.2" - "@parcel/css-win32-x64-msvc" "1.12.2" - -"@parcel/diagnostic@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/diagnostic/-/diagnostic-2.7.0.tgz#cf2596a20ce9277334616e12bbdac98490189e99" - integrity sha512-pdq/cTwVoL0n8yuDCRXFRSQHVWdmmIXPt3R3iT4KtYDYvOrMT2dLPT79IMqQkhYPANW8GuL15n/WxRngfRdkug== - dependencies: - "@mischnic/json-sourcemap" "^0.1.0" - nullthrows "^1.1.1" - -"@parcel/events@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/events/-/events-2.7.0.tgz#b6db8464d45626686134d412d3a36d024ffb1482" - integrity sha512-kQDwMKgZ1U4M/G17qeDYF6bW5kybluN6ajYPc7mZcrWg+trEI/oXi81GMFaMX0BSUhwhbiN5+/Vb2wiG/Sn6ig== - -"@parcel/fs-search@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/fs-search/-/fs-search-2.7.0.tgz#15e658006039ddc7b92528df5266ee2b9c47b6a4" - integrity sha512-K1Hv25bnRpwQVA15RvcRuB8ZhfclnCHA8N8L6w7Ul1ncSJDxCIkIAc5hAubYNNYW3kWjCC2SOaEgFKnbvMllEQ== - dependencies: - detect-libc "^1.0.3" - -"@parcel/fs@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/fs/-/fs-2.7.0.tgz#c9a0c60bdbef7101ff47f2db6b23814c3db06007" - integrity sha512-PU5fo4Hh8y03LZgemgVREttc0wyHQUNmsJCybxTB7EjJie2CqJRumo+DFppArlvdchLwJdc9em03yQV/GNWrEg== - dependencies: - "@parcel/fs-search" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - "@parcel/watcher" "^2.0.0" - "@parcel/workers" "2.7.0" - -"@parcel/graph@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/graph/-/graph-2.7.0.tgz#2ae326c62764aaa53324b89d9c83ec0bc6ad55bf" - integrity sha512-Q6E94GS6q45PtsZh+m+gvFRp/N1Qopxhu2sxjcWsGs5iBd6IWn2oYLWOH5iVzEjWuYpW2HkB08lH6J50O63uOA== - dependencies: - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/hash@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/hash/-/hash-2.7.0.tgz#8825cff69a0bc4816737415e6e2aa29e8671c0b1" - integrity sha512-k6bSKnIlPJMPU3yjQzfgfvF9zuJZGOAlJgzpL4BbWvdbE8BTdjzLcFn0Ujrtud94EgIkiXd22sC2HpCUWoHGdA== - dependencies: - detect-libc "^1.0.3" - xxhash-wasm "^0.4.2" - -"@parcel/logger@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/logger/-/logger-2.7.0.tgz#1aa1de0458bdd613714ce4031134d92135aec590" - integrity sha512-qjMY/bYo38+o+OiIrTRldU9CwL1E7J72t+xkTP8QIcUxLWz5LYR0YbynZUVulmBSfqsykjjxCy4a+8siVr+lPw== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/events" "2.7.0" - -"@parcel/markdown-ansi@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/markdown-ansi/-/markdown-ansi-2.7.0.tgz#4ba70e3661ce06cd8fd2eb3f7b84028853a586e4" - integrity sha512-ipOX0D6FVZFEXeb/z8MnTMq2RQEIuaILY90olVIuHEFLHHfOPEn+RK3u13HA1ChF5/9E3cMD79tu6x9JL9Kqag== - dependencies: - chalk "^4.1.0" - -"@parcel/namer-default@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/namer-default/-/namer-default-2.7.0.tgz#e008097586016f16509834db11985dcc772c314c" - integrity sha512-lIKMdsmi//7fepecNDYmJYzBlL91HifPsX03lJCdu1dC6q5fBs+gG0XjKKG7yPnSCw1qH/4m7drzt9+dRZYAHQ== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/node-resolver-core@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/node-resolver-core/-/node-resolver-core-2.7.0.tgz#468074aa58a2f0026a492607153079ebb16308e3" - integrity sha512-5UJQHalqMxdhJIs2hhqQzFfQpF7+NAowsRq064lYtiRvcD8wMr3OOQ9wd1iazGpFSl4JKdT7BwDU9/miDJmanQ== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - semver "^5.7.1" - -"@parcel/optimizer-css@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-css/-/optimizer-css-2.7.0.tgz#460cde19b9ee61efed577473cb0822796b6e8b1a" - integrity sha512-IfnOMACqhcAclKyOW9X9JpsknB6OShk9OVvb8EvbDTKHJhQHNNmzE88OkSI/pS3ZVZP9Zj+nWcVHguV+kvDeiQ== - dependencies: - "@parcel/css" "^1.12.2" - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - browserslist "^4.6.6" - nullthrows "^1.1.1" - -"@parcel/optimizer-htmlnano@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-htmlnano/-/optimizer-htmlnano-2.7.0.tgz#d2e843b539a430fcca723f08efcee26f98b3d40b" - integrity sha512-5QrGdWS5Hi4VXE3nQNrGqugmSXt68YIsWwKRAdarOxzyULSJS3gbCiQOXqIPRJobfZjnSIcdtkyxSiCUe1inIA== - dependencies: - "@parcel/plugin" "2.7.0" - htmlnano "^2.0.0" - nullthrows "^1.1.1" - posthtml "^0.16.5" - svgo "^2.4.0" - -"@parcel/optimizer-image@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-image/-/optimizer-image-2.7.0.tgz#180d7709e6268e0634967eb265bf901aba2471ce" - integrity sha512-EnaXz5UjR67FUu0BEcqZTT9LsbB/iFAkkghCotbnbOuC5QQsloq6tw54TKU3y+R3qsjgUoMtGxPcGfVoXxZXYw== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - "@parcel/workers" "2.7.0" - detect-libc "^1.0.3" - -"@parcel/optimizer-svgo@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-svgo/-/optimizer-svgo-2.7.0.tgz#9b4ab38a9d3c99504cf399a5c7709e5d2a615e71" - integrity sha512-IO1JV4NpfP3V7FrhsqCcV8pDQIHraFi1/ZvEJyssITxjH49Im/txKlwMiQuZZryAPn8Xb8g395Muawuk6AK6sg== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - svgo "^2.4.0" - -"@parcel/optimizer-terser@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/optimizer-terser/-/optimizer-terser-2.7.0.tgz#b9ef408f45714952d1502940b6a63e5ebd3f0940" - integrity sha512-07VZjIO8xsl2/WmS/qHI8lI/cpu47iS9eRpqwfZEEsdk1cfz50jhWkmFudHBxiHGMfcZ//1+DdaPg9RDBWZtZA== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - terser "^5.2.0" - -"@parcel/package-manager@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/package-manager/-/package-manager-2.7.0.tgz#5de1bf5c94d95330e98dffb2a66c22d1f20c4c8a" - integrity sha512-wmfSX1mRrTi8MeA4KrnPk/x7zGUsILCQmTo6lA4gygzAxDbM1pGuyFN8/Kt0y0SFO2lbljARtD/4an5qdotH+Q== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/fs" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - "@parcel/workers" "2.7.0" - semver "^5.7.1" - -"@parcel/packager-css@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-css/-/packager-css-2.7.0.tgz#060af8b96e5ad53620b4021610364804e26d69b2" - integrity sha512-44nzZwu+ssGuiFmYM6cf/Y4iChiUZ4DUzzpegnGlhXtKJKe4NHntxThJynuRZWKN2AAf48avApDpimg2jW0KDw== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/packager-html@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-html/-/packager-html-2.7.0.tgz#4f86698d05f5ec5c3ebc8b46102a6ae42c58a24a" - integrity sha512-Zgqd7sdcY/UnR370GR0q2ilmEohUDXsO8A1F28QCJzIsR1iCB6KRUT74+pawfQ1IhXZLaaFLLYe0UWcfm0JeXg== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - posthtml "^0.16.5" - -"@parcel/packager-js@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-js/-/packager-js-2.7.0.tgz#e694cc731d75697e63d3d4be0bdbbe4a2ae8f1fc" - integrity sha512-wTRdM81PgRVDzWGXdWmqLwguWnTYWzhEDdjXpW2n8uMOu/CjHhMtogk65aaYk3GOnq6OBL/NsrmBiV/zKPj1vA== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - globals "^13.2.0" - nullthrows "^1.1.1" - -"@parcel/packager-raw@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-raw/-/packager-raw-2.7.0.tgz#5b68de05c19bbcf438f40b4ae9f45411ba9739bd" - integrity sha512-jg2Zp8dI5VpIQlaeahXDCfrPN9m/DKht1NkR9P2CylMAwqCcc1Xc1RRiF0wfwcPZpPMpq1265n+4qnB7rjGBlA== - dependencies: - "@parcel/plugin" "2.7.0" - -"@parcel/packager-svg@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/packager-svg/-/packager-svg-2.7.0.tgz#e7e96503c86815eca285b9cc8908105075d9ab38" - integrity sha512-EmJg3HpD6/xxKBjir/CdCKJZwI24iVfBuxRS9LUp3xHAIebOzVh1z6IN+i2Di5+NyRwfOFaLliL4uMa1zwbyCA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - posthtml "^0.16.4" - -"@parcel/plugin@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/plugin/-/plugin-2.7.0.tgz#0211281025d02afbc5a23fba237b7aae02e34e51" - integrity sha512-qqgx+nnMn6/0lRc4lKbLGmhNtBiT93S2gFNB4Eb4Pfz/SxVYoW+fmml+KdfOSiZffWOAH5L6NwhyD7N8aSikzw== - dependencies: - "@parcel/types" "2.7.0" - -"@parcel/reporter-cli@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/reporter-cli/-/reporter-cli-2.7.0.tgz#86499624e258034001b64c10b14e23ae4b92f44b" - integrity sha512-80gEODg8cnAmnxGVuaSVDo8JJ54P9AA2bHwSs1cIkHWlJ3BjDQb83H31bBHncJ5Kn5kQ/j+7WjlqHpTCiOR9PA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - chalk "^4.1.0" - term-size "^2.2.1" - -"@parcel/reporter-dev-server@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/reporter-dev-server/-/reporter-dev-server-2.7.0.tgz#9bd3d10f745e0cbc9ab983ec046953c2c564dcb2" - integrity sha512-ySuou5addK8fGue8aXzo536BaEjMujDrEc1xkp4TasInXHVcA98b+SYX5NAZTGob5CxKvZQ5ylhg77zW30B+iA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - -"@parcel/resolver-default@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/resolver-default/-/resolver-default-2.7.0.tgz#648a257b81abe2eda09700d8f36348a88ea0442e" - integrity sha512-v8TvWsbLK7/q7n4gv6OrYNbW18xUx4zKbVMGZb1u4yMhzEH4HFr1D9OeoTq3jk+ximAigds8B6triQbL5exF7A== - dependencies: - "@parcel/node-resolver-core" "2.7.0" - "@parcel/plugin" "2.7.0" - -"@parcel/runtime-browser-hmr@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-browser-hmr/-/runtime-browser-hmr-2.7.0.tgz#aed79b96c0d97021f56f2b7e35bc2d4f70869e26" - integrity sha512-PLbMLdclQeYsi2LkilZVGFV1n3y55G1jaBvby4ekedUZjMw3SWdMY2tDxgSDdFWfLCnYHJXdGUQSzGGi1kPzjA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - -"@parcel/runtime-js@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-js/-/runtime-js-2.7.0.tgz#63dd744e96c3554ac86778ffce1c8055e7653fce" - integrity sha512-9/YUZTBNrSN2H6rbz/o1EOM0O7I3ZR/x9IDzxjJBD6Mi+0uCgCD02aedare/SNr1qgnbZZWmhpOzC+YgREcfLA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/runtime-react-refresh@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-react-refresh/-/runtime-react-refresh-2.7.0.tgz#c56b847ef1144264e918339381e040ffe811efc5" - integrity sha512-vDKO0rWqRzEpmvoZ4kkYUiSsTxT5NnH904BFPFxKI0wJCl6yEmPuEifmATo73OuYhP6jIP3Qfl1R4TtiDFPJ1Q== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - react-error-overlay "6.0.9" - react-refresh "^0.9.0" - -"@parcel/runtime-service-worker@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/runtime-service-worker/-/runtime-service-worker-2.7.0.tgz#352c3722ec635eee2898d9698541e3c1f53e8906" - integrity sha512-uD2pAV0yV6+e7JaWH4KVPbG+zRCrxr/OACyS9tIh+Q/R1vRmh8zGM3yhdrcoiZ7tFOnM72vd6xY11eTrUsSVig== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/source-map@^2.0.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@parcel/source-map/-/source-map-2.1.0.tgz#bd47aa0a93ae261436f7c5da40b09856e1a331d5" - integrity sha512-E7UOEIof2o89LrKk1agSLmwakjigmEdDp1ZaEdsLVEvq63R/bul4Ij5CT+0ZDcijGpl5tnTbQADY9EyYGtjYgQ== - dependencies: - detect-libc "^1.0.3" - -"@parcel/transformer-babel@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-babel/-/transformer-babel-2.7.0.tgz#eae98d50a99cd722c3146cd57c479bfb86f537b2" - integrity sha512-7iklDXXnKH1530+QbI+e4kIJ+Q1puA1ulRS10db3aUJMj5GnvXGDFwhSZ7+T1ps66QHO7cVO29VlbqiRDarH1Q== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - browserslist "^4.6.6" - json5 "^2.2.0" - nullthrows "^1.1.1" - semver "^5.7.0" - -"@parcel/transformer-css@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-css/-/transformer-css-2.7.0.tgz#d0879ec04191e5ba3eadb6fc06b7ec0db3f5c3f6" - integrity sha512-J4EpWK9spQpXyNCmKK8Xnane0xW/1B/EAmfp7Fiv7g+5yUjY4ODf4KUugvE+Eb2gekPkhOKNHermO2KrX0/PFA== - dependencies: - "@parcel/css" "^1.12.2" - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - browserslist "^4.6.6" - nullthrows "^1.1.1" - -"@parcel/transformer-html@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-html/-/transformer-html-2.7.0.tgz#5397a924fea683ef2c345f36f99f67f0181d6967" - integrity sha512-wYJl5rn81W+Rlk9oQwDJcjoVsWVDKyeri84FzmlGXOsg0EYgnqOiG+3MDM8GeZjfuGe5fuoum4eqZeS0WdUHXw== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/plugin" "2.7.0" - nullthrows "^1.1.1" - posthtml "^0.16.5" - posthtml-parser "^0.10.1" - posthtml-render "^3.0.0" - semver "^5.7.1" - -"@parcel/transformer-image@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-image/-/transformer-image-2.7.0.tgz#e569ffc426c6060bc4dcccc0347c526930abfba4" - integrity sha512-mhi9/R5/ULhCkL2COVIKhNFoLDiZwQgprdaTJr5fnODggVxEX5o7ebFV6KNLMTEkwZUJWoB1hL0ziI0++DtoFA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - "@parcel/workers" "2.7.0" - nullthrows "^1.1.1" - -"@parcel/transformer-js@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-js/-/transformer-js-2.7.0.tgz#1e69295a11bf70d880cdd3846cf89016a74aac50" - integrity sha512-mzerR+D4rDomUSIk5RSTa2w+DXBdXUeQrpDO74WCDdpDi1lIl8ppFpqtmU7O6y6p8QsgkmS9b0g/vhcry6CJTA== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/utils" "2.7.0" - "@parcel/workers" "2.7.0" - "@swc/helpers" "^0.4.2" - browserslist "^4.6.6" - detect-libc "^1.0.3" - nullthrows "^1.1.1" - regenerator-runtime "^0.13.7" - semver "^5.7.1" - -"@parcel/transformer-json@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-json/-/transformer-json-2.7.0.tgz#c203f74cd445ce93eb833dd88be8127d1eadc6c3" - integrity sha512-RQjuxBpYOch+kr4a0zi77KJtOLTPYRM7iq4NN80zKnA0r0dwDUCxZBtaj2l0O0o3R4MMJnm+ncP+cB7XR7dZYA== - dependencies: - "@parcel/plugin" "2.7.0" - json5 "^2.2.0" - -"@parcel/transformer-postcss@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-postcss/-/transformer-postcss-2.7.0.tgz#66be4469ae9186c89638ff67ec5808139caaeb8e" - integrity sha512-b6RskXBWf0MjpC9qjR2dQ1ZdRnlOiKYseG5CEovWCqM218RtdydFKz7jS+5Gxkb6qBtOG7zGPONXdPe+gTILcA== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - clone "^2.1.1" - nullthrows "^1.1.1" - postcss-value-parser "^4.2.0" - semver "^5.7.1" - -"@parcel/transformer-posthtml@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-posthtml/-/transformer-posthtml-2.7.0.tgz#67d5761b895574edb771f7ab1b24ec80775f58bd" - integrity sha512-cP8YOiSynWJ1ycmBlhnnHeuQb2cwmklZ+BNyLUktj5p78kDy2de7VjX+dRNRHoW4H9OgEcSF4UEfDVVz5RYIhw== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - nullthrows "^1.1.1" - posthtml "^0.16.5" - posthtml-parser "^0.10.1" - posthtml-render "^3.0.0" - semver "^5.7.1" - -"@parcel/transformer-raw@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-raw/-/transformer-raw-2.7.0.tgz#d6d8b94d1b33efc3dbf748ec1954c8917a2e1566" - integrity sha512-sDnItWCFSDez0izK1i5cgv+kXzZTbcJh4rNpVIgmE1kBLvAz608sqgcCkavb2wVJIvLesxYM+5G4p1CwkDlZ1g== - dependencies: - "@parcel/plugin" "2.7.0" - -"@parcel/transformer-react-refresh-wrap@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-react-refresh-wrap/-/transformer-react-refresh-wrap-2.7.0.tgz#e206b529d4a3444e14ba329d3323c5be3ded25d2" - integrity sha512-1vRmIJzyBA1nIiXTAU6tZExq2FvJj/2F0ft6KDw8GYPv0KjmdiPo/PmaZ7JeSVOM6SdXQIQCbTmp1vkMP7DtkA== - dependencies: - "@parcel/plugin" "2.7.0" - "@parcel/utils" "2.7.0" - react-refresh "^0.9.0" - -"@parcel/transformer-svg@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/transformer-svg/-/transformer-svg-2.7.0.tgz#c2a7d10bdbc6aeb2ae7ef85db5b583574807071c" - integrity sha512-ioER37zceuuE+K6ZrnjCyMUWEnv+63hIAFResc1OXxRhyt+7kzMz9ZqK0Mt6QMLwl1dxhkLmrU41n9IxzKZuSQ== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/plugin" "2.7.0" - nullthrows "^1.1.1" - posthtml "^0.16.5" - posthtml-parser "^0.10.1" - posthtml-render "^3.0.0" - semver "^5.7.1" - -"@parcel/types@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/types/-/types-2.7.0.tgz#c89e95964339324c1931ef7a17906a72291d6b73" - integrity sha512-+dhXVUnseTCpJvBTGMp0V6X13z6O/A/+CUtwEpMGZ8XSmZ4Gk44GvaTiBOp0bJpWG4fvCKp+UmC8PYbrDiiziw== - dependencies: - "@parcel/cache" "2.7.0" - "@parcel/diagnostic" "2.7.0" - "@parcel/fs" "2.7.0" - "@parcel/package-manager" "2.7.0" - "@parcel/source-map" "^2.0.0" - "@parcel/workers" "2.7.0" - utility-types "^3.10.0" - -"@parcel/utils@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/utils/-/utils-2.7.0.tgz#f795d0f43efdd449ab0bbfac3632cd7f3ec0e4dd" - integrity sha512-jNZ5bIGg1r1RDRKi562o4kuVwnz+XJ2Ie3b0Zwrqwvgfj6AbRFIKzDd+h85dWWmcDYzKUbHp11u6VJl1u8Vapg== - dependencies: - "@parcel/codeframe" "2.7.0" - "@parcel/diagnostic" "2.7.0" - "@parcel/hash" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/markdown-ansi" "2.7.0" - "@parcel/source-map" "^2.0.0" - chalk "^4.1.0" - -"@parcel/watcher@^2.0.0": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.5.tgz#f913a54e1601b0aac972803829b0eece48de215b" - integrity sha512-x0hUbjv891omnkcHD7ZOhiyyUqUUR6MNjq89JhEI3BxppeKWAm6NPQsqqRrAkCJBogdT/o/My21sXtTI9rJIsw== - dependencies: - node-addon-api "^3.2.1" - node-gyp-build "^4.3.0" - -"@parcel/workers@2.7.0": - version "2.7.0" - resolved "https://registry.yarnpkg.com/@parcel/workers/-/workers-2.7.0.tgz#d74955d361337127227912a5ab26cb3079ebfc78" - integrity sha512-99VfaOX+89+RaoTSyH9ZQtkMBFZBFMvJmVJ/GeJT6QCd2wtKBStTHlaSnQOkLD/iRjJCNwV2xpZmm8YkTwV+hg== - dependencies: - "@parcel/diagnostic" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/types" "2.7.0" - "@parcel/utils" "2.7.0" - chrome-trace-event "^1.0.2" - nullthrows "^1.1.1" - -"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" - integrity sha1-m4sMxmPWaafY9vXQiToU00jzD78= - -"@protobufjs/base64@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" - integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== - -"@protobufjs/codegen@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" - integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== - -"@protobufjs/eventemitter@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" - integrity sha1-NVy8mLr61ZePntCV85diHx0Ga3A= - -"@protobufjs/fetch@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" - integrity sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU= - dependencies: - "@protobufjs/aspromise" "^1.1.1" - "@protobufjs/inquire" "^1.1.0" - -"@protobufjs/float@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" - integrity sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E= - -"@protobufjs/inquire@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" - integrity sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik= - -"@protobufjs/path@^1.1.2": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" - integrity sha1-bMKyDFya1q0NzP0hynZz2Nf79o0= - -"@protobufjs/pool@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" - integrity sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q= - -"@protobufjs/utf8@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" - integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= - -"@swc/helpers@^0.4.2": - version "0.4.7" - resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.7.tgz#25a32e462e799a5a102eb9c241f73bbc4cb806a7" - integrity sha512-jJKr/2JOivCQxb5Xpli3asedRLH34QgJ3G+7gm6CoCOwt/LDDX9g67OuxvjFOiYZfngWYB66ZbjU6cUNtQdavg== - dependencies: - tslib "^2.4.0" - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@trysound/sax@0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" - integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== - -"@types/linkify-it@*": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9" - integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA== - -"@types/long@^4.0.0", "@types/long@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9" - integrity sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w== - -"@types/markdown-it@^12.2.3": - version "12.2.3" - resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51" - integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ== - dependencies: - "@types/linkify-it" "*" - "@types/mdurl" "*" - -"@types/mdurl@*": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9" - integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== - -"@types/node@>=12.12.47": - version "14.14.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" - integrity sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g== - -"@types/node@>=13.7.0": - version "18.7.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.8.tgz#6bbf2be6fbf9c187a5040d4277d24a06a18957a1" - integrity sha512-/YP55EMK2341JkODUb8DM9O0x1SIz2aBvyF33Uf1c76St3VpsMXEIW0nxuKkq/5cxnbz0RD9cfwNZHEAZQD3ag== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -abbrev@1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" - integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== - -abort-controller@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" - integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== - dependencies: - event-target-shim "^5.0.0" - -abortcontroller-polyfill@^1.1.9: - version "1.7.3" - resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" - integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== - -accepts@~1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" - integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== - dependencies: - mime-types "~2.1.34" - negotiator "0.6.3" - -acorn-jsx@^5.3.2: - version "5.3.2" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" - integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== - -acorn@^8.5.0, acorn@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ajv@^6.10.0, ajv@^6.12.4: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - -argparse@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" - integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - -arrify@^2.0.0, arrify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" - integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= - -base-x@^3.0.8: - version "3.0.9" - resolved "https://registry.yarnpkg.com/base-x/-/base-x-3.0.9.tgz#6349aaabb58526332de9f60995e548a53fe21320" - integrity sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ== - dependencies: - safe-buffer "^5.0.1" - -base64-js@^1.3.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -bcrypt@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" - integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.0" - node-addon-api "^3.1.0" - -bignumber.js@^9.0.0: - version "9.0.1" - resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5" - integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== - dependencies: - bytes "3.1.2" - content-type "~1.0.4" - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.10.3" - raw-body "2.5.1" - type-is "~1.6.18" - unpipe "1.0.0" - -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== - dependencies: - balanced-match "^1.0.0" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browserslist@^4.6.6: - version "4.21.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a" - integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ== - dependencies: - caniuse-lite "^1.0.30001370" - electron-to-chromium "^1.4.202" - node-releases "^2.0.6" - update-browserslist-db "^1.0.5" - -buffer-equal-constant-time@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" - integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= - -buffer-from@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== - -bytes@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" - integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== - -call-bind@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -caniuse-lite@^1.0.30001370: - version "1.0.30001379" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001379.tgz#c42f61d3ee45152ad0fa5005a6b5b34c27797a07" - integrity sha512-zXf+qxuN8OJrK5Bl5HbJg8cc5/Zm01WNW4ooVWUh92YlKqQZW3fwN5lXLB+kI8wkP5vTWkIIN+rutZuJhf4ykw== - -catharsis@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.9.0.tgz#40382a168be0e6da308c277d3a2b3eb40c7d2121" - integrity sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A== - dependencies: - lodash "^4.17.15" - -chalk@^2.0.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -chokidar@^3.5.2: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chownr@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" - integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" - integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== - -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^7.0.0, commander@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" - integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -concat-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-2.0.0.tgz#414cf5af790a48c60ab9be4527d56d5e41133cb1" - integrity sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A== - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^3.0.2" - typedarray "^0.0.6" - -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - -content-disposition@0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" - integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== - dependencies: - safe-buffer "5.2.1" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" - integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= - -cors@^2.8.5: - version "2.8.5" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" - integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== - dependencies: - object-assign "^4" - vary "^1" - -cosmiconfig@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -css-select@^4.1.3: - version "4.3.0" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - -css-tree@^1.1.2, css-tree@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" - integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== - dependencies: - mdn-data "2.0.14" - source-map "^0.6.1" - -css-what@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" - integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== - -csso@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529" - integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA== - dependencies: - css-tree "^1.1.2" - -dayjs@^1.11.5: - version "1.11.5" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" - integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== - -debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== - dependencies: - ms "2.1.2" - -debug@^3.2.7: - version "3.2.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" - integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== - dependencies: - ms "^2.1.1" - -debug@^4.3.2: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -deep-is@^0.1.3, deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - -depd@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - -destroy@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" - integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== - -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= - -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" - integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== - -domhandler@^4.2.0, domhandler@^4.2.2, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -dotenv-expand@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" - integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== - -dotenv@^16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" - integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== - -dotenv@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-7.0.0.tgz#a2be3cd52736673206e8a85fb5210eea29628e7c" - integrity sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g== - -duplexify@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.1.tgz#7027dc374f157b122a8ae08c2d3ea4d2d953aa61" - integrity sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - -ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" - integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== - dependencies: - safe-buffer "^5.0.1" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.4.202: - version "1.4.225" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz#3e27bdd157cbaf19768141f2e0f0f45071e52338" - integrity sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - -entities@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4" - integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q== - -entities@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.1.0.tgz#992d3129cf7df6870b96c57858c249a120f8b8b5" - integrity sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w== - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -escodegen@^1.13.0: - version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -eslint-scope@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642" - integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw== - dependencies: - esrecurse "^4.3.0" - estraverse "^5.2.0" - -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - -eslint@^8.22.0: - version "8.22.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.22.0.tgz#78fcb044196dfa7eef30a9d65944f6f980402c48" - integrity sha512-ci4t0sz6vSRKdmkOGmprBo6fmI4PrphDFMy5JEq/fNS0gQkJM3rLmrqcp8ipMcdobH3KtUP40KniAE9W19S4wA== - dependencies: - "@eslint/eslintrc" "^1.3.0" - "@humanwhocodes/config-array" "^0.10.4" - "@humanwhocodes/gitignore-to-minimatch" "^1.0.2" - ajv "^6.10.0" - chalk "^4.0.0" - cross-spawn "^7.0.2" - debug "^4.3.2" - doctrine "^3.0.0" - escape-string-regexp "^4.0.0" - eslint-scope "^7.1.1" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.3.0" - espree "^9.3.3" - esquery "^1.4.0" - esutils "^2.0.2" - fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" - find-up "^5.0.0" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.15.0" - globby "^11.1.0" - grapheme-splitter "^1.0.4" - ignore "^5.2.0" - import-fresh "^3.0.0" - imurmurhash "^0.1.4" - is-glob "^4.0.0" - js-yaml "^4.1.0" - json-stable-stringify-without-jsonify "^1.0.1" - levn "^0.4.1" - lodash.merge "^4.6.2" - minimatch "^3.1.2" - natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" - strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" - text-table "^0.2.0" - v8-compile-cache "^2.0.3" - -espree@^9.0.0, espree@^9.3.2, espree@^9.3.3: - version "9.3.3" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.3.tgz#2dd37c4162bb05f433ad3c1a52ddf8a49dc08e9d" - integrity sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng== - dependencies: - acorn "^8.8.0" - acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.3.0" - -esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esquery@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" - integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== - dependencies: - estraverse "^5.1.0" - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.2.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.1.0, estraverse@^5.2.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" - integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -event-target-shim@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" - integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== - -express@^4.18.1: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== - dependencies: - accepts "~1.3.8" - array-flatten "1.1.1" - body-parser "1.20.0" - content-disposition "0.5.4" - content-type "~1.0.4" - cookie "0.5.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "2.0.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "2.4.1" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.7" - qs "6.10.3" - range-parser "~1.2.1" - safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" - setprototypeof "1.2.0" - statuses "2.0.1" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - -fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-glob@^3.2.9: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== - -fast-text-encoding@^1.0.0, fast-text-encoding@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz#ec02ac8e01ab8a319af182dae2681213cfe9ce53" - integrity sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig== - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== - dependencies: - flat-cache "^3.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "2.4.1" - parseurl "~1.3.3" - statuses "2.0.1" - unpipe "~1.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== - dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" - -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -fs-minipass@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" - integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg== - dependencies: - minipass "^3.0.0" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== - -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaxios@^5.0.0, gaxios@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-5.0.1.tgz#50fc76a2d04bc1700ed8c3ff1561e52255dfc6e0" - integrity sha512-keK47BGKHyyOVQxgcUaSaFvr3ehZYAlvhvpHXy0YB2itzZef+GqZR8TBsfVRWghdwlKrYsn+8L8i3eblF7Oviw== - dependencies: - extend "^3.0.2" - https-proxy-agent "^5.0.0" - is-stream "^2.0.0" - node-fetch "^2.6.7" - -gcp-metadata@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-5.0.0.tgz#a00f999f60a4461401e7c515f8a3267cfb401ee7" - integrity sha512-gfwuX3yA3nNsHSWUL4KG90UulNiq922Ukj3wLTrcnX33BB7PwB1o0ubR8KVvXu9nJH+P5w1j2SQSNNqto+H0DA== - dependencies: - gaxios "^5.0.0" - json-bigint "^1.0.0" - -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-port@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119" - integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.1: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -glob@^7.1.3: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^8.0.0: - version "8.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.0.3.tgz#415c6eb2deed9e502c68fa44a272e6da6eeca42e" - integrity sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -globals@^13.15.0, globals@^13.2.0: - version "13.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.17.0.tgz#902eb1e680a41da93945adbdcb5a9f361ba69bd4" - integrity sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw== - dependencies: - type-fest "^0.20.2" - -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - -google-auth-library@^8.0.2: - version "8.3.0" - resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-8.3.0.tgz#9286a613ee089f1d1728c07b9a302a22acf2dc2a" - integrity sha512-rXasaUScggvyD5ELpQC7SIOUOdqyOIifCK9TRDOLFEcuw1JxuyewKAwDs2QiKzMf8uLa0L360W0bVlV+sJDc8g== - dependencies: - arrify "^2.0.0" - base64-js "^1.3.0" - ecdsa-sig-formatter "^1.0.11" - fast-text-encoding "^1.0.0" - gaxios "^5.0.0" - gcp-metadata "^5.0.0" - gtoken "^6.0.0" - jws "^4.0.0" - lru-cache "^6.0.0" - -google-gax@^3.0.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/google-gax/-/google-gax-3.2.1.tgz#1562ac3f7e0403daa114ee47f5f821eaa9498a78" - integrity sha512-vWUFzAd/WaEgTOIMuQjjsGC9awlHy5Kl83kO+mVibSTloQjWlfa1k5FklFpnFMCcusMJvGgg0PlzDZS8sl0w8w== - dependencies: - "@grpc/grpc-js" "~1.6.0" - "@grpc/proto-loader" "^0.7.0" - "@types/long" "^4.0.0" - abort-controller "^3.0.0" - duplexify "^4.0.0" - fast-text-encoding "^1.0.3" - google-auth-library "^8.0.2" - is-stream-ended "^0.1.4" - node-fetch "^2.6.1" - object-hash "^3.0.0" - proto3-json-serializer "^1.0.0" - protobufjs "7.0.0" - protobufjs-cli "1.0.0" - retry-request "^5.0.0" - -google-p12-pem@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/google-p12-pem/-/google-p12-pem-4.0.0.tgz#f46581add1dc6ea0b96160cda6ce37ee35ab8ca3" - integrity sha512-lRTMn5ElBdDixv4a86bixejPSRk1boRtUowNepeKEVvYiFlkLuAJUVpEz6PfObDHYEKnZWq/9a2zC98xu62A9w== - dependencies: - node-forge "^1.3.1" - -graceful-fs@^4.1.9: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== - -grapheme-splitter@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" - integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== - -gtoken@^6.0.0: - version "6.1.1" - resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-6.1.1.tgz#29ebf3e6893719176d180f5694f1cad525ce3c04" - integrity sha512-HPM4VzzPEGxjQ7T2xLrdSYBs+h1c0yHAUiN+8RHPDoiZbndlpg9Sx3SjWcrTt9+N3FHsSABEpjvdQVan5AAuZQ== - dependencies: - gaxios "^5.0.1" - google-p12-pem "^4.0.0" - jws "^4.0.0" - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== - -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -htmlnano@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/htmlnano/-/htmlnano-2.0.2.tgz#3e3170941e2446a86211196d740272ebca78f878" - integrity sha512-+ZrQFS4Ub+zd+/fWwfvoYCEGNEa0/zrpys6CyXxvZDwtL7Pl+pOtRkiujyvBQ7Lmfp7/iEPxtOFgxWA16Gkj3w== - dependencies: - cosmiconfig "^7.0.1" - posthtml "^0.16.5" - timsort "^0.3.0" - -htmlparser2@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5" - integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.2" - domutils "^2.8.0" - entities "^3.0.1" - -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - -iconv-lite@0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ignore-by-default@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" - integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== - -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-json@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-json/-/is-json-2.0.1.tgz#6be166d144828a131d686891b983df62c39491ff" - integrity sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA== - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-stream-ended@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" - integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw== - -is-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" - integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== - -is@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/is/-/is-3.3.0.tgz#61cff6dd3c4193db94a3d62582072b44e5645d79" - integrity sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== - dependencies: - argparse "^2.0.1" - -js2xmlparser@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-4.0.2.tgz#2a1fdf01e90585ef2ae872a01bc169c6a8d5e60a" - integrity sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA== - dependencies: - xmlcreate "^2.0.4" - -jsdoc@^3.6.3: - version "3.6.11" - resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.6.11.tgz#8bbb5747e6f579f141a5238cbad4e95e004458ce" - integrity sha512-8UCU0TYeIYD9KeLzEcAu2q8N/mx9O3phAGl32nmHlE0LpaJL71mMkP4d+QE5zWfNt50qheHtOZ0qoxVrsX5TUg== - dependencies: - "@babel/parser" "^7.9.4" - "@types/markdown-it" "^12.2.3" - bluebird "^3.7.2" - catharsis "^0.9.0" - escape-string-regexp "^2.0.0" - js2xmlparser "^4.0.2" - klaw "^3.0.0" - markdown-it "^12.3.2" - markdown-it-anchor "^8.4.1" - marked "^4.0.10" - mkdirp "^1.0.4" - requizzle "^0.2.3" - strip-json-comments "^3.1.0" - taffydb "2.6.2" - underscore "~1.13.2" - -json-bigint@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" - integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== - dependencies: - bignumber.js "^9.0.0" - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json-stable-stringify-without-jsonify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" - integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== - -json5@^2.2.0, json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== - -jwa@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.0.tgz#a7e9c3f29dae94027ebcaf49975c9345593410fc" - integrity sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA== - dependencies: - buffer-equal-constant-time "1.0.1" - ecdsa-sig-formatter "1.0.11" - safe-buffer "^5.0.1" - -jws@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.0.tgz#2d4e8cf6a318ffaa12615e9dec7e86e6c97310f4" - integrity sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg== - dependencies: - jwa "^2.0.0" - safe-buffer "^5.0.1" - -klaw@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/klaw/-/klaw-3.0.0.tgz#b11bec9cf2492f06756d6e809ab73a2910259146" - integrity sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g== - dependencies: - graceful-fs "^4.1.9" - -levn@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" - integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ== - dependencies: - prelude-ls "^1.2.1" - type-check "~0.4.0" - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA== - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lines-and-columns@^1.1.6: - version "1.2.4" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" - integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== - -linkify-it@^3.0.1: - version "3.0.3" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-3.0.3.tgz#a98baf44ce45a550efb4d49c769d07524cc2fa2e" - integrity sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ== - dependencies: - uc.micro "^1.0.1" - -lmdb@2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/lmdb/-/lmdb-2.5.2.tgz#37e28a9fb43405f4dc48c44cec0e13a14c4a6ff1" - integrity sha512-V5V5Xa2Hp9i2XsbDALkBTeHXnBXh/lEmk9p22zdr7jtuOIY9TGhjK6vAvTpOOx9IKU4hJkRWZxn/HsvR1ELLtA== - dependencies: - msgpackr "^1.5.4" - node-addon-api "^4.3.0" - node-gyp-build-optional-packages "5.0.3" - ordered-binary "^1.2.4" - weak-lru-cache "^1.2.2" - optionalDependencies: - "@lmdb/lmdb-darwin-arm64" "2.5.2" - "@lmdb/lmdb-darwin-x64" "2.5.2" - "@lmdb/lmdb-linux-arm" "2.5.2" - "@lmdb/lmdb-linux-arm64" "2.5.2" - "@lmdb/lmdb-linux-x64" "2.5.2" - "@lmdb/lmdb-win32-x64" "2.5.2" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.camelcase@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" - integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= - -lodash.merge@^4.6.2: - version "4.6.2" - resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" - integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== - -lodash@^4.17.14, lodash@^4.17.15: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -long@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== - -long@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-5.2.0.tgz#2696dadf4b4da2ce3f6f6b89186085d94d52fd61" - integrity sha512-9RTUNjK60eJbx3uz+TEGF7fUr29ZDxR5QzXcyDpeSfeH28S9ycINflOgOlppit5U+4kNTe83KQnMEerw7GmE8w== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -markdown-it-anchor@^8.4.1: - version "8.6.4" - resolved "https://registry.yarnpkg.com/markdown-it-anchor/-/markdown-it-anchor-8.6.4.tgz#affb8aa0910a504c114e9fcad53ac3a5b907b0e6" - integrity sha512-Ul4YVYZNxMJYALpKtu+ZRdrryYt/GlQ5CK+4l1bp/gWXOG2QWElt6AqF3Mih/wfUKdZbNAZVXGR73/n6U/8img== - -markdown-it@^12.3.2: - version "12.3.2" - resolved "https://registry.yarnpkg.com/markdown-it/-/markdown-it-12.3.2.tgz#bf92ac92283fe983fe4de8ff8abfb5ad72cd0c90" - integrity sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg== - dependencies: - argparse "^2.0.1" - entities "~2.1.0" - linkify-it "^3.0.1" - mdurl "^1.0.1" - uc.micro "^1.0.5" - -marked@^4.0.10: - version "4.0.18" - resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.18.tgz#cd0ac54b2e5610cfb90e8fd46ccaa8292c9ed569" - integrity sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw== - -mdn-data@2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" - integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== - -mdurl@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" - integrity sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g== - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge2@^1.3.0, merge2@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -mime-db@1.46.0: - version "1.46.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee" - integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ== - -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@~2.1.24: - version "2.1.29" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2" - integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ== - dependencies: - mime-db "1.46.0" - -mime-types@~2.1.34: - version "2.1.35" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^5.0.1: - version "5.1.0" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.0.tgz#1717b464f4971b144f6aabe8f2d0b8e4511e09c7" - integrity sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg== - dependencies: - brace-expansion "^2.0.1" - -minimist@^1.2.0: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -minipass@^3.0.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== - dependencies: - yallist "^4.0.0" - -minizlib@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" - integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg== - dependencies: - minipass "^3.0.0" - yallist "^4.0.0" - -mkdirp@^1.0.3, mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" - integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== - -msgpackr-extract@^2.0.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-2.1.2.tgz#56272030f3e163e1b51964ef8b1cd5e7240c03ed" - integrity sha512-cmrmERQFb19NX2JABOGtrKdHMyI6RUyceaPBQ2iRz9GnDkjBWFjNJC0jyyoOfZl2U/LZE3tQCCQc4dlRyA8mcA== - dependencies: - node-gyp-build-optional-packages "5.0.3" - optionalDependencies: - "@msgpackr-extract/msgpackr-extract-darwin-arm64" "2.1.2" - "@msgpackr-extract/msgpackr-extract-darwin-x64" "2.1.2" - "@msgpackr-extract/msgpackr-extract-linux-arm" "2.1.2" - "@msgpackr-extract/msgpackr-extract-linux-arm64" "2.1.2" - "@msgpackr-extract/msgpackr-extract-linux-x64" "2.1.2" - "@msgpackr-extract/msgpackr-extract-win32-x64" "2.1.2" - -msgpackr@^1.5.4: - version "1.6.2" - resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.6.2.tgz#176cd9f6b4437dad87a839b37f23c2dfee408d9a" - integrity sha512-bqSQ0DYJbXbrJcrZFmMygUZmqQiDfI2ewFVWcrZY12w5XHWtPuW4WppDT/e63Uu311ajwkRRXSoF0uILroBeTA== - optionalDependencies: - msgpackr-extract "^2.0.2" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== - -negotiator@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" - integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== - -node-addon-api@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.1.0.tgz#98b21931557466c6729e51cb77cd39c965f42239" - integrity sha512-flmrDNB06LIl5lywUz7YlNGZH/5p0M7W28k8hzd9Lshtdh1wshD2Y+U4h9LD6KObOy1f+fEVdgprPrEymjM5uw== - -node-addon-api@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-3.2.1.tgz#81325e0a2117789c0128dab65e7e38f07ceba161" - integrity sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A== - -node-addon-api@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" - integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== - -node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - -node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-forge@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" - integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== - -node-gyp-build-optional-packages@5.0.3: - version "5.0.3" - resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17" - integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA== - -node-gyp-build@^4.3.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.5.0.tgz#7a64eefa0b21112f89f58379da128ac177f20e40" - integrity sha512-2iGbaQBV+ITgCz76ZEjmhUKAKVf7xfY1sRl4UiKQspfZMH2h06SyhNsnSVy50cwkFQDGLyif6m/6uFXHkOZ6rg== - -node-releases@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503" - integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg== - -nodemon@^2.0.19: - version "2.0.19" - resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.19.tgz#cac175f74b9cb8b57e770d47841995eebe4488bd" - integrity sha512-4pv1f2bMDj0Eeg/MhGqxrtveeQ5/G/UVe9iO6uTZzjnRluSA4PVWf8CW99LUPwGB3eNIA7zUFoP77YuI7hOc0A== - dependencies: - chokidar "^3.5.2" - debug "^3.2.7" - ignore-by-default "^1.0.1" - minimatch "^3.0.4" - pstree.remy "^1.1.8" - semver "^5.7.1" - simple-update-notifier "^1.0.7" - supports-color "^5.5.0" - touch "^3.1.0" - undefsafe "^2.0.5" - -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - -nopt@~1.0.10: - version "1.0.10" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee" - integrity sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg== - dependencies: - abbrev "1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npmlog@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - -nth-check@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" - integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== - dependencies: - boolbase "^1.0.0" - -nullthrows@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" - integrity sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw== - -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - -object-assign@^4, object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= - -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -on-finished@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" - integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== - dependencies: - deep-is "^0.1.3" - fast-levenshtein "^2.0.6" - levn "^0.4.1" - prelude-ls "^1.2.1" - type-check "^0.4.0" - word-wrap "^1.2.3" - -ordered-binary@^1.2.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ordered-binary/-/ordered-binary-1.3.0.tgz#a116d64c923278216e335602d279750b2ebd746e" - integrity sha512-knIeYepTI6BDAzGxqFEDGtI/iGqs57H32CInAIxEvAHG46vk1Di0CEpyc1A7iY39B1mfik3g3KLYwOTNnnMHLA== - -p-limit@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -parcel@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/parcel/-/parcel-2.7.0.tgz#41fdd3e5c7144d4cf7f1fa3ab8d0ea0d47d31f77" - integrity sha512-pRYwnivwtNP0tip8xYSo4zCB0XhLt7/gJzP1p8OovCqkmFjG9VG+GW9TcAKqMIo0ovEa9tT+/s6gY1Qy+BONGQ== - dependencies: - "@parcel/config-default" "2.7.0" - "@parcel/core" "2.7.0" - "@parcel/diagnostic" "2.7.0" - "@parcel/events" "2.7.0" - "@parcel/fs" "2.7.0" - "@parcel/logger" "2.7.0" - "@parcel/package-manager" "2.7.0" - "@parcel/reporter-cli" "2.7.0" - "@parcel/reporter-dev-server" "2.7.0" - "@parcel/utils" "2.7.0" - chalk "^4.1.0" - commander "^7.0.0" - get-port "^4.2.0" - v8-compile-cache "^2.0.0" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -posthtml-parser@^0.10.1: - version "0.10.2" - resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.10.2.tgz#df364d7b179f2a6bf0466b56be7b98fd4e97c573" - integrity sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg== - dependencies: - htmlparser2 "^7.1.1" - -posthtml-parser@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/posthtml-parser/-/posthtml-parser-0.11.0.tgz#25d1c7bf811ea83559bc4c21c189a29747a24b7a" - integrity sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw== - dependencies: - htmlparser2 "^7.1.1" - -posthtml-render@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/posthtml-render/-/posthtml-render-3.0.0.tgz#97be44931496f495b4f07b99e903cc70ad6a3205" - integrity sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA== - dependencies: - is-json "^2.0.1" - -posthtml@^0.16.4, posthtml@^0.16.5: - version "0.16.6" - resolved "https://registry.yarnpkg.com/posthtml/-/posthtml-0.16.6.tgz#e2fc407f67a64d2fa3567afe770409ffdadafe59" - integrity sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ== - dependencies: - posthtml-parser "^0.11.0" - posthtml-render "^3.0.0" - -prelude-ls@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" - integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -proto3-json-serializer@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/proto3-json-serializer/-/proto3-json-serializer-1.0.3.tgz#c23a037eb7c3c7d963a0976fa8b09e942ac83fbe" - integrity sha512-4Xo7uzbTfc8ur9R8VgI0pJpI6aHix76cc7DHJEfZKrZ6vOUbOddxBrsMzAGG2s6b3iHknl4Gn50dA2Y3AoCdow== - dependencies: - protobufjs "^7.0.0" - -protobufjs-cli@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/protobufjs-cli/-/protobufjs-cli-1.0.0.tgz#62f99de142118f34c7b4ee53c2dc122612d2711c" - integrity sha512-7NZEBrFuuU2ZdzlhmAmvh8fdU7A4OFhzYX9AB7a5vXjopAeiks6ZgUSjOlOO7ItCDJQm3y9RWjk7spUbHc4X0w== - dependencies: - chalk "^4.0.0" - escodegen "^1.13.0" - espree "^9.0.0" - estraverse "^5.1.0" - glob "^8.0.0" - jsdoc "^3.6.3" - minimist "^1.2.0" - semver "^7.1.2" - tmp "^0.2.1" - uglify-js "^3.7.7" - -protobufjs@7.0.0, protobufjs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.0.0.tgz#8c678e1351fd926178fce5a4213913e8d990974f" - integrity sha512-ffNIEm+quOcYtQvHdW406v1NQmZSuqVklxsXk076BtuFnlYZfigLU+JOMrTD8TUOyqHYbRI/fSVNvgd25YeN3w== - dependencies: - "@protobufjs/aspromise" "^1.1.2" - "@protobufjs/base64" "^1.1.2" - "@protobufjs/codegen" "^2.0.4" - "@protobufjs/eventemitter" "^1.1.0" - "@protobufjs/fetch" "^1.1.0" - "@protobufjs/float" "^1.0.2" - "@protobufjs/inquire" "^1.1.0" - "@protobufjs/path" "^1.1.2" - "@protobufjs/pool" "^1.1.0" - "@protobufjs/utf8" "^1.1.0" - "@types/long" "^4.0.1" - "@types/node" ">=13.7.0" - long "^5.0.0" - -proxy-addr@~2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -pstree.remy@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/pstree.remy/-/pstree.remy-1.1.8.tgz#c242224f4a67c21f686839bbdb4ac282b8373d3a" - integrity sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w== - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - -react-error-overlay@6.0.9: - version "6.0.9" - resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" - integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== - -react-refresh@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" - integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== - -readable-stream@^2.0.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readable-stream@^3.0.2, readable-stream@^3.1.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -regenerator-runtime@^0.13.7: - version "0.13.9" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" - integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== - -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - -requizzle@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.3.tgz#4675c90aacafb2c036bd39ba2daa4a1cb777fded" - integrity sha512-YanoyJjykPxGHii0fZP0uUPEXpvqfBDxWV7s6GKAiiOsiqhX6vHNyW3Qzdmqp/iq/ExbhaGbVrjB4ruEVSM4GQ== - dependencies: - lodash "^4.17.14" - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -retry-request@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/retry-request/-/retry-request-5.0.1.tgz#c6be2a4a36f1554ba3251fa8fd945af26ee0e9ec" - integrity sha512-lxFKrlBt0OZzCWh/V0uPEN0vlr3OhdeXnpeY5OES+ckslm791Cb1D5P7lJUSnY7J5hiCjcyaUGmzCnIGDCUBig== - dependencies: - debug "^4.1.1" - extend "^3.0.2" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -semver@^5.7.0, semver@^5.7.1: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@^7.1.2: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - -semver@~7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" - integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== - -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== - dependencies: - debug "2.6.9" - depd "2.0.0" - destroy "1.2.0" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" - mime "1.6.0" - ms "2.1.3" - on-finished "2.4.1" - range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" - -set-blocking@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -setprototypeof@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" - integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== - dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" - -signal-exit@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" - integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== - -simple-update-notifier@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/simple-update-notifier/-/simple-update-notifier-1.0.7.tgz#7edf75c5bdd04f88828d632f762b2bc32996a9cc" - integrity sha512-BBKgR84BJQJm6WjWFMHgLVuo61FBDSj1z/xSFUIozqO6wO7ii0JxCqlIud7Enr/+LhlbNI0whErq96P2qHNWew== - dependencies: - semver "~7.0.0" - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -source-map-support@~0.5.20: - version "0.5.21" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" - integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -split-array-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/split-array-stream/-/split-array-stream-2.0.0.tgz#85a4f8bfe14421d7bca7f33a6d176d0c076a53b1" - integrity sha512-hmMswlVY91WvGMxs0k8MRgq8zb2mSen4FmDNc5AFiTWtrBpdZN6nwD6kROVe4vNL+ywrvbCKsWVCnEd4riELIg== - dependencies: - is-stream-ended "^0.1.4" - -stable@^0.1.8: - version "0.1.8" - resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" - integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== - -statuses@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -stream-events@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/stream-events/-/stream-events-1.0.5.tgz#bbc898ec4df33a4902d892333d47da9bf1c406d5" - integrity sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg== - dependencies: - stubs "^3.0.0" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -"string-width@^1.0.2 || 2": - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^4.1.0, string-width@^4.2.0: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^3.0.0, strip-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -stubs@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/stubs/-/stubs-3.0.0.tgz#e8d2ba1fa9c90570303c030b6900f7d5f89abe5b" - integrity sha1-6NK6H6nJBXAwPAMLaQD31fiavls= - -supports-color@^5.3.0, supports-color@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -svgo@^2.4.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24" - integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg== - dependencies: - "@trysound/sax" "0.2.0" - commander "^7.2.0" - css-select "^4.1.3" - css-tree "^1.1.3" - csso "^4.2.0" - picocolors "^1.0.0" - stable "^0.1.8" - -taffydb@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268" - integrity sha512-y3JaeRSplks6NYQuCOj3ZFMO3j60rTwbuKCvZxsAraGYH2epusatvZ0baZYA01WsGqJBq/Dl6vOrMUJqyMj8kA== - -tar@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83" - integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -term-size@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" - integrity sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg== - -terser@^5.2.0: - version "5.14.2" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10" - integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA== - dependencies: - "@jridgewell/source-map" "^0.3.2" - acorn "^8.5.0" - commander "^2.20.0" - source-map-support "~0.5.20" - -text-table@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" - integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== - -timsort@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" - integrity sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A== - -tmp@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14" - integrity sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ== - dependencies: - rimraf "^3.0.0" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -toidentifier@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" - integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== - -touch@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" - integrity sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA== - dependencies: - nopt "~1.0.10" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== - -tslib@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" - integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== - -type-check@^0.4.0, type-check@~0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" - integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew== - dependencies: - prelude-ls "^1.2.1" - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg== - dependencies: - prelude-ls "~1.1.2" - -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - -type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= - -uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== - -uglify-js@^3.7.7: - version "3.17.0" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.0.tgz#55bd6e9d19ce5eef0d5ad17cd1f587d85b180a85" - integrity sha512-aTeNPVmgIMPpm1cxXr2Q/nEbvkmV8yq66F3om7X3P/cvOXQ0TMQ64Wk63iyT1gPlmdmGzjGpyLh1f3y8MZWXGg== - -undefsafe@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" - integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== - -underscore@~1.13.2: - version "1.13.4" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.4.tgz#7886b46bbdf07f768e0052f1828e1dcab40c0dee" - integrity sha512-BQFnUDuAQ4Yf/cYY5LNrK9NCJFKriaRbD9uR1fTeXnBeoa97W0i41qkZfGO9pSo8I5KzjAcSY2XYtdf0oKd7KQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -update-browserslist-db@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.5.tgz#be06a5eedd62f107b7c19eb5bcefb194411abf38" - integrity sha512-dteFFpCyvuDdr9S/ff1ISkKt/9YZxKjI9WlRR99c180GaztJtRa/fn18FdxGVKVsnPY7/a/FDN68mcvUmP4U7Q== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -utility-types@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" - integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -v8-compile-cache@^2.0.0, v8-compile-cache@^2.0.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" - integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -weak-lru-cache@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz#fdbb6741f36bae9540d12f480ce8254060dccd19" - integrity sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw== - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -which@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - -word-wrap@^1.2.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -xmlcreate@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-2.0.4.tgz#0c5ab0f99cdd02a81065fa9cd8f8ae87624889be" - integrity sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg== - -xxhash-wasm@^0.4.2: - version "0.4.2" - resolved "https://registry.yarnpkg.com/xxhash-wasm/-/xxhash-wasm-0.4.2.tgz#752398c131a4dd407b5132ba62ad372029be6f79" - integrity sha512-/eyHVRJQCirEkSZ1agRSCwriMhwlyUcFkXD5TPVSLP+IPzjsqMVzZwdoczLp1SoQU0R3dxz1RpIK+4YNQbCVOA== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^16.2.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/crabfit-frontend/.eslintrc.js b/frontend/.eslintrc.js similarity index 100% rename from crabfit-frontend/.eslintrc.js rename to frontend/.eslintrc.js diff --git a/crabfit-frontend/.gcloudignore b/frontend/.gcloudignore similarity index 100% rename from crabfit-frontend/.gcloudignore rename to frontend/.gcloudignore diff --git a/crabfit-frontend/.gitignore b/frontend/.gitignore similarity index 100% rename from crabfit-frontend/.gitignore rename to frontend/.gitignore diff --git a/crabfit-frontend/app.yaml b/frontend/app.yaml similarity index 100% rename from crabfit-frontend/app.yaml rename to frontend/app.yaml diff --git a/crabfit-frontend/index.html b/frontend/index.html similarity index 100% rename from crabfit-frontend/index.html rename to frontend/index.html diff --git a/crabfit-frontend/jsconfig.json b/frontend/jsconfig.json similarity index 100% rename from crabfit-frontend/jsconfig.json rename to frontend/jsconfig.json diff --git a/crabfit-frontend/package.json b/frontend/package.json similarity index 100% rename from crabfit-frontend/package.json rename to frontend/package.json diff --git a/crabfit-frontend/public/.well-known/assetlinks.json b/frontend/public/.well-known/assetlinks.json similarity index 100% rename from crabfit-frontend/public/.well-known/assetlinks.json rename to frontend/public/.well-known/assetlinks.json diff --git a/crabfit-frontend/public/.well-known/microsoft-identity-association.json b/frontend/public/.well-known/microsoft-identity-association.json similarity index 100% rename from crabfit-frontend/public/.well-known/microsoft-identity-association.json rename to frontend/public/.well-known/microsoft-identity-association.json diff --git a/crabfit-frontend/public/favicon.ico b/frontend/public/favicon.ico similarity index 100% rename from crabfit-frontend/public/favicon.ico rename to frontend/public/favicon.ico diff --git a/crabfit-frontend/public/fonts/karla-italic-variable.ttf b/frontend/public/fonts/karla-italic-variable.ttf similarity index 100% rename from crabfit-frontend/public/fonts/karla-italic-variable.ttf rename to frontend/public/fonts/karla-italic-variable.ttf diff --git a/crabfit-frontend/public/fonts/karla-variable.ttf b/frontend/public/fonts/karla-variable.ttf similarity index 100% rename from crabfit-frontend/public/fonts/karla-variable.ttf rename to frontend/public/fonts/karla-variable.ttf diff --git a/crabfit-frontend/public/fonts/molot.woff b/frontend/public/fonts/molot.woff similarity index 100% rename from crabfit-frontend/public/fonts/molot.woff rename to frontend/public/fonts/molot.woff diff --git a/crabfit-frontend/public/fonts/molot.woff2 b/frontend/public/fonts/molot.woff2 similarity index 100% rename from crabfit-frontend/public/fonts/molot.woff2 rename to frontend/public/fonts/molot.woff2 diff --git a/crabfit-frontend/public/fonts/samuraibob.woff b/frontend/public/fonts/samuraibob.woff similarity index 100% rename from crabfit-frontend/public/fonts/samuraibob.woff rename to frontend/public/fonts/samuraibob.woff diff --git a/crabfit-frontend/public/fonts/samuraibob.woff2 b/frontend/public/fonts/samuraibob.woff2 similarity index 100% rename from crabfit-frontend/public/fonts/samuraibob.woff2 rename to frontend/public/fonts/samuraibob.woff2 diff --git a/crabfit-frontend/public/i18n/de/common.json b/frontend/public/i18n/de/common.json similarity index 100% rename from crabfit-frontend/public/i18n/de/common.json rename to frontend/public/i18n/de/common.json diff --git a/crabfit-frontend/public/i18n/de/event.json b/frontend/public/i18n/de/event.json similarity index 100% rename from crabfit-frontend/public/i18n/de/event.json rename to frontend/public/i18n/de/event.json diff --git a/crabfit-frontend/public/i18n/de/help.json b/frontend/public/i18n/de/help.json similarity index 100% rename from crabfit-frontend/public/i18n/de/help.json rename to frontend/public/i18n/de/help.json diff --git a/crabfit-frontend/public/i18n/de/home.json b/frontend/public/i18n/de/home.json similarity index 100% rename from crabfit-frontend/public/i18n/de/home.json rename to frontend/public/i18n/de/home.json diff --git a/crabfit-frontend/public/i18n/de/privacy.json b/frontend/public/i18n/de/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/de/privacy.json rename to frontend/public/i18n/de/privacy.json diff --git a/crabfit-frontend/public/i18n/en-GB/common.json b/frontend/public/i18n/en-GB/common.json similarity index 100% rename from crabfit-frontend/public/i18n/en-GB/common.json rename to frontend/public/i18n/en-GB/common.json diff --git a/crabfit-frontend/public/i18n/en-GB/event.json b/frontend/public/i18n/en-GB/event.json similarity index 100% rename from crabfit-frontend/public/i18n/en-GB/event.json rename to frontend/public/i18n/en-GB/event.json diff --git a/crabfit-frontend/public/i18n/en-GB/help.json b/frontend/public/i18n/en-GB/help.json similarity index 100% rename from crabfit-frontend/public/i18n/en-GB/help.json rename to frontend/public/i18n/en-GB/help.json diff --git a/crabfit-frontend/public/i18n/en-GB/home.json b/frontend/public/i18n/en-GB/home.json similarity index 100% rename from crabfit-frontend/public/i18n/en-GB/home.json rename to frontend/public/i18n/en-GB/home.json diff --git a/crabfit-frontend/public/i18n/en-GB/privacy.json b/frontend/public/i18n/en-GB/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/en-GB/privacy.json rename to frontend/public/i18n/en-GB/privacy.json diff --git a/crabfit-frontend/public/i18n/en/common.json b/frontend/public/i18n/en/common.json similarity index 100% rename from crabfit-frontend/public/i18n/en/common.json rename to frontend/public/i18n/en/common.json diff --git a/crabfit-frontend/public/i18n/en/event.json b/frontend/public/i18n/en/event.json similarity index 100% rename from crabfit-frontend/public/i18n/en/event.json rename to frontend/public/i18n/en/event.json diff --git a/crabfit-frontend/public/i18n/en/help.json b/frontend/public/i18n/en/help.json similarity index 100% rename from crabfit-frontend/public/i18n/en/help.json rename to frontend/public/i18n/en/help.json diff --git a/crabfit-frontend/public/i18n/en/home.json b/frontend/public/i18n/en/home.json similarity index 100% rename from crabfit-frontend/public/i18n/en/home.json rename to frontend/public/i18n/en/home.json diff --git a/crabfit-frontend/public/i18n/en/privacy.json b/frontend/public/i18n/en/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/en/privacy.json rename to frontend/public/i18n/en/privacy.json diff --git a/crabfit-frontend/public/i18n/es/common.json b/frontend/public/i18n/es/common.json similarity index 100% rename from crabfit-frontend/public/i18n/es/common.json rename to frontend/public/i18n/es/common.json diff --git a/crabfit-frontend/public/i18n/es/event.json b/frontend/public/i18n/es/event.json similarity index 100% rename from crabfit-frontend/public/i18n/es/event.json rename to frontend/public/i18n/es/event.json diff --git a/crabfit-frontend/public/i18n/es/help.json b/frontend/public/i18n/es/help.json similarity index 100% rename from crabfit-frontend/public/i18n/es/help.json rename to frontend/public/i18n/es/help.json diff --git a/crabfit-frontend/public/i18n/es/home.json b/frontend/public/i18n/es/home.json similarity index 100% rename from crabfit-frontend/public/i18n/es/home.json rename to frontend/public/i18n/es/home.json diff --git a/crabfit-frontend/public/i18n/es/privacy.json b/frontend/public/i18n/es/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/es/privacy.json rename to frontend/public/i18n/es/privacy.json diff --git a/crabfit-frontend/public/i18n/fr/common.json b/frontend/public/i18n/fr/common.json similarity index 100% rename from crabfit-frontend/public/i18n/fr/common.json rename to frontend/public/i18n/fr/common.json diff --git a/crabfit-frontend/public/i18n/fr/event.json b/frontend/public/i18n/fr/event.json similarity index 100% rename from crabfit-frontend/public/i18n/fr/event.json rename to frontend/public/i18n/fr/event.json diff --git a/crabfit-frontend/public/i18n/fr/help.json b/frontend/public/i18n/fr/help.json similarity index 100% rename from crabfit-frontend/public/i18n/fr/help.json rename to frontend/public/i18n/fr/help.json diff --git a/crabfit-frontend/public/i18n/fr/home.json b/frontend/public/i18n/fr/home.json similarity index 100% rename from crabfit-frontend/public/i18n/fr/home.json rename to frontend/public/i18n/fr/home.json diff --git a/crabfit-frontend/public/i18n/fr/privacy.json b/frontend/public/i18n/fr/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/fr/privacy.json rename to frontend/public/i18n/fr/privacy.json diff --git a/crabfit-frontend/public/i18n/hi/common.json b/frontend/public/i18n/hi/common.json similarity index 100% rename from crabfit-frontend/public/i18n/hi/common.json rename to frontend/public/i18n/hi/common.json diff --git a/crabfit-frontend/public/i18n/hi/event.json b/frontend/public/i18n/hi/event.json similarity index 100% rename from crabfit-frontend/public/i18n/hi/event.json rename to frontend/public/i18n/hi/event.json diff --git a/crabfit-frontend/public/i18n/hi/help.json b/frontend/public/i18n/hi/help.json similarity index 100% rename from crabfit-frontend/public/i18n/hi/help.json rename to frontend/public/i18n/hi/help.json diff --git a/crabfit-frontend/public/i18n/hi/home.json b/frontend/public/i18n/hi/home.json similarity index 100% rename from crabfit-frontend/public/i18n/hi/home.json rename to frontend/public/i18n/hi/home.json diff --git a/crabfit-frontend/public/i18n/hi/privacy.json b/frontend/public/i18n/hi/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/hi/privacy.json rename to frontend/public/i18n/hi/privacy.json diff --git a/crabfit-frontend/public/i18n/id/common.json b/frontend/public/i18n/id/common.json similarity index 100% rename from crabfit-frontend/public/i18n/id/common.json rename to frontend/public/i18n/id/common.json diff --git a/crabfit-frontend/public/i18n/id/event.json b/frontend/public/i18n/id/event.json similarity index 100% rename from crabfit-frontend/public/i18n/id/event.json rename to frontend/public/i18n/id/event.json diff --git a/crabfit-frontend/public/i18n/id/help.json b/frontend/public/i18n/id/help.json similarity index 100% rename from crabfit-frontend/public/i18n/id/help.json rename to frontend/public/i18n/id/help.json diff --git a/crabfit-frontend/public/i18n/id/home.json b/frontend/public/i18n/id/home.json similarity index 100% rename from crabfit-frontend/public/i18n/id/home.json rename to frontend/public/i18n/id/home.json diff --git a/crabfit-frontend/public/i18n/id/privacy.json b/frontend/public/i18n/id/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/id/privacy.json rename to frontend/public/i18n/id/privacy.json diff --git a/crabfit-frontend/public/i18n/it/common.json b/frontend/public/i18n/it/common.json similarity index 100% rename from crabfit-frontend/public/i18n/it/common.json rename to frontend/public/i18n/it/common.json diff --git a/crabfit-frontend/public/i18n/it/event.json b/frontend/public/i18n/it/event.json similarity index 100% rename from crabfit-frontend/public/i18n/it/event.json rename to frontend/public/i18n/it/event.json diff --git a/crabfit-frontend/public/i18n/it/help.json b/frontend/public/i18n/it/help.json similarity index 100% rename from crabfit-frontend/public/i18n/it/help.json rename to frontend/public/i18n/it/help.json diff --git a/crabfit-frontend/public/i18n/it/home.json b/frontend/public/i18n/it/home.json similarity index 100% rename from crabfit-frontend/public/i18n/it/home.json rename to frontend/public/i18n/it/home.json diff --git a/crabfit-frontend/public/i18n/it/privacy.json b/frontend/public/i18n/it/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/it/privacy.json rename to frontend/public/i18n/it/privacy.json diff --git a/crabfit-frontend/public/i18n/ja/common.json b/frontend/public/i18n/ja/common.json similarity index 100% rename from crabfit-frontend/public/i18n/ja/common.json rename to frontend/public/i18n/ja/common.json diff --git a/crabfit-frontend/public/i18n/ja/event.json b/frontend/public/i18n/ja/event.json similarity index 100% rename from crabfit-frontend/public/i18n/ja/event.json rename to frontend/public/i18n/ja/event.json diff --git a/crabfit-frontend/public/i18n/ja/home.json b/frontend/public/i18n/ja/home.json similarity index 100% rename from crabfit-frontend/public/i18n/ja/home.json rename to frontend/public/i18n/ja/home.json diff --git a/crabfit-frontend/public/i18n/ja/privacy.json b/frontend/public/i18n/ja/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/ja/privacy.json rename to frontend/public/i18n/ja/privacy.json diff --git a/crabfit-frontend/public/i18n/ko/common.json b/frontend/public/i18n/ko/common.json similarity index 100% rename from crabfit-frontend/public/i18n/ko/common.json rename to frontend/public/i18n/ko/common.json diff --git a/crabfit-frontend/public/i18n/ko/event.json b/frontend/public/i18n/ko/event.json similarity index 100% rename from crabfit-frontend/public/i18n/ko/event.json rename to frontend/public/i18n/ko/event.json diff --git a/crabfit-frontend/public/i18n/ko/help.json b/frontend/public/i18n/ko/help.json similarity index 100% rename from crabfit-frontend/public/i18n/ko/help.json rename to frontend/public/i18n/ko/help.json diff --git a/crabfit-frontend/public/i18n/ko/home.json b/frontend/public/i18n/ko/home.json similarity index 100% rename from crabfit-frontend/public/i18n/ko/home.json rename to frontend/public/i18n/ko/home.json diff --git a/crabfit-frontend/public/i18n/ko/privacy.json b/frontend/public/i18n/ko/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/ko/privacy.json rename to frontend/public/i18n/ko/privacy.json diff --git a/crabfit-frontend/public/i18n/pl/common.json b/frontend/public/i18n/pl/common.json similarity index 100% rename from crabfit-frontend/public/i18n/pl/common.json rename to frontend/public/i18n/pl/common.json diff --git a/crabfit-frontend/public/i18n/pl/home.json b/frontend/public/i18n/pl/home.json similarity index 100% rename from crabfit-frontend/public/i18n/pl/home.json rename to frontend/public/i18n/pl/home.json diff --git a/crabfit-frontend/public/i18n/pl/privacy.json b/frontend/public/i18n/pl/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/pl/privacy.json rename to frontend/public/i18n/pl/privacy.json diff --git a/crabfit-frontend/public/i18n/pt-BR/common.json b/frontend/public/i18n/pt-BR/common.json similarity index 100% rename from crabfit-frontend/public/i18n/pt-BR/common.json rename to frontend/public/i18n/pt-BR/common.json diff --git a/crabfit-frontend/public/i18n/pt-BR/event.json b/frontend/public/i18n/pt-BR/event.json similarity index 100% rename from crabfit-frontend/public/i18n/pt-BR/event.json rename to frontend/public/i18n/pt-BR/event.json diff --git a/crabfit-frontend/public/i18n/pt-BR/help.json b/frontend/public/i18n/pt-BR/help.json similarity index 100% rename from crabfit-frontend/public/i18n/pt-BR/help.json rename to frontend/public/i18n/pt-BR/help.json diff --git a/crabfit-frontend/public/i18n/pt-BR/home.json b/frontend/public/i18n/pt-BR/home.json similarity index 100% rename from crabfit-frontend/public/i18n/pt-BR/home.json rename to frontend/public/i18n/pt-BR/home.json diff --git a/crabfit-frontend/public/i18n/pt-BR/privacy.json b/frontend/public/i18n/pt-BR/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/pt-BR/privacy.json rename to frontend/public/i18n/pt-BR/privacy.json diff --git a/crabfit-frontend/public/i18n/pt_PT/common.json b/frontend/public/i18n/pt_PT/common.json similarity index 100% rename from crabfit-frontend/public/i18n/pt_PT/common.json rename to frontend/public/i18n/pt_PT/common.json diff --git a/crabfit-frontend/public/i18n/pt_PT/event.json b/frontend/public/i18n/pt_PT/event.json similarity index 100% rename from crabfit-frontend/public/i18n/pt_PT/event.json rename to frontend/public/i18n/pt_PT/event.json diff --git a/crabfit-frontend/public/i18n/pt_PT/help.json b/frontend/public/i18n/pt_PT/help.json similarity index 100% rename from crabfit-frontend/public/i18n/pt_PT/help.json rename to frontend/public/i18n/pt_PT/help.json diff --git a/crabfit-frontend/public/i18n/pt_PT/home.json b/frontend/public/i18n/pt_PT/home.json similarity index 100% rename from crabfit-frontend/public/i18n/pt_PT/home.json rename to frontend/public/i18n/pt_PT/home.json diff --git a/crabfit-frontend/public/i18n/pt_PT/privacy.json b/frontend/public/i18n/pt_PT/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/pt_PT/privacy.json rename to frontend/public/i18n/pt_PT/privacy.json diff --git a/crabfit-frontend/public/i18n/ru/common.json b/frontend/public/i18n/ru/common.json similarity index 100% rename from crabfit-frontend/public/i18n/ru/common.json rename to frontend/public/i18n/ru/common.json diff --git a/crabfit-frontend/public/i18n/ru/event.json b/frontend/public/i18n/ru/event.json similarity index 100% rename from crabfit-frontend/public/i18n/ru/event.json rename to frontend/public/i18n/ru/event.json diff --git a/crabfit-frontend/public/i18n/ru/help.json b/frontend/public/i18n/ru/help.json similarity index 100% rename from crabfit-frontend/public/i18n/ru/help.json rename to frontend/public/i18n/ru/help.json diff --git a/crabfit-frontend/public/i18n/ru/home.json b/frontend/public/i18n/ru/home.json similarity index 100% rename from crabfit-frontend/public/i18n/ru/home.json rename to frontend/public/i18n/ru/home.json diff --git a/crabfit-frontend/public/i18n/ru/privacy.json b/frontend/public/i18n/ru/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/ru/privacy.json rename to frontend/public/i18n/ru/privacy.json diff --git a/crabfit-frontend/public/index.css b/frontend/public/index.css similarity index 100% rename from crabfit-frontend/public/index.css rename to frontend/public/index.css diff --git a/crabfit-frontend/public/logo-icon.png b/frontend/public/logo-icon.png similarity index 100% rename from crabfit-frontend/public/logo-icon.png rename to frontend/public/logo-icon.png diff --git a/crabfit-frontend/public/logo192.png b/frontend/public/logo192.png similarity index 100% rename from crabfit-frontend/public/logo192.png rename to frontend/public/logo192.png diff --git a/crabfit-frontend/public/logo512.png b/frontend/public/logo512.png similarity index 100% rename from crabfit-frontend/public/logo512.png rename to frontend/public/logo512.png diff --git a/crabfit-frontend/public/manifest.json b/frontend/public/manifest.json similarity index 100% rename from crabfit-frontend/public/manifest.json rename to frontend/public/manifest.json diff --git a/crabfit-frontend/public/robots.txt b/frontend/public/robots.txt similarity index 100% rename from crabfit-frontend/public/robots.txt rename to frontend/public/robots.txt diff --git a/crabfit-frontend/public/sitemap.xml b/frontend/public/sitemap.xml similarity index 100% rename from crabfit-frontend/public/sitemap.xml rename to frontend/public/sitemap.xml diff --git a/crabfit-frontend/public/sw.js b/frontend/public/sw.js similarity index 100% rename from crabfit-frontend/public/sw.js rename to frontend/public/sw.js diff --git a/crabfit-frontend/src/App.jsx b/frontend/src/App.jsx similarity index 100% rename from crabfit-frontend/src/App.jsx rename to frontend/src/App.jsx diff --git a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx b/frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx similarity index 100% rename from crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx rename to frontend/src/components/AvailabilityEditor/AvailabilityEditor.jsx diff --git a/crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js b/frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js similarity index 100% rename from crabfit-frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js rename to frontend/src/components/AvailabilityEditor/AvailabilityEditor.styles.js diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx similarity index 100% rename from crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx rename to frontend/src/components/AvailabilityViewer/AvailabilityViewer.jsx diff --git a/crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js b/frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js similarity index 100% rename from crabfit-frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js rename to frontend/src/components/AvailabilityViewer/AvailabilityViewer.styles.js diff --git a/crabfit-frontend/src/components/Button/Button.jsx b/frontend/src/components/Button/Button.jsx similarity index 100% rename from crabfit-frontend/src/components/Button/Button.jsx rename to frontend/src/components/Button/Button.jsx diff --git a/crabfit-frontend/src/components/Button/Button.styles.js b/frontend/src/components/Button/Button.styles.js similarity index 100% rename from crabfit-frontend/src/components/Button/Button.styles.js rename to frontend/src/components/Button/Button.styles.js diff --git a/crabfit-frontend/src/components/CalendarField/CalendarField.jsx b/frontend/src/components/CalendarField/CalendarField.jsx similarity index 100% rename from crabfit-frontend/src/components/CalendarField/CalendarField.jsx rename to frontend/src/components/CalendarField/CalendarField.jsx diff --git a/crabfit-frontend/src/components/CalendarField/CalendarField.styles.js b/frontend/src/components/CalendarField/CalendarField.styles.js similarity index 100% rename from crabfit-frontend/src/components/CalendarField/CalendarField.styles.js rename to frontend/src/components/CalendarField/CalendarField.styles.js diff --git a/crabfit-frontend/src/components/Center/Center.js b/frontend/src/components/Center/Center.js similarity index 100% rename from crabfit-frontend/src/components/Center/Center.js rename to frontend/src/components/Center/Center.js diff --git a/crabfit-frontend/src/components/Donate/Donate.jsx b/frontend/src/components/Donate/Donate.jsx similarity index 100% rename from crabfit-frontend/src/components/Donate/Donate.jsx rename to frontend/src/components/Donate/Donate.jsx diff --git a/crabfit-frontend/src/components/Donate/Donate.styles.js b/frontend/src/components/Donate/Donate.styles.js similarity index 100% rename from crabfit-frontend/src/components/Donate/Donate.styles.js rename to frontend/src/components/Donate/Donate.styles.js diff --git a/crabfit-frontend/src/components/Egg/Egg.jsx b/frontend/src/components/Egg/Egg.jsx similarity index 100% rename from crabfit-frontend/src/components/Egg/Egg.jsx rename to frontend/src/components/Egg/Egg.jsx diff --git a/crabfit-frontend/src/components/Egg/Egg.styles.js b/frontend/src/components/Egg/Egg.styles.js similarity index 100% rename from crabfit-frontend/src/components/Egg/Egg.styles.js rename to frontend/src/components/Egg/Egg.styles.js diff --git a/crabfit-frontend/src/components/Error/Error.jsx b/frontend/src/components/Error/Error.jsx similarity index 100% rename from crabfit-frontend/src/components/Error/Error.jsx rename to frontend/src/components/Error/Error.jsx diff --git a/crabfit-frontend/src/components/Error/Error.styles.js b/frontend/src/components/Error/Error.styles.js similarity index 100% rename from crabfit-frontend/src/components/Error/Error.styles.js rename to frontend/src/components/Error/Error.styles.js diff --git a/crabfit-frontend/src/components/Footer/Footer.jsx b/frontend/src/components/Footer/Footer.jsx similarity index 100% rename from crabfit-frontend/src/components/Footer/Footer.jsx rename to frontend/src/components/Footer/Footer.jsx diff --git a/crabfit-frontend/src/components/Footer/Footer.styles.js b/frontend/src/components/Footer/Footer.styles.js similarity index 100% rename from crabfit-frontend/src/components/Footer/Footer.styles.js rename to frontend/src/components/Footer/Footer.styles.js diff --git a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.jsx b/frontend/src/components/GoogleCalendar/GoogleCalendar.jsx similarity index 100% rename from crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.jsx rename to frontend/src/components/GoogleCalendar/GoogleCalendar.jsx diff --git a/crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js b/frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js similarity index 100% rename from crabfit-frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js rename to frontend/src/components/GoogleCalendar/GoogleCalendar.styles.js diff --git a/crabfit-frontend/src/components/Legend/Legend.jsx b/frontend/src/components/Legend/Legend.jsx similarity index 100% rename from crabfit-frontend/src/components/Legend/Legend.jsx rename to frontend/src/components/Legend/Legend.jsx diff --git a/crabfit-frontend/src/components/Legend/Legend.styles.js b/frontend/src/components/Legend/Legend.styles.js similarity index 100% rename from crabfit-frontend/src/components/Legend/Legend.styles.js rename to frontend/src/components/Legend/Legend.styles.js diff --git a/crabfit-frontend/src/components/Loading/Loading.jsx b/frontend/src/components/Loading/Loading.jsx similarity index 100% rename from crabfit-frontend/src/components/Loading/Loading.jsx rename to frontend/src/components/Loading/Loading.jsx diff --git a/crabfit-frontend/src/components/Loading/Loading.styles.js b/frontend/src/components/Loading/Loading.styles.js similarity index 100% rename from crabfit-frontend/src/components/Loading/Loading.styles.js rename to frontend/src/components/Loading/Loading.styles.js diff --git a/crabfit-frontend/src/components/Logo/Logo.jsx b/frontend/src/components/Logo/Logo.jsx similarity index 100% rename from crabfit-frontend/src/components/Logo/Logo.jsx rename to frontend/src/components/Logo/Logo.jsx diff --git a/crabfit-frontend/src/components/Logo/Logo.styles.js b/frontend/src/components/Logo/Logo.styles.js similarity index 100% rename from crabfit-frontend/src/components/Logo/Logo.styles.js rename to frontend/src/components/Logo/Logo.styles.js diff --git a/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.jsx b/frontend/src/components/OutlookCalendar/OutlookCalendar.jsx similarity index 100% rename from crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.jsx rename to frontend/src/components/OutlookCalendar/OutlookCalendar.jsx diff --git a/crabfit-frontend/src/components/Recents/Recents.jsx b/frontend/src/components/Recents/Recents.jsx similarity index 100% rename from crabfit-frontend/src/components/Recents/Recents.jsx rename to frontend/src/components/Recents/Recents.jsx diff --git a/crabfit-frontend/src/components/Recents/Recents.styles.js b/frontend/src/components/Recents/Recents.styles.js similarity index 100% rename from crabfit-frontend/src/components/Recents/Recents.styles.js rename to frontend/src/components/Recents/Recents.styles.js diff --git a/crabfit-frontend/src/components/SelectField/SelectField.jsx b/frontend/src/components/SelectField/SelectField.jsx similarity index 100% rename from crabfit-frontend/src/components/SelectField/SelectField.jsx rename to frontend/src/components/SelectField/SelectField.jsx diff --git a/crabfit-frontend/src/components/SelectField/SelectField.styles.js b/frontend/src/components/SelectField/SelectField.styles.js similarity index 100% rename from crabfit-frontend/src/components/SelectField/SelectField.styles.js rename to frontend/src/components/SelectField/SelectField.styles.js diff --git a/crabfit-frontend/src/components/Settings/Settings.jsx b/frontend/src/components/Settings/Settings.jsx similarity index 100% rename from crabfit-frontend/src/components/Settings/Settings.jsx rename to frontend/src/components/Settings/Settings.jsx diff --git a/crabfit-frontend/src/components/Settings/Settings.styles.js b/frontend/src/components/Settings/Settings.styles.js similarity index 100% rename from crabfit-frontend/src/components/Settings/Settings.styles.js rename to frontend/src/components/Settings/Settings.styles.js diff --git a/crabfit-frontend/src/components/TextField/TextField.jsx b/frontend/src/components/TextField/TextField.jsx similarity index 100% rename from crabfit-frontend/src/components/TextField/TextField.jsx rename to frontend/src/components/TextField/TextField.jsx diff --git a/crabfit-frontend/src/components/TextField/TextField.styles.js b/frontend/src/components/TextField/TextField.styles.js similarity index 100% rename from crabfit-frontend/src/components/TextField/TextField.styles.js rename to frontend/src/components/TextField/TextField.styles.js diff --git a/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.jsx b/frontend/src/components/TimeRangeField/TimeRangeField.jsx similarity index 100% rename from crabfit-frontend/src/components/TimeRangeField/TimeRangeField.jsx rename to frontend/src/components/TimeRangeField/TimeRangeField.jsx diff --git a/crabfit-frontend/src/components/TimeRangeField/TimeRangeField.styles.js b/frontend/src/components/TimeRangeField/TimeRangeField.styles.js similarity index 100% rename from crabfit-frontend/src/components/TimeRangeField/TimeRangeField.styles.js rename to frontend/src/components/TimeRangeField/TimeRangeField.styles.js diff --git a/crabfit-frontend/src/components/ToggleField/ToggleField.jsx b/frontend/src/components/ToggleField/ToggleField.jsx similarity index 100% rename from crabfit-frontend/src/components/ToggleField/ToggleField.jsx rename to frontend/src/components/ToggleField/ToggleField.jsx diff --git a/crabfit-frontend/src/components/ToggleField/ToggleField.styles.js b/frontend/src/components/ToggleField/ToggleField.styles.js similarity index 100% rename from crabfit-frontend/src/components/ToggleField/ToggleField.styles.js rename to frontend/src/components/ToggleField/ToggleField.styles.js diff --git a/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.jsx b/frontend/src/components/TranslateDialog/TranslateDialog.jsx similarity index 100% rename from crabfit-frontend/src/components/TranslateDialog/TranslateDialog.jsx rename to frontend/src/components/TranslateDialog/TranslateDialog.jsx diff --git a/crabfit-frontend/src/components/TranslateDialog/TranslateDialog.styles.js b/frontend/src/components/TranslateDialog/TranslateDialog.styles.js similarity index 100% rename from crabfit-frontend/src/components/TranslateDialog/TranslateDialog.styles.js rename to frontend/src/components/TranslateDialog/TranslateDialog.styles.js diff --git a/crabfit-frontend/src/components/index.js b/frontend/src/components/index.js similarity index 100% rename from crabfit-frontend/src/components/index.js rename to frontend/src/components/index.js diff --git a/crabfit-frontend/src/i18n/index.js b/frontend/src/i18n/index.js similarity index 100% rename from crabfit-frontend/src/i18n/index.js rename to frontend/src/i18n/index.js diff --git a/crabfit-frontend/src/i18n/locales.js b/frontend/src/i18n/locales.js similarity index 100% rename from crabfit-frontend/src/i18n/locales.js rename to frontend/src/i18n/locales.js diff --git a/crabfit-frontend/src/index.jsx b/frontend/src/index.jsx similarity index 100% rename from crabfit-frontend/src/index.jsx rename to frontend/src/index.jsx diff --git a/crabfit-frontend/src/pages/Create/Create.jsx b/frontend/src/pages/Create/Create.jsx similarity index 100% rename from crabfit-frontend/src/pages/Create/Create.jsx rename to frontend/src/pages/Create/Create.jsx diff --git a/crabfit-frontend/src/pages/Create/Create.styles.js b/frontend/src/pages/Create/Create.styles.js similarity index 100% rename from crabfit-frontend/src/pages/Create/Create.styles.js rename to frontend/src/pages/Create/Create.styles.js diff --git a/crabfit-frontend/src/pages/Event/Event.jsx b/frontend/src/pages/Event/Event.jsx similarity index 100% rename from crabfit-frontend/src/pages/Event/Event.jsx rename to frontend/src/pages/Event/Event.jsx diff --git a/crabfit-frontend/src/pages/Event/Event.styles.js b/frontend/src/pages/Event/Event.styles.js similarity index 100% rename from crabfit-frontend/src/pages/Event/Event.styles.js rename to frontend/src/pages/Event/Event.styles.js diff --git a/crabfit-frontend/src/pages/Help/Help.jsx b/frontend/src/pages/Help/Help.jsx similarity index 100% rename from crabfit-frontend/src/pages/Help/Help.jsx rename to frontend/src/pages/Help/Help.jsx diff --git a/crabfit-frontend/src/pages/Help/Help.styles.js b/frontend/src/pages/Help/Help.styles.js similarity index 100% rename from crabfit-frontend/src/pages/Help/Help.styles.js rename to frontend/src/pages/Help/Help.styles.js diff --git a/crabfit-frontend/src/pages/Home/Home.jsx b/frontend/src/pages/Home/Home.jsx similarity index 100% rename from crabfit-frontend/src/pages/Home/Home.jsx rename to frontend/src/pages/Home/Home.jsx diff --git a/crabfit-frontend/src/pages/Home/Home.styles.js b/frontend/src/pages/Home/Home.styles.js similarity index 100% rename from crabfit-frontend/src/pages/Home/Home.styles.js rename to frontend/src/pages/Home/Home.styles.js diff --git a/crabfit-frontend/src/pages/Privacy/Privacy.jsx b/frontend/src/pages/Privacy/Privacy.jsx similarity index 100% rename from crabfit-frontend/src/pages/Privacy/Privacy.jsx rename to frontend/src/pages/Privacy/Privacy.jsx diff --git a/crabfit-frontend/src/pages/Privacy/Privacy.styles.js b/frontend/src/pages/Privacy/Privacy.styles.js similarity index 100% rename from crabfit-frontend/src/pages/Privacy/Privacy.styles.js rename to frontend/src/pages/Privacy/Privacy.styles.js diff --git a/crabfit-frontend/src/pages/index.js b/frontend/src/pages/index.js similarity index 100% rename from crabfit-frontend/src/pages/index.js rename to frontend/src/pages/index.js diff --git a/crabfit-frontend/src/res/google.svg b/frontend/src/res/google.svg similarity index 100% rename from crabfit-frontend/src/res/google.svg rename to frontend/src/res/google.svg diff --git a/crabfit-frontend/src/res/logo.svg b/frontend/src/res/logo.svg similarity index 100% rename from crabfit-frontend/src/res/logo.svg rename to frontend/src/res/logo.svg diff --git a/crabfit-frontend/src/res/outlook.svg b/frontend/src/res/outlook.svg similarity index 100% rename from crabfit-frontend/src/res/outlook.svg rename to frontend/src/res/outlook.svg diff --git a/crabfit-frontend/src/res/paypal.svg b/frontend/src/res/paypal.svg similarity index 100% rename from crabfit-frontend/src/res/paypal.svg rename to frontend/src/res/paypal.svg diff --git a/crabfit-frontend/src/res/timezones.json b/frontend/src/res/timezones.json similarity index 100% rename from crabfit-frontend/src/res/timezones.json rename to frontend/src/res/timezones.json diff --git a/crabfit-frontend/src/res/video_thumb.jpg b/frontend/src/res/video_thumb.jpg similarity index 100% rename from crabfit-frontend/src/res/video_thumb.jpg rename to frontend/src/res/video_thumb.jpg diff --git a/crabfit-frontend/src/services/index.js b/frontend/src/services/index.js similarity index 100% rename from crabfit-frontend/src/services/index.js rename to frontend/src/services/index.js diff --git a/crabfit-frontend/src/stores/index.js b/frontend/src/stores/index.js similarity index 100% rename from crabfit-frontend/src/stores/index.js rename to frontend/src/stores/index.js diff --git a/crabfit-frontend/src/stores/localeUpdateStore.js b/frontend/src/stores/localeUpdateStore.js similarity index 100% rename from crabfit-frontend/src/stores/localeUpdateStore.js rename to frontend/src/stores/localeUpdateStore.js diff --git a/crabfit-frontend/src/stores/recentsStore.js b/frontend/src/stores/recentsStore.js similarity index 100% rename from crabfit-frontend/src/stores/recentsStore.js rename to frontend/src/stores/recentsStore.js diff --git a/crabfit-frontend/src/stores/settingsStore.js b/frontend/src/stores/settingsStore.js similarity index 100% rename from crabfit-frontend/src/stores/settingsStore.js rename to frontend/src/stores/settingsStore.js diff --git a/crabfit-frontend/src/stores/translateStore.js b/frontend/src/stores/translateStore.js similarity index 100% rename from crabfit-frontend/src/stores/translateStore.js rename to frontend/src/stores/translateStore.js diff --git a/crabfit-frontend/src/stores/twaStore.js b/frontend/src/stores/twaStore.js similarity index 100% rename from crabfit-frontend/src/stores/twaStore.js rename to frontend/src/stores/twaStore.js diff --git a/crabfit-frontend/src/utils/index.js b/frontend/src/utils/index.js similarity index 100% rename from crabfit-frontend/src/utils/index.js rename to frontend/src/utils/index.js diff --git a/crabfit-frontend/vite.config.js b/frontend/vite.config.js similarity index 100% rename from crabfit-frontend/vite.config.js rename to frontend/vite.config.js diff --git a/crabfit-frontend/yarn.lock b/frontend/yarn.lock similarity index 100% rename from crabfit-frontend/yarn.lock rename to frontend/yarn.lock From fc8e2a4360952146f722c16a83ddf97d1921dbfc Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 13:46:23 +1000 Subject: [PATCH 02/29] Set up some routes using new Rust API --- backend/Cargo.lock | 81 ++++++- backend/Cargo.toml | 11 +- backend/adaptors/sql/Cargo.toml | 2 +- backend/adaptors/sql/src/entity/event.rs | 15 +- backend/adaptors/sql/src/entity/person.rs | 13 -- backend/adaptors/sql/src/entity/stats.rs | 10 - backend/adaptors/sql/src/lib.rs | 111 +++++++--- .../{setup_tables.rs => m01_setup_tables.rs} | 2 + backend/adaptors/sql/src/migration/mod.rs | 4 +- backend/common/Cargo.toml | 9 + backend/common/src/adaptor.rs | 29 +++ backend/{data => common}/src/event.rs | 1 + backend/{data => common}/src/lib.rs | 0 backend/{data => common}/src/person.rs | 0 backend/{data => common}/src/stats.rs | 0 backend/data/Cargo.toml | 9 - backend/data/src/adaptor.rs | 37 ---- backend/src/errors.rs | 22 ++ backend/src/main.rs | 36 +++- backend/src/payloads.rs | 43 ++++ backend/src/res/adjectives.json | 201 ++++++++++++++++++ backend/src/res/crabs.json | 47 ++++ backend/src/routes/create_event.rs | 92 ++++++++ backend/src/routes/get_event.rs | 34 +++ backend/src/routes/get_stats.rs | 20 ++ backend/src/routes/mod.rs | 8 + 26 files changed, 703 insertions(+), 134 deletions(-) rename backend/adaptors/sql/src/migration/{setup_tables.rs => m01_setup_tables.rs} (97%) create mode 100644 backend/common/Cargo.toml create mode 100644 backend/common/src/adaptor.rs rename backend/{data => common}/src/event.rs (91%) rename backend/{data => common}/src/lib.rs (100%) rename backend/{data => common}/src/person.rs (100%) rename backend/{data => common}/src/stats.rs (100%) delete mode 100644 backend/data/Cargo.toml delete mode 100644 backend/data/src/adaptor.rs create mode 100644 backend/src/errors.rs create mode 100644 backend/src/payloads.rs create mode 100644 backend/src/res/adjectives.json create mode 100644 backend/src/res/crabs.json create mode 100644 backend/src/routes/create_event.rs create mode 100644 backend/src/routes/get_event.rs create mode 100644 backend/src/routes/get_stats.rs create mode 100644 backend/src/routes/mod.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 6bb802a..f4ada34 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -30,6 +30,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" +dependencies = [ + "memchr", +] + [[package]] name = "aliasable" version = "0.1.3" @@ -492,6 +501,14 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -537,11 +554,18 @@ name = "crabfit_backend" version = "1.1.0" dependencies = [ "axum", - "data", + "chrono", + "common", "dotenv", + "punycode", + "rand", + "regex", "serde", + "serde_json", "sql-adaptor", "tokio", + "tracing", + "tracing-subscriber", ] [[package]] @@ -637,14 +661,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "data" -version = "0.1.0" -dependencies = [ - "async-trait", - "chrono", -] - [[package]] name = "der" version = "0.5.1" @@ -1305,6 +1321,16 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1453,6 +1479,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking" version = "2.1.0" @@ -1649,6 +1681,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "punycode" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" + [[package]] name = "quote" version = "1.0.27" @@ -1723,6 +1761,8 @@ version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax 0.7.1", ] @@ -2204,7 +2244,7 @@ dependencies = [ "async-std", "async-trait", "chrono", - "data", + "common", "sea-orm", "sea-orm-migration", "serde", @@ -2596,6 +2636,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", ] [[package]] @@ -2605,12 +2657,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers", + "nu-ansi-term", "once_cell", "regex", "sharded-slab", + "smallvec", "thread_local", "tracing", "tracing-core", + "tracing-log", ] [[package]] @@ -2684,6 +2739,12 @@ dependencies = [ "serde", ] +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "value-bag" version = "1.0.0-alpha.9" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index fefc872..99a15f9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -4,12 +4,19 @@ version = "1.1.0" edition = "2021" [workspace] -members = ["data", "adaptors/*"] +members = ["common", "adaptors/*"] [dependencies] axum = "0.6.18" serde = { version = "1.0.162", features = ["derive"] } tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] } -data = { path = "data" } +common = { path = "common" } sql-adaptor = { path = "adaptors/sql" } dotenv = "0.15.0" +serde_json = "1.0.96" +rand = "0.8.5" +punycode = "0.4.1" +regex = "1.8.1" +tracing = "0.1.37" +tracing-subscriber = "0.3.17" +chrono = "0.4.24" diff --git a/backend/adaptors/sql/Cargo.toml b/backend/adaptors/sql/Cargo.toml index 3fb6ca3..b56724a 100644 --- a/backend/adaptors/sql/Cargo.toml +++ b/backend/adaptors/sql/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] async-trait = "0.1.68" -data = { path = "../../data" } +common = { path = "../../common" } sea-orm = { version = "0.11.3", features = [ "macros", "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", "runtime-tokio-native-tls" ] } serde = { version = "1.0.162", features = [ "derive" ] } async-std = { version = "1", features = ["attributes", "tokio1"] } diff --git a/backend/adaptors/sql/src/entity/event.rs b/backend/adaptors/sql/src/entity/event.rs index 5d3ccd7..72b03e9 100644 --- a/backend/adaptors/sql/src/entity/event.rs +++ b/backend/adaptors/sql/src/entity/event.rs @@ -1,7 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 -use chrono::{DateTime as ChronoDateTime, Utc}; -use data::event::Event; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -11,6 +9,7 @@ pub struct Model { pub id: String, pub name: String, pub created_at: DateTime, + pub visited_at: DateTime, pub times: Json, pub timezone: String, } @@ -28,15 +27,3 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} - -impl From for Event { - fn from(value: Model) -> Self { - Self { - id: value.id, - name: value.name, - created_at: ChronoDateTime::::from_utc(value.created_at, Utc), - times: serde_json::from_value(value.times).unwrap_or(vec![]), - timezone: value.timezone, - } - } -} diff --git a/backend/adaptors/sql/src/entity/person.rs b/backend/adaptors/sql/src/entity/person.rs index 82a2664..01f9a37 100644 --- a/backend/adaptors/sql/src/entity/person.rs +++ b/backend/adaptors/sql/src/entity/person.rs @@ -1,7 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 -use chrono::{DateTime as ChronoDateTime, Utc}; -use data::person::Person; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -35,14 +33,3 @@ impl Related for Entity { } impl ActiveModelBehavior for ActiveModel {} - -impl From for Person { - fn from(value: Model) -> Self { - Self { - name: value.name, - password_hash: value.password_hash, - created_at: ChronoDateTime::::from_utc(value.created_at, Utc), - availability: serde_json::from_value(value.availability).unwrap_or(vec![]), - } - } -} diff --git a/backend/adaptors/sql/src/entity/stats.rs b/backend/adaptors/sql/src/entity/stats.rs index 0d8a8c3..6485ff8 100644 --- a/backend/adaptors/sql/src/entity/stats.rs +++ b/backend/adaptors/sql/src/entity/stats.rs @@ -1,6 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3 -use data::stats::Stats; use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] @@ -16,12 +15,3 @@ pub struct Model { pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} - -impl From for Stats { - fn from(value: Model) -> Self { - Self { - event_count: value.event_count, - person_count: value.person_count, - } - } -} diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index f396c1b..ac34104 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -1,7 +1,8 @@ use std::{env, error::Error}; use async_trait::async_trait; -use data::{ +use chrono::{DateTime as ChronoDateTime, Utc}; +use common::{ adaptor::Adaptor, event::{Event, EventDeletion}, person::Person, @@ -10,54 +11,48 @@ use data::{ use entity::{event, person, stats}; use migration::{Migrator, MigratorTrait}; use sea_orm::{ + strum::Display, ActiveModelTrait, ActiveValue::{NotSet, Set}, ColumnTrait, Database, DatabaseConnection, DbErr, EntityTrait, ModelTrait, QueryFilter, - TransactionTrait, TryIntoModel, + TransactionError, TransactionTrait, TryIntoModel, }; use serde_json::json; mod entity; mod migration; -pub struct PostgresAdaptor { +pub struct SqlAdaptor { db: DatabaseConnection, } #[async_trait] -impl Adaptor for PostgresAdaptor { - async fn new() -> Self { - let connection_string = env::var("DATABASE_URL").unwrap(); +impl Adaptor for SqlAdaptor { + type Error = SqlAdaptorError; - // Connect to the database - let db = Database::connect(&connection_string).await.unwrap(); - println!("Connected to database at {}", connection_string); - - // Setup tables - Migrator::up(&db, None).await.unwrap(); - - Self { db } + async fn get_stats(&self) -> Result { + let stats_row = get_stats_row(&self.db).await?; + Ok(Stats { + event_count: stats_row.event_count.unwrap(), + person_count: stats_row.person_count.unwrap(), + }) } - async fn get_stats(&self) -> Result> { - Ok(get_stats_row(&self.db).await?.try_into_model()?.into()) - } - - async fn increment_stat_event_count(&self) -> Result> { + async fn increment_stat_event_count(&self) -> Result { let mut current_stats = get_stats_row(&self.db).await?; current_stats.event_count = Set(current_stats.event_count.unwrap() + 1); Ok(current_stats.save(&self.db).await?.event_count.unwrap()) } - async fn increment_stat_person_count(&self) -> Result> { + async fn increment_stat_person_count(&self) -> Result { let mut current_stats = get_stats_row(&self.db).await?; current_stats.person_count = Set(current_stats.person_count.unwrap() + 1); Ok(current_stats.save(&self.db).await?.person_count.unwrap()) } - async fn get_people(&self, event_id: String) -> Result>, Box> { + async fn get_people(&self, event_id: String) -> Result>, Self::Error> { // TODO: optimize into one query let event_row = event::Entity::find_by_id(event_id).one(&self.db).await?; @@ -75,11 +70,7 @@ impl Adaptor for PostgresAdaptor { }) } - async fn upsert_person( - &self, - event_id: String, - person: Person, - ) -> Result> { + async fn upsert_person(&self, event_id: String, person: Person) -> Result { Ok(person::ActiveModel { name: Set(person.name), password_hash: Set(person.password_hash), @@ -93,28 +84,29 @@ impl Adaptor for PostgresAdaptor { .into()) } - async fn get_event(&self, id: String) -> Result, Box> { + async fn get_event(&self, id: String) -> Result, Self::Error> { Ok(event::Entity::find_by_id(id) .one(&self.db) .await? .map(|model| model.into())) } - async fn create_event(&self, event: Event) -> Result> { + async fn create_event(&self, event: Event) -> Result { Ok(event::ActiveModel { id: Set(event.id), name: Set(event.name), created_at: Set(event.created_at.naive_utc()), + visited_at: Set(event.visited_at.naive_utc()), times: Set(serde_json::to_value(event.times).unwrap_or(json!([]))), timezone: Set(event.timezone), } - .save(&self.db) + .insert(&self.db) .await? .try_into_model()? .into()) } - async fn delete_event(&self, id: String) -> Result> { + async fn delete_event(&self, id: String) -> Result { let event_id = id.clone(); let person_count = self .db @@ -141,6 +133,7 @@ impl Adaptor for PostgresAdaptor { // Get the current stats as an ActiveModel async fn get_stats_row(db: &DatabaseConnection) -> Result { let current_stats = stats::Entity::find().one(db).await?; + Ok(match current_stats { Some(model) => model.into(), None => stats::ActiveModel { @@ -150,3 +143,61 @@ async fn get_stats_row(db: &DatabaseConnection) -> Result Self { + let connection_string = env::var("DATABASE_URL").unwrap(); + + // Connect to the database + let db = Database::connect(&connection_string).await.unwrap(); + println!("Connected to database at {}", connection_string); + + // Setup tables + Migrator::up(&db, None).await.unwrap(); + + Self { db } + } +} + +impl From for Event { + fn from(value: event::Model) -> Self { + Self { + id: value.id, + name: value.name, + created_at: ChronoDateTime::::from_utc(value.created_at, Utc), + visited_at: ChronoDateTime::::from_utc(value.visited_at, Utc), + times: serde_json::from_value(value.times).unwrap_or(vec![]), + timezone: value.timezone, + } + } +} + +impl From for Person { + fn from(value: person::Model) -> Self { + Self { + name: value.name, + password_hash: value.password_hash, + created_at: ChronoDateTime::::from_utc(value.created_at, Utc), + availability: serde_json::from_value(value.availability).unwrap_or(vec![]), + } + } +} + +#[derive(Display, Debug)] +pub enum SqlAdaptorError { + DbErr(DbErr), + TransactionError(TransactionError), +} + +impl Error for SqlAdaptorError {} + +impl From for SqlAdaptorError { + fn from(value: DbErr) -> Self { + Self::DbErr(value) + } +} +impl From> for SqlAdaptorError { + fn from(value: TransactionError) -> Self { + Self::TransactionError(value) + } +} diff --git a/backend/adaptors/sql/src/migration/setup_tables.rs b/backend/adaptors/sql/src/migration/m01_setup_tables.rs similarity index 97% rename from backend/adaptors/sql/src/migration/setup_tables.rs rename to backend/adaptors/sql/src/migration/m01_setup_tables.rs index 5ce5478..1285b8c 100644 --- a/backend/adaptors/sql/src/migration/setup_tables.rs +++ b/backend/adaptors/sql/src/migration/m01_setup_tables.rs @@ -36,6 +36,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Event::Id).string().not_null().primary_key()) .col(ColumnDef::new(Event::Name).string().not_null()) .col(ColumnDef::new(Event::CreatedAt).timestamp().not_null()) + .col(ColumnDef::new(Event::VisitedAt).timestamp().not_null()) .col(ColumnDef::new(Event::Times).json().not_null()) .col(ColumnDef::new(Event::Timezone).string().not_null()) .to_owned(), @@ -105,6 +106,7 @@ enum Event { Id, Name, CreatedAt, + VisitedAt, Times, Timezone, } diff --git a/backend/adaptors/sql/src/migration/mod.rs b/backend/adaptors/sql/src/migration/mod.rs index 08be57d..08464e8 100644 --- a/backend/adaptors/sql/src/migration/mod.rs +++ b/backend/adaptors/sql/src/migration/mod.rs @@ -1,12 +1,12 @@ pub use sea_orm_migration::prelude::*; -mod setup_tables; +mod m01_setup_tables; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { - vec![Box::new(setup_tables::Migration)] + vec![Box::new(m01_setup_tables::Migration)] } } diff --git a/backend/common/Cargo.toml b/backend/common/Cargo.toml new file mode 100644 index 0000000..5f96e36 --- /dev/null +++ b/backend/common/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "common" +description = "Shared structs and traits for the data storage and transfer of Crab Fit" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.68" +chrono = "0.4.24" diff --git a/backend/common/src/adaptor.rs b/backend/common/src/adaptor.rs new file mode 100644 index 0000000..55f3544 --- /dev/null +++ b/backend/common/src/adaptor.rs @@ -0,0 +1,29 @@ +use std::error::Error; + +use async_trait::async_trait; + +use crate::{ + event::{Event, EventDeletion}, + person::Person, + stats::Stats, +}; + +/// Data storage adaptor, all methods on an adaptor can return an error if +/// something goes wrong, or potentially None if the data requested was not found. +#[async_trait] +pub trait Adaptor: Send + Sync { + type Error: Error; + + async fn get_stats(&self) -> Result; + async fn increment_stat_event_count(&self) -> Result; + async fn increment_stat_person_count(&self) -> Result; + + async fn get_people(&self, event_id: String) -> Result>, Self::Error>; + async fn upsert_person(&self, event_id: String, person: Person) -> Result; + + async fn get_event(&self, id: String) -> Result, Self::Error>; + async fn create_event(&self, event: Event) -> Result; + + /// Delete an event as well as all related people + async fn delete_event(&self, id: String) -> Result; +} diff --git a/backend/data/src/event.rs b/backend/common/src/event.rs similarity index 91% rename from backend/data/src/event.rs rename to backend/common/src/event.rs index 09129ee..4584183 100644 --- a/backend/data/src/event.rs +++ b/backend/common/src/event.rs @@ -4,6 +4,7 @@ pub struct Event { pub id: String, pub name: String, pub created_at: DateTime, + pub visited_at: DateTime, pub times: Vec, pub timezone: String, } diff --git a/backend/data/src/lib.rs b/backend/common/src/lib.rs similarity index 100% rename from backend/data/src/lib.rs rename to backend/common/src/lib.rs diff --git a/backend/data/src/person.rs b/backend/common/src/person.rs similarity index 100% rename from backend/data/src/person.rs rename to backend/common/src/person.rs diff --git a/backend/data/src/stats.rs b/backend/common/src/stats.rs similarity index 100% rename from backend/data/src/stats.rs rename to backend/common/src/stats.rs diff --git a/backend/data/Cargo.toml b/backend/data/Cargo.toml deleted file mode 100644 index 1a3a21f..0000000 --- a/backend/data/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[package] -name = "data" -description = "Structs and traits for the data storage and transfer of Crab Fit" -version = "0.1.0" -edition = "2021" - -[dependencies] -async-trait = "0.1.68" -chrono = "0.4.24" diff --git a/backend/data/src/adaptor.rs b/backend/data/src/adaptor.rs deleted file mode 100644 index 9064453..0000000 --- a/backend/data/src/adaptor.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::error::Error; - -use async_trait::async_trait; - -use crate::{ - event::{Event, EventDeletion}, - person::Person, - stats::Stats, -}; - -/// Data storage adaptor, all methods on an adaptor can return an error if -/// something goes wrong, or potentially None if the data requested was not found. -#[async_trait] -pub trait Adaptor { - /// Creates a new adaptor and performs all setup required - /// - /// # Panics - /// If an error occurs while setting up the adaptor - async fn new() -> Self; - - async fn get_stats(&self) -> Result>; - async fn increment_stat_event_count(&self) -> Result>; - async fn increment_stat_person_count(&self) -> Result>; - - async fn get_people(&self, event_id: String) -> Result>, Box>; - async fn upsert_person( - &self, - event_id: String, - person: Person, - ) -> Result>; - - async fn get_event(&self, id: String) -> Result, Box>; - async fn create_event(&self, event: Event) -> Result>; - - /// Delete an event as well as all related people - async fn delete_event(&self, id: String) -> Result>; -} diff --git a/backend/src/errors.rs b/backend/src/errors.rs new file mode 100644 index 0000000..f4cb7f8 --- /dev/null +++ b/backend/src/errors.rs @@ -0,0 +1,22 @@ +use axum::{http::StatusCode, response::IntoResponse}; +use common::adaptor::Adaptor; + +pub enum ApiError { + AdaptorError(A::Error), + NotFound, + // NotAuthorized, +} + +// Define what the error types above should return +impl IntoResponse for ApiError { + fn into_response(self) -> axum::response::Response { + match self { + ApiError::AdaptorError(e) => { + tracing::error!(?e); + StatusCode::INTERNAL_SERVER_ERROR.into_response() + } + ApiError::NotFound => StatusCode::NOT_FOUND.into_response(), + // ApiError::NotAuthorized => StatusCode::UNAUTHORIZED.into_response(), + } + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 5ff6304..2864391 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,8 +1,17 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, sync::Arc}; -use axum::{routing::get, Router, Server}; -use data::adaptor::Adaptor; -use sql_adaptor::PostgresAdaptor; +use axum::{ + extract, + routing::{get, post}, + Router, Server, +}; +use routes::*; +use sql_adaptor::SqlAdaptor; +use tokio::sync::Mutex; + +mod errors; +mod payloads; +mod routes; #[cfg(debug_assertions)] const MODE: &str = "debug"; @@ -10,14 +19,29 @@ const MODE: &str = "debug"; #[cfg(not(debug_assertions))] const MODE: &str = "release"; +pub struct ApiState { + adaptor: A, +} + +pub type State = extract::State>>>; + #[tokio::main] async fn main() { + tracing_subscriber::fmt::init(); + // Load env dotenv::dotenv().ok(); - PostgresAdaptor::new().await; + let shared_state = Arc::new(Mutex::new(ApiState { + adaptor: SqlAdaptor::new().await, + })); - let app = Router::new().route("/", get(get_root)); + let app = Router::new() + .route("/", get(get_root)) + .route("/stats", get(get_stats)) + .route("/event/:event_id", get(get_event)) + .route("/event", post(create_event)) + .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs new file mode 100644 index 0000000..3a44398 --- /dev/null +++ b/backend/src/payloads.rs @@ -0,0 +1,43 @@ +use axum::Json; +use common::event::Event; +use serde::{Deserialize, Serialize}; + +use crate::errors::ApiError; + +pub type ApiResult = Result, ApiError>; + +#[derive(Deserialize)] +pub struct EventInput { + pub name: String, + pub times: Vec, + pub timezone: String, +} + +#[derive(Serialize)] +pub struct EventResponse { + pub id: String, + pub name: String, + pub times: Vec, + pub timezone: String, + pub created: i64, +} + +impl From for EventResponse { + fn from(value: Event) -> Self { + Self { + id: value.id, + name: value.name, + times: value.times, + timezone: value.timezone, + created: value.created_at.timestamp(), + } + } +} + +#[derive(Serialize)] +#[serde(rename_all(serialize = "camelCase"))] +pub struct StatsResponse { + pub event_count: i32, + pub person_count: i32, + pub version: String, +} diff --git a/backend/src/res/adjectives.json b/backend/src/res/adjectives.json new file mode 100644 index 0000000..48860c5 --- /dev/null +++ b/backend/src/res/adjectives.json @@ -0,0 +1,201 @@ +[ + "Adorable", + "Adventurous", + "Aggressive", + "Agreeable", + "Alert", + "Alive", + "Amused", + "Angry", + "Annoyed", + "Annoying", + "Anxious", + "Arrogant", + "Ashamed", + "Attractive", + "Average", + "Beautiful", + "Better", + "Bewildered", + "Blue", + "Blushing", + "Bored", + "Brainy", + "Brave", + "Breakable", + "Bright", + "Busy", + "Calm", + "Careful", + "Cautious", + "Charming", + "Cheerful", + "Clean", + "Clear", + "Clever", + "Cloudy", + "Clumsy", + "Colorful", + "Comfortable", + "Concerned", + "Confused", + "Cooperative", + "Courageous", + "Crazy", + "Creepy", + "Crowded", + "Curious", + "Cute", + "Dangerous", + "Dark", + "Defiant", + "Delightful", + "Depressed", + "Determined", + "Different", + "Difficult", + "Disgusted", + "Distinct", + "Disturbed", + "Dizzy", + "Doubtful", + "Drab", + "Dull", + "Eager", + "Easy", + "Elated", + "Elegant", + "Embarrassed", + "Enchanting", + "Encouraging", + "Energetic", + "Enthusiastic", + "Envious", + "Evil", + "Excited", + "Expensive", + "Exuberant", + "Fair", + "Faithful", + "Famous", + "Fancy", + "Fantastic", + "Fierce", + "Fine", + "Foolish", + "Fragile", + "Frail", + "Frantic", + "Friendly", + "Frightened", + "Funny", + "Gentle", + "Gifted", + "Glamorous", + "Gleaming", + "Glorious", + "Good", + "Gorgeous", + "Graceful", + "Grumpy", + "Handsome", + "Happy", + "Healthy", + "Helpful", + "Hilarious", + "Homely", + "Hungry", + "Important", + "Impossible", + "Inexpensive", + "Innocent", + "Inquisitive", + "Itchy", + "Jealous", + "Jittery", + "Jolly", + "Joyous", + "Kind", + "Lazy", + "Light", + "Lively", + "Lonely", + "Long", + "Lovely", + "Lucky", + "Magnificent", + "Misty", + "Modern", + "Motionless", + "Muddy", + "Mushy", + "Mysterious", + "Naughty", + "Nervous", + "Nice", + "Nutty", + "Obedient", + "Obnoxious", + "Odd", + "Old-fashioned", + "Open", + "Outrageous", + "Outstanding", + "Panicky", + "Perfect", + "Plain", + "Pleasant", + "Poised", + "Powerful", + "Precious", + "Prickly", + "Proud", + "Puzzled", + "Quaint", + "Real", + "Relieved", + "Scary", + "Selfish", + "Shiny", + "Shy", + "Silly", + "Sleepy", + "Smiling", + "Smoggy", + "Sparkling", + "Splendid", + "Spotless", + "Stormy", + "Strange", + "Successful", + "Super", + "Talented", + "Tame", + "Tasty", + "Tender", + "Tense", + "Terrible", + "Thankful", + "Thoughtful", + "Thoughtless", + "Tired", + "Tough", + "Uninterested", + "Unsightly", + "Unusual", + "Upset", + "Uptight", + "Vast", + "Victorious", + "Vivacious", + "Wandering", + "Weary", + "Wicked", + "Wide-eyed", + "Wild", + "Witty", + "Worried", + "Worrisome", + "Zany", + "Zealous" +] diff --git a/backend/src/res/crabs.json b/backend/src/res/crabs.json new file mode 100644 index 0000000..62bbe15 --- /dev/null +++ b/backend/src/res/crabs.json @@ -0,0 +1,47 @@ +[ + "American Horseshoe", + "Atlantic Ghost", + "Baja Elbow", + "Big Claw Purple Hermit", + "Coldwater Mole", + "Cuata Swim", + "Deepwater Frog", + "Dwarf Teardrop", + "Elegant Hermit", + "Flat Spider", + "Ghost", + "Globe Purse", + "Green", + "Halloween", + "Harbor Spider", + "Inflated Spider", + "Left Clawed Hermit", + "Lumpy Claw", + "Magnificent Hermit", + "Mexican Spider", + "Mouthless Land", + "Northern Lemon Rock", + "Pacific Arrow", + "Pacific Mole", + "Paco Box", + "Panamic Spider", + "Purple Shore", + "Red Rock", + "Red Swim", + "Red-leg Hermit", + "Robust Swim", + "Rough Swim", + "Sand Swim", + "Sally Lightfoot", + "Shamed-face Box", + "Shamed-face Heart Box", + "Shell", + "Small Arched Box", + "Southern Kelp", + "Spotted Box", + "Striated Mole", + "Striped Shore", + "Tropical Mole", + "Walking Rock", + "Yellow Shore" +] diff --git a/backend/src/routes/create_event.rs b/backend/src/routes/create_event.rs new file mode 100644 index 0000000..74bc66f --- /dev/null +++ b/backend/src/routes/create_event.rs @@ -0,0 +1,92 @@ +use axum::{extract, Json}; +use common::{adaptor::Adaptor, event::Event}; +use rand::{seq::SliceRandom, thread_rng, Rng}; +use regex::Regex; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, EventInput, EventResponse}, + State, +}; + +pub async fn create_event( + extract::State(state): State, + Json(input): Json, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + // Get the current timestamp + let now = chrono::offset::Utc::now(); + + // Generate a name if none provided + let name = match input.name.trim() { + "" => generate_name(), + x => x.to_string(), + }; + + // Generate an ID + let mut id = generate_id(&name); + + // Check the ID doesn't already exist + while (adaptor + .get_event(id.clone()) + .await + .map_err(ApiError::AdaptorError)?) + .is_some() + { + id = generate_id(&name); + } + + let event = adaptor + .create_event(Event { + id, + name, + created_at: now, + visited_at: now, + times: input.times, + timezone: input.timezone, + }) + .await + .map_err(ApiError::AdaptorError)?; + + // Update stats + adaptor + .increment_stat_event_count() + .await + .map_err(ApiError::AdaptorError)?; + + Ok(Json(event.into())) +} + +// Generate a random name based on an adjective and a crab species +fn generate_name() -> String { + let adjectives: Vec = + serde_json::from_slice(include_bytes!("../res/adjectives.json")).unwrap(); + let crabs: Vec = serde_json::from_slice(include_bytes!("../res/crabs.json")).unwrap(); + + format!( + "{} {} Crab", + adjectives.choose(&mut thread_rng()).unwrap(), + crabs.choose(&mut thread_rng()).unwrap() + ) +} + +// Generate a slug for the crab fit +fn generate_id(name: &str) -> String { + let mut id = encode_name(name.to_string()); + if id.replace('-', "").is_empty() { + id = encode_name(generate_name()); + } + let number = thread_rng().gen_range(100000..=999999); + format!("{}-{}", id, number) +} + +// Use punycode to encode the name +fn encode_name(name: String) -> String { + let pc = punycode::encode(&name.trim().to_lowercase()) + .unwrap_or(String::from("")) + .trim() + .replace(|c: char| !c.is_ascii_alphanumeric() && c != ' ', ""); + let re = Regex::new(r"\s+").unwrap(); + re.replace_all(&pc, "-").to_string() +} diff --git a/backend/src/routes/get_event.rs b/backend/src/routes/get_event.rs new file mode 100644 index 0000000..98ad2a6 --- /dev/null +++ b/backend/src/routes/get_event.rs @@ -0,0 +1,34 @@ +use axum::{ + extract::{self, Path}, + Json, +}; +use common::adaptor::Adaptor; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, EventResponse}, + State, +}; + +pub async fn get_event( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let event = adaptor + .get_event(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match event { + Some(event) => Ok(Json(EventResponse { + id: event.id, + name: event.name, + times: event.times, + timezone: event.timezone, + created: event.created_at.timestamp(), + })), + None => Err(ApiError::NotFound), + } +} diff --git a/backend/src/routes/get_stats.rs b/backend/src/routes/get_stats.rs new file mode 100644 index 0000000..71a0d81 --- /dev/null +++ b/backend/src/routes/get_stats.rs @@ -0,0 +1,20 @@ +use axum::{extract, Json}; +use common::adaptor::Adaptor; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, StatsResponse}, + State, +}; + +pub async fn get_stats(extract::State(state): State) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let stats = adaptor.get_stats().await.map_err(ApiError::AdaptorError)?; + + Ok(Json(StatsResponse { + event_count: stats.event_count, + person_count: stats.person_count, + version: env!("CARGO_PKG_VERSION").to_string(), + })) +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs new file mode 100644 index 0000000..f1fb456 --- /dev/null +++ b/backend/src/routes/mod.rs @@ -0,0 +1,8 @@ +mod get_event; +pub use get_event::get_event; + +mod get_stats; +pub use get_stats::get_stats; + +mod create_event; +pub use create_event::create_event; From 8a26cebf3bb1be2bee157fa2e78d1b3de3201a31 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 13:54:16 +1000 Subject: [PATCH 03/29] Allow name to be optionally passed when creating an event --- backend/src/payloads.rs | 2 +- backend/src/routes/create_event.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 3a44398..3f9de74 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -8,7 +8,7 @@ pub type ApiResult = Result, ApiError>; #[derive(Deserialize)] pub struct EventInput { - pub name: String, + pub name: Option, pub times: Vec, pub timezone: String, } diff --git a/backend/src/routes/create_event.rs b/backend/src/routes/create_event.rs index 74bc66f..9e0d6f8 100644 --- a/backend/src/routes/create_event.rs +++ b/backend/src/routes/create_event.rs @@ -19,9 +19,9 @@ pub async fn create_event( let now = chrono::offset::Utc::now(); // Generate a name if none provided - let name = match input.name.trim() { - "" => generate_name(), - x => x.to_string(), + let name = match input.name { + Some(x) if !x.is_empty() => x.trim().to_string(), + _ => generate_name(), }; // Generate an ID From 7770ab958a6a71b967a1911887c7cc901b06608c Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 14:02:16 +1000 Subject: [PATCH 04/29] Add get_people route --- backend/src/main.rs | 1 + backend/src/payloads.rs | 19 ++++++++++++++++++- backend/src/routes/get_people.rs | 28 ++++++++++++++++++++++++++++ backend/src/routes/mod.rs | 3 +++ 4 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 backend/src/routes/get_people.rs diff --git a/backend/src/main.rs b/backend/src/main.rs index 2864391..bf3da57 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -41,6 +41,7 @@ async fn main() { .route("/stats", get(get_stats)) .route("/event/:event_id", get(get_event)) .route("/event", post(create_event)) + .route("/event/:event_id/people", get(get_people)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 3f9de74..27e9334 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -1,5 +1,5 @@ use axum::Json; -use common::event::Event; +use common::{event::Event, person::Person}; use serde::{Deserialize, Serialize}; use crate::errors::ApiError; @@ -41,3 +41,20 @@ pub struct StatsResponse { pub person_count: i32, pub version: String, } + +#[derive(Serialize)] +pub struct PersonResponse { + pub name: String, + pub availability: Vec, + pub created: i64, +} + +impl From for PersonResponse { + fn from(value: Person) -> Self { + Self { + name: value.name, + availability: value.availability, + created: value.created_at.timestamp(), + } + } +} diff --git a/backend/src/routes/get_people.rs b/backend/src/routes/get_people.rs new file mode 100644 index 0000000..e8fb9c2 --- /dev/null +++ b/backend/src/routes/get_people.rs @@ -0,0 +1,28 @@ +use axum::{ + extract::{self, Path}, + Json, +}; +use common::adaptor::Adaptor; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, PersonResponse}, + State, +}; + +pub async fn get_people( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult, A> { + let adaptor = &state.lock().await.adaptor; + + let people = adaptor + .get_people(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match people { + Some(people) => Ok(Json(people.into_iter().map(|p| p.into()).collect())), + None => Err(ApiError::NotFound), + } +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index f1fb456..1eb81d7 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -6,3 +6,6 @@ pub use get_stats::get_stats; mod create_event; pub use create_event::create_event; + +mod get_people; +pub use get_people::get_people; From d2a94b078c73ea1f25f3c3fa3b98e5644f25fae2 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 16:30:16 +1000 Subject: [PATCH 05/29] Add get_person route --- backend/Cargo.lock | 51 ++++++++++++++++++- backend/Cargo.toml | 1 + backend/adaptors/sql/src/lib.rs | 22 +++++--- backend/src/errors.rs | 4 +- backend/src/main.rs | 3 +- backend/src/payloads.rs | 5 ++ backend/src/routes/get_person.rs | 87 ++++++++++++++++++++++++++++++++ backend/src/routes/mod.rs | 3 ++ 8 files changed, 164 insertions(+), 12 deletions(-) create mode 100644 backend/src/routes/get_person.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f4ada34..12e9e0b 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -295,12 +295,31 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "bcrypt" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a" +dependencies = [ + "base64 0.21.0", + "blowfish", + "getrandom", + "subtle", + "zeroize", +] + [[package]] name = "bigdecimal" version = "0.3.1" @@ -342,6 +361,16 @@ dependencies = [ "log", ] +[[package]] +name = "blowfish" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" +dependencies = [ + "byteorder", + "cipher", +] + [[package]] name = "borsh" version = "0.10.3" @@ -455,6 +484,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clap" version = "3.2.25" @@ -554,6 +593,7 @@ name = "crabfit_backend" version = "1.1.0" dependencies = [ "axum", + "bcrypt", "chrono", "common", "dotenv", @@ -1119,6 +1159,15 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2280,7 +2329,7 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ "ahash 0.7.6", "atoi", - "base64", + "base64 0.13.1", "bigdecimal", "bitflags", "byteorder", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 99a15f9..86fce04 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -20,3 +20,4 @@ regex = "1.8.1" tracing = "0.1.37" tracing-subscriber = "0.3.17" chrono = "0.4.24" +bcrypt = "0.14.0" diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index ac34104..74419d1 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -71,17 +71,23 @@ impl Adaptor for SqlAdaptor { } async fn upsert_person(&self, event_id: String, person: Person) -> Result { - Ok(person::ActiveModel { - name: Set(person.name), + let data = person::ActiveModel { + name: Set(person.name.clone()), password_hash: Set(person.password_hash), created_at: Set(person.created_at.naive_utc()), availability: Set(serde_json::to_value(person.availability).unwrap_or(json!([]))), - event_id: Set(event_id), - } - .save(&self.db) - .await? - .try_into_model()? - .into()) + event_id: Set(event_id.clone()), + }; + + Ok( + match person::Entity::find_by_id((event_id, person.name)) + .one(&self.db) + .await? + { + Some(_) => data.update(&self.db).await?.try_into_model()?.into(), + None => data.insert(&self.db).await?.try_into_model()?.into(), + }, + ) } async fn get_event(&self, id: String) -> Result, Self::Error> { diff --git a/backend/src/errors.rs b/backend/src/errors.rs index f4cb7f8..47719e0 100644 --- a/backend/src/errors.rs +++ b/backend/src/errors.rs @@ -4,7 +4,7 @@ use common::adaptor::Adaptor; pub enum ApiError { AdaptorError(A::Error), NotFound, - // NotAuthorized, + NotAuthorized, } // Define what the error types above should return @@ -16,7 +16,7 @@ impl IntoResponse for ApiError { StatusCode::INTERNAL_SERVER_ERROR.into_response() } ApiError::NotFound => StatusCode::NOT_FOUND.into_response(), - // ApiError::NotAuthorized => StatusCode::UNAUTHORIZED.into_response(), + ApiError::NotAuthorized => StatusCode::UNAUTHORIZED.into_response(), } } } diff --git a/backend/src/main.rs b/backend/src/main.rs index bf3da57..d9c05fb 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -39,9 +39,10 @@ async fn main() { let app = Router::new() .route("/", get(get_root)) .route("/stats", get(get_stats)) - .route("/event/:event_id", get(get_event)) .route("/event", post(create_event)) + .route("/event/:event_id", get(get_event)) .route("/event/:event_id/people", get(get_people)) + .route("/event/:event_id/people/:person_name", get(get_person)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 27e9334..655c19f 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -58,3 +58,8 @@ impl From for PersonResponse { } } } + +#[derive(Deserialize)] +pub struct PersonInput { + pub password: Option, +} diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/get_person.rs new file mode 100644 index 0000000..348acf5 --- /dev/null +++ b/backend/src/routes/get_person.rs @@ -0,0 +1,87 @@ +use axum::{ + extract::{self, Path}, + Json, +}; +use common::{adaptor::Adaptor, person::Person}; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, PersonInput, PersonResponse}, + State, +}; + +pub async fn get_person( + extract::State(state): State, + Path((event_id, person_name)): Path<(String, String)>, + input: Option>, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + // Get inputted password + let password = match input { + Some(Json(i)) => i.password, + None => None, + }; + + let existing_people = adaptor + .get_people(event_id.clone()) + .await + .map_err(ApiError::AdaptorError)?; + + // Event not found + if existing_people.is_none() { + return Err(ApiError::NotFound); + } + + // Check if the user already exists + let existing_person = existing_people + .unwrap() + .into_iter() + .find(|p| p.name == person_name); + + match existing_person { + // Login + Some(p) => { + // Verify password (if set) + if verify_password(&p, password) { + Ok(Json(p.into())) + } else { + Err(ApiError::NotAuthorized) + } + } + // Signup + None => { + // Update stats + adaptor + .increment_stat_person_count() + .await + .map_err(ApiError::AdaptorError)?; + + Ok(Json( + adaptor + .upsert_person( + event_id, + Person { + name: person_name, + password_hash: password + .map(|raw| bcrypt::hash(raw, 10).unwrap_or(String::from(""))), + created_at: chrono::offset::Utc::now(), + availability: vec![], + }, + ) + .await + .map_err(ApiError::AdaptorError)? + .into(), + )) + } + } +} + +fn verify_password(person: &Person, raw: Option) -> bool { + match &person.password_hash { + Some(hash) => bcrypt::verify(raw.unwrap_or(String::from("")), hash).unwrap_or(false), + // Specifically allow a user who doesn't have a password + // set to log in with or without any password input + None => true, + } +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 1eb81d7..84f5044 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -9,3 +9,6 @@ pub use create_event::create_event; mod get_people; pub use get_people::get_people; + +mod get_person; +pub use get_person::get_person; From cd18427d1b8ad702ea734532a0996051d77fc0ff Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 17:13:04 +1000 Subject: [PATCH 06/29] Add update_person route --- backend/adaptors/sql/src/lib.rs | 13 ++++++- backend/src/main.rs | 8 +++- backend/src/payloads.rs | 29 ++++++++++---- backend/src/routes/get_event.rs | 8 +--- backend/src/routes/get_person.rs | 6 +-- backend/src/routes/get_stats.rs | 6 +-- backend/src/routes/mod.rs | 3 ++ backend/src/routes/update_person.rs | 59 +++++++++++++++++++++++++++++ 8 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 backend/src/routes/update_person.rs diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index 74419d1..0a9d017 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -80,7 +80,7 @@ impl Adaptor for SqlAdaptor { }; Ok( - match person::Entity::find_by_id((event_id, person.name)) + match person::Entity::find_by_id((person.name, event_id)) .one(&self.db) .await? { @@ -156,7 +156,16 @@ impl SqlAdaptor { // Connect to the database let db = Database::connect(&connection_string).await.unwrap(); - println!("Connected to database at {}", connection_string); + println!( + "{} Connected to database at {}", + match db { + DatabaseConnection::SqlxMySqlPoolConnection(_) => "🐬", + DatabaseConnection::SqlxPostgresPoolConnection(_) => "🐘", + DatabaseConnection::SqlxSqlitePoolConnection(_) => "🪶", + DatabaseConnection::Disconnected => panic!("Failed to connect"), + }, + connection_string + ); // Setup tables Migrator::up(&db, None).await.unwrap(); diff --git a/backend/src/main.rs b/backend/src/main.rs index d9c05fb..3ec7bf7 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -2,7 +2,7 @@ use std::{net::SocketAddr, sync::Arc}; use axum::{ extract, - routing::{get, post}, + routing::{get, patch, post}, Router, Server, }; use routes::*; @@ -43,11 +43,15 @@ async fn main() { .route("/event/:event_id", get(get_event)) .route("/event/:event_id/people", get(get_people)) .route("/event/:event_id/people/:person_name", get(get_person)) + .route("/event/:event_id/people/:person_name", patch(update_person)) .with_state(shared_state); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); - println!("Crab Fit API listening at http://{} in {} mode", addr, MODE); + println!( + "🦀 Crab Fit API listening at http://{} in {} mode", + addr, MODE + ); Server::bind(&addr) .serve(app.into_make_service()) .await diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 655c19f..2bfe430 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -1,5 +1,5 @@ use axum::Json; -use common::{event::Event, person::Person}; +use common::{event::Event, person::Person, stats::Stats}; use serde::{Deserialize, Serialize}; use crate::errors::ApiError; @@ -19,7 +19,7 @@ pub struct EventResponse { pub name: String, pub times: Vec, pub timezone: String, - pub created: i64, + pub created_at: i64, } impl From for EventResponse { @@ -29,24 +29,33 @@ impl From for EventResponse { name: value.name, times: value.times, timezone: value.timezone, - created: value.created_at.timestamp(), + created_at: value.created_at.timestamp(), } } } #[derive(Serialize)] -#[serde(rename_all(serialize = "camelCase"))] pub struct StatsResponse { pub event_count: i32, pub person_count: i32, pub version: String, } +impl From for StatsResponse { + fn from(value: Stats) -> Self { + Self { + event_count: value.event_count, + person_count: value.person_count, + version: env!("CARGO_PKG_VERSION").to_string(), + } + } +} + #[derive(Serialize)] pub struct PersonResponse { pub name: String, pub availability: Vec, - pub created: i64, + pub created_at: i64, } impl From for PersonResponse { @@ -54,12 +63,18 @@ impl From for PersonResponse { Self { name: value.name, availability: value.availability, - created: value.created_at.timestamp(), + created_at: value.created_at.timestamp(), } } } #[derive(Deserialize)] -pub struct PersonInput { +pub struct GetPersonInput { pub password: Option, } + +#[derive(Deserialize)] +pub struct UpdatePersonInput { + pub password: Option, + pub availability: Vec, +} diff --git a/backend/src/routes/get_event.rs b/backend/src/routes/get_event.rs index 98ad2a6..5ce0ca1 100644 --- a/backend/src/routes/get_event.rs +++ b/backend/src/routes/get_event.rs @@ -22,13 +22,7 @@ pub async fn get_event( .map_err(ApiError::AdaptorError)?; match event { - Some(event) => Ok(Json(EventResponse { - id: event.id, - name: event.name, - times: event.times, - timezone: event.timezone, - created: event.created_at.timestamp(), - })), + Some(event) => Ok(Json(event.into())), None => Err(ApiError::NotFound), } } diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/get_person.rs index 348acf5..0e00df3 100644 --- a/backend/src/routes/get_person.rs +++ b/backend/src/routes/get_person.rs @@ -6,14 +6,14 @@ use common::{adaptor::Adaptor, person::Person}; use crate::{ errors::ApiError, - payloads::{ApiResult, PersonInput, PersonResponse}, + payloads::{ApiResult, GetPersonInput, PersonResponse}, State, }; pub async fn get_person( extract::State(state): State, Path((event_id, person_name)): Path<(String, String)>, - input: Option>, + input: Option>, ) -> ApiResult { let adaptor = &state.lock().await.adaptor; @@ -77,7 +77,7 @@ pub async fn get_person( } } -fn verify_password(person: &Person, raw: Option) -> bool { +pub fn verify_password(person: &Person, raw: Option) -> bool { match &person.password_hash { Some(hash) => bcrypt::verify(raw.unwrap_or(String::from("")), hash).unwrap_or(false), // Specifically allow a user who doesn't have a password diff --git a/backend/src/routes/get_stats.rs b/backend/src/routes/get_stats.rs index 71a0d81..46d9efc 100644 --- a/backend/src/routes/get_stats.rs +++ b/backend/src/routes/get_stats.rs @@ -12,9 +12,5 @@ pub async fn get_stats(extract::State(state): State) -> ApiResult let stats = adaptor.get_stats().await.map_err(ApiError::AdaptorError)?; - Ok(Json(StatsResponse { - event_count: stats.event_count, - person_count: stats.person_count, - version: env!("CARGO_PKG_VERSION").to_string(), - })) + Ok(Json(stats.into())) } diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 84f5044..4881408 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -12,3 +12,6 @@ pub use get_people::get_people; mod get_person; pub use get_person::get_person; + +mod update_person; +pub use update_person::update_person; diff --git a/backend/src/routes/update_person.rs b/backend/src/routes/update_person.rs new file mode 100644 index 0000000..99b3804 --- /dev/null +++ b/backend/src/routes/update_person.rs @@ -0,0 +1,59 @@ +use axum::{ + extract::{self, Path}, + Json, +}; +use common::{adaptor::Adaptor, person::Person}; + +use crate::{ + errors::ApiError, + payloads::{ApiResult, PersonResponse, UpdatePersonInput}, + State, +}; + +use super::get_person::verify_password; + +pub async fn update_person( + extract::State(state): State, + Path((event_id, person_name)): Path<(String, String)>, + Json(input): Json, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let existing_people = adaptor + .get_people(event_id.clone()) + .await + .map_err(ApiError::AdaptorError)?; + + // Event not found + if existing_people.is_none() { + return Err(ApiError::NotFound); + } + + // Check if the user exists + let existing_person = existing_people + .unwrap() + .into_iter() + .find(|p| p.name == person_name) + .ok_or(ApiError::NotFound)?; + + // Verify password (if set) + if !verify_password(&existing_person, input.password) { + return Err(ApiError::NotAuthorized); + } + + Ok(Json( + adaptor + .upsert_person( + event_id, + Person { + name: existing_person.name, + password_hash: existing_person.password_hash, + created_at: existing_person.created_at, + availability: input.availability, + }, + ) + .await + .map_err(ApiError::AdaptorError)? + .into(), + )) +} From 4676abf6a9df3efd38947cc041e73ab04d2eb6a9 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 18:04:57 +1000 Subject: [PATCH 07/29] Add cors config --- backend/Cargo.lock | 25 +++++++++++++++++++++++++ backend/Cargo.toml | 1 + backend/src/main.rs | 32 +++++++++++++++++++++++--------- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 12e9e0b..f1f695a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -604,6 +604,7 @@ dependencies = [ "serde_json", "sql-adaptor", "tokio", + "tower-http", "tracing", "tracing-subscriber", ] @@ -1080,6 +1081,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + [[package]] name = "httparse" version = "1.8.0" @@ -2642,6 +2649,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.2" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 86fce04..d123fac 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -21,3 +21,4 @@ tracing = "0.1.37" tracing-subscriber = "0.3.17" chrono = "0.4.24" bcrypt = "0.14.0" +tower-http = { version = "0.4.0", features = ["cors"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index 3ec7bf7..644a879 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,24 +1,20 @@ -use std::{net::SocketAddr, sync::Arc}; +use std::{env, net::SocketAddr, sync::Arc}; use axum::{ extract, + http::{HeaderValue, Method}, routing::{get, patch, post}, Router, Server, }; use routes::*; use sql_adaptor::SqlAdaptor; use tokio::sync::Mutex; +use tower_http::cors::CorsLayer; mod errors; mod payloads; mod routes; -#[cfg(debug_assertions)] -const MODE: &str = "debug"; - -#[cfg(not(debug_assertions))] -const MODE: &str = "release"; - pub struct ApiState { adaptor: A, } @@ -36,6 +32,18 @@ async fn main() { adaptor: SqlAdaptor::new().await, })); + let cors = CorsLayer::new() + .allow_methods([Method::GET, Method::POST, Method::PATCH]) + .allow_origin( + if cfg!(debug_assertions) { + "http://localhost:1234".to_owned() + } else { + env::var("FRONTEND_URL").expect("Missing FRONTEND_URL environment variable") + } + .parse::() + .unwrap(), + ); + let app = Router::new() .route("/", get(get_root)) .route("/stats", get(get_stats)) @@ -44,13 +52,19 @@ async fn main() { .route("/event/:event_id/people", get(get_people)) .route("/event/:event_id/people/:person_name", get(get_person)) .route("/event/:event_id/people/:person_name", patch(update_person)) - .with_state(shared_state); + .with_state(shared_state) + .layer(cors); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); println!( "🦀 Crab Fit API listening at http://{} in {} mode", - addr, MODE + addr, + if cfg!(debug_assertions) { + "debug" + } else { + "release" + } ); Server::bind(&addr) .serve(app.into_make_service()) From 983424e17b11754efa8381015656678fafb07b58 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 22:03:52 +1000 Subject: [PATCH 08/29] Add rate limiting functionality --- backend/Cargo.lock | 173 +++++++++++++++++++++++++++++++++++++++++++- backend/Cargo.toml | 4 +- backend/src/main.rs | 27 ++++++- 3 files changed, 197 insertions(+), 7 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f1f695a..768aa05 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -604,7 +604,9 @@ dependencies = [ "serde_json", "sql-adaptor", "tokio", + "tower", "tower-http", + "tower_governor", "tracing", "tracing-subscriber", ] @@ -702,6 +704,19 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "dashmap" +version = "5.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +dependencies = [ + "cfg-if", + "hashbrown 0.12.3", + "lock_api", + "once_cell", + "parking_lot_core 0.9.7", +] + [[package]] name = "der" version = "0.5.1" @@ -840,6 +855,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "forwarded-header-value" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" +dependencies = [ + "nonempty", + "thiserror", +] + [[package]] name = "futures" version = "0.3.28" @@ -848,6 +873,7 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -889,7 +915,7 @@ checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" dependencies = [ "futures-core", "lock_api", - "parking_lot", + "parking_lot 0.11.2", ] [[package]] @@ -913,6 +939,17 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "futures-sink" version = "0.3.28" @@ -925,6 +962,12 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +[[package]] +name = "futures-timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" + [[package]] name = "futures-util" version = "0.3.28" @@ -934,6 +977,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -975,6 +1019,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "governor" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5" +dependencies = [ + "cfg-if", + "dashmap", + "futures", + "futures-timer", + "no-std-compat", + "nonzero_ext", + "parking_lot 0.12.1", + "quanta", + "rand", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1295,6 +1357,15 @@ dependencies = [ "value-bag", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1367,6 +1438,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "no-std-compat" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" + [[package]] name = "nom" version = "7.1.3" @@ -1377,6 +1454,18 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nonempty" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" + +[[package]] +name = "nonzero_ext" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -1555,7 +1644,17 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", - "parking_lot_core", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.7", ] [[package]] @@ -1572,6 +1671,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.2.16", + "smallvec", + "windows-sys 0.45.0", +] + [[package]] name = "paste" version = "1.0.12" @@ -1743,6 +1855,22 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9e1dcb320d6839f6edb64f7a4a59d39b30480d4d1765b56873f7c858538a5fe" +[[package]] +name = "quanta" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" +dependencies = [ + "crossbeam-utils", + "libc", + "mach", + "once_cell", + "raw-cpuid", + "wasi 0.10.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.27" @@ -1782,6 +1910,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2237,6 +2374,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + [[package]] name = "simdutf8" version = "0.1.4" @@ -2586,7 +2732,9 @@ dependencies = [ "libc", "mio", "num_cpus", + "parking_lot 0.12.1", "pin-project-lite", + "signal-hook-registry", "socket2", "tokio-macros", "windows-sys 0.48.0", @@ -2665,6 +2813,7 @@ dependencies = [ "pin-project-lite", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -2679,6 +2828,26 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "tower_governor" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6be418f6d18863291f0a7fa1da1de71495a19a54b5fb44969136f731a47e86" +dependencies = [ + "axum", + "forwarded-header-value", + "futures", + "futures-core", + "governor", + "http", + "pin-project", + "thiserror", + "tokio", + "tower", + "tower-layer", + "tracing", +] + [[package]] name = "tracing" version = "0.1.37" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d123fac..2e0e2a7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -21,4 +21,6 @@ tracing = "0.1.37" tracing-subscriber = "0.3.17" chrono = "0.4.24" bcrypt = "0.14.0" -tower-http = { version = "0.4.0", features = ["cors"] } +tower-http = { version = "0.4.0", features = ["cors", "trace"] } +tower_governor = "0.0.4" +tower = "0.4.13" diff --git a/backend/src/main.rs b/backend/src/main.rs index 644a879..a875d68 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,15 +1,18 @@ use std::{env, net::SocketAddr, sync::Arc}; use axum::{ + error_handling::HandleErrorLayer, extract, http::{HeaderValue, Method}, routing::{get, patch, post}, - Router, Server, + BoxError, Router, Server, }; use routes::*; use sql_adaptor::SqlAdaptor; use tokio::sync::Mutex; -use tower_http::cors::CorsLayer; +use tower::ServiceBuilder; +use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; +use tower_http::{cors::CorsLayer, trace::TraceLayer}; mod errors; mod payloads; @@ -32,6 +35,7 @@ async fn main() { adaptor: SqlAdaptor::new().await, })); + // CORS configuration let cors = CorsLayer::new() .allow_methods([Method::GET, Method::POST, Method::PATCH]) .allow_origin( @@ -44,6 +48,19 @@ async fn main() { .unwrap(), ); + // Rate limiting configuration (using tower_governor) + // From the docs: Allows bursts with up to eight requests and replenishes + // one element after 500ms, based on peer IP. + let governor_config = Box::new(GovernorConfigBuilder::default().finish().unwrap()); + let rate_limit = ServiceBuilder::new() + // Handle errors from governor and convert into HTTP responses + .layer(HandleErrorLayer::new(|e: BoxError| async move { + display_error(e) + })) + .layer(GovernorLayer { + config: Box::leak(governor_config), + }); + let app = Router::new() .route("/", get(get_root)) .route("/stats", get(get_stats)) @@ -53,7 +70,9 @@ async fn main() { .route("/event/:event_id/people/:person_name", get(get_person)) .route("/event/:event_id/people/:person_name", patch(update_person)) .with_state(shared_state) - .layer(cors); + .layer(cors) + .layer(rate_limit) + .layer(TraceLayer::new_for_http()); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -67,7 +86,7 @@ async fn main() { } ); Server::bind(&addr) - .serve(app.into_make_service()) + .serve(app.into_make_service_with_connect_info::()) .await .unwrap(); } From 300285e84ba51d43d3f4ce87efa11b5c70892aaf Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sat, 13 May 2023 22:36:11 +1000 Subject: [PATCH 09/29] Update cargo metadata --- backend/Cargo.lock | 2 +- backend/Cargo.toml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 768aa05..ad21c05 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -590,7 +590,7 @@ dependencies = [ [[package]] name = "crabfit_backend" -version = "1.1.0" +version = "2.0.0" dependencies = [ "axum", "bcrypt", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2e0e2a7..8f0dfd3 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,8 @@ [package] name = "crabfit_backend" -version = "1.1.0" +description = "API for Crab Fit" +license = "GPL-3.0-only" +version = "2.0.0" edition = "2021" [workspace] From f46f456db0c14e793ed0c26e31c077e4dab4e9ac Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sun, 14 May 2023 00:48:23 +1000 Subject: [PATCH 10/29] Add API docs with utoipa --- backend/Cargo.lock | 171 ++++++++++++++++++++++++++++ backend/Cargo.toml | 2 + backend/src/main.rs | 32 ++++++ backend/src/payloads.rs | 13 ++- backend/src/routes/create_event.rs | 21 +++- backend/src/routes/get_event.rs | 14 +++ backend/src/routes/get_people.rs | 14 +++ backend/src/routes/get_person.rs | 19 ++++ backend/src/routes/get_stats.rs | 10 ++ backend/src/routes/mod.rs | 12 +- backend/src/routes/update_person.rs | 19 ++++ 11 files changed, 311 insertions(+), 16 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index ad21c05..4e5bcab 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -8,6 +8,12 @@ version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "ahash" version = "0.7.6" @@ -609,6 +615,17 @@ dependencies = [ "tower_governor", "tracing", "tracing-subscriber", + "utoipa", + "utoipa-swagger-ui", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", ] [[package]] @@ -813,6 +830,16 @@ dependencies = [ "instant", ] +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "flume" version = "0.10.14" @@ -1226,6 +1253,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1402,12 +1430,31 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" version = "0.8.6" @@ -2034,6 +2081,41 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "shellexpand", + "syn 1.0.109", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rust_decimal" version = "1.29.1" @@ -2078,6 +2160,15 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.21" @@ -2374,6 +2465,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shellexpand" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ccc8076840c4da029af4f87e4e8daeb0fca6b87bbb02e10cb60b791450e11e4" +dependencies = [ + "dirs", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2923,6 +3023,15 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" @@ -2973,6 +3082,46 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utoipa" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ae74ef183fae36d650f063ae7bde1cacbe1cd7e72b617cbe1e985551878b98" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ea8ac818da7e746a63285594cce8a96f5e00ee31994e655bd827569cb8b137b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.15", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "062bba5a3568e126ac72049a63254f4cb1da2eb713db0c1ab2a4c76be191db8c" +dependencies = [ + "axum", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "utoipa", + "zip", +] + [[package]] name = "uuid" version = "1.3.2" @@ -3016,6 +3165,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -3316,3 +3475,15 @@ name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" + +[[package]] +name = "zip" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e92305c174683d78035cbf1b70e18db6329cc0f1b9cae0a52ca90bf5bfe7125" +dependencies = [ + "byteorder", + "crc32fast", + "crossbeam-utils", + "flate2", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 8f0dfd3..63c90b9 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -26,3 +26,5 @@ bcrypt = "0.14.0" tower-http = { version = "0.4.0", features = ["cors", "trace"] } tower_governor = "0.0.4" tower = "0.4.13" +utoipa = { version = "3.3.0", features = ["axum_extras", "preserve_order"] } +utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] } diff --git a/backend/src/main.rs b/backend/src/main.rs index a875d68..8d7b5f1 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -13,6 +13,8 @@ use tokio::sync::Mutex; use tower::ServiceBuilder; use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; use tower_http::{cors::CorsLayer, trace::TraceLayer}; +use utoipa::OpenApi; +use utoipa_swagger_ui::SwaggerUi; mod errors; mod payloads; @@ -31,6 +33,35 @@ async fn main() { // Load env dotenv::dotenv().ok(); + #[derive(OpenApi)] + #[openapi( + info(title = "Crab Fit API"), + paths( + routes::get_stats::get_stats, + routes::create_event::create_event, + routes::get_event::get_event, + routes::get_people::get_people, + routes::get_person::get_person, + routes::update_person::update_person, + ), + components( + schemas( + payloads::StatsResponse, + payloads::EventResponse, + payloads::PersonResponse, + payloads::EventInput, + payloads::GetPersonInput, + payloads::UpdatePersonInput, + ), + ), + tags( + (name = "info"), + (name = "event"), + (name = "person"), + ), + )] + struct ApiDoc; + let shared_state = Arc::new(Mutex::new(ApiState { adaptor: SqlAdaptor::new().await, })); @@ -62,6 +93,7 @@ async fn main() { }); let app = Router::new() + .merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi())) .route("/", get(get_root)) .route("/stats", get(get_stats)) .route("/event", post(create_event)) diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 2bfe430..60779e6 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -1,19 +1,20 @@ use axum::Json; use common::{event::Event, person::Person, stats::Stats}; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; use crate::errors::ApiError; pub type ApiResult = Result, ApiError>; -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct EventInput { pub name: Option, pub times: Vec, pub timezone: String, } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct EventResponse { pub id: String, pub name: String, @@ -34,7 +35,7 @@ impl From for EventResponse { } } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct StatsResponse { pub event_count: i32, pub person_count: i32, @@ -51,7 +52,7 @@ impl From for StatsResponse { } } -#[derive(Serialize)] +#[derive(Serialize, ToSchema)] pub struct PersonResponse { pub name: String, pub availability: Vec, @@ -68,12 +69,12 @@ impl From for PersonResponse { } } -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct GetPersonInput { pub password: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, ToSchema)] pub struct UpdatePersonInput { pub password: Option, pub availability: Vec, diff --git a/backend/src/routes/create_event.rs b/backend/src/routes/create_event.rs index 9e0d6f8..0de6a4b 100644 --- a/backend/src/routes/create_event.rs +++ b/backend/src/routes/create_event.rs @@ -1,18 +1,31 @@ -use axum::{extract, Json}; +use axum::{extract, http::StatusCode, Json}; use common::{adaptor::Adaptor, event::Event}; use rand::{seq::SliceRandom, thread_rng, Rng}; use regex::Regex; use crate::{ errors::ApiError, - payloads::{ApiResult, EventInput, EventResponse}, + payloads::{EventInput, EventResponse}, State, }; +#[utoipa::path( + post, + path = "/event", + request_body(content = EventInput, description = "New event details"), + responses( + (status = 201, description = "Created", body = EventResponse), + (status = 415, description = "Unsupported input format"), + (status = 422, description = "Invalid input provided"), + (status = 429, description = "Too many requests"), + ), + tag = "event", +)] +/// Create a new event pub async fn create_event( extract::State(state): State, Json(input): Json, -) -> ApiResult { +) -> Result<(StatusCode, Json), ApiError> { let adaptor = &state.lock().await.adaptor; // Get the current timestamp @@ -55,7 +68,7 @@ pub async fn create_event( .await .map_err(ApiError::AdaptorError)?; - Ok(Json(event.into())) + Ok((StatusCode::CREATED, Json(event.into()))) } // Generate a random name based on an adjective and a crab species diff --git a/backend/src/routes/get_event.rs b/backend/src/routes/get_event.rs index 5ce0ca1..36759fa 100644 --- a/backend/src/routes/get_event.rs +++ b/backend/src/routes/get_event.rs @@ -10,6 +10,20 @@ use crate::{ State, }; +#[utoipa::path( + get, + path = "/event/{event_id}", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = EventResponse), + (status = 404, description = "Not found"), + (status = 429, description = "Too many requests"), + ), + tag = "event", +)] +/// Get details about an event pub async fn get_event( extract::State(state): State, Path(event_id): Path, diff --git a/backend/src/routes/get_people.rs b/backend/src/routes/get_people.rs index e8fb9c2..cee8f6b 100644 --- a/backend/src/routes/get_people.rs +++ b/backend/src/routes/get_people.rs @@ -10,6 +10,20 @@ use crate::{ State, }; +#[utoipa::path( + get, + path = "/event/{event_id}/people", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = [PersonResponse]), + (status = 404, description = "Event not found"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Get availabilities for an event pub async fn get_people( extract::State(state): State, Path(event_id): Path, diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/get_person.rs index 0e00df3..90c104f 100644 --- a/backend/src/routes/get_person.rs +++ b/backend/src/routes/get_person.rs @@ -10,6 +10,25 @@ use crate::{ State, }; +#[utoipa::path( + get, + path = "/event/{event_id}/people/{person_name}", + params( + ("event_id", description = "The ID of the event"), + ("person_name", description = "The name of the person"), + ), + request_body(content = GetPersonInput, description = "Person details"), + responses( + (status = 200, description = "Ok", body = PersonResponse), + (status = 401, description = "Incorrect password"), + (status = 404, description = "Event not found"), + (status = 415, description = "Unsupported input format"), + (status = 422, description = "Invalid input provided"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Login or create a person for an event pub async fn get_person( extract::State(state): State, Path((event_id, person_name)): Path<(String, String)>, diff --git a/backend/src/routes/get_stats.rs b/backend/src/routes/get_stats.rs index 46d9efc..46398ce 100644 --- a/backend/src/routes/get_stats.rs +++ b/backend/src/routes/get_stats.rs @@ -7,6 +7,16 @@ use crate::{ State, }; +#[utoipa::path( + get, + path = "/stats", + responses( + (status = 200, description = "Ok", body = StatsResponse), + (status = 429, description = "Too many requests"), + ), + tag = "info", +)] +/// Get current stats pub async fn get_stats(extract::State(state): State) -> ApiResult { let adaptor = &state.lock().await.adaptor; diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index 4881408..e9e6bed 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -1,17 +1,17 @@ -mod get_event; +pub mod get_event; pub use get_event::get_event; -mod get_stats; +pub mod get_stats; pub use get_stats::get_stats; -mod create_event; +pub mod create_event; pub use create_event::create_event; -mod get_people; +pub mod get_people; pub use get_people::get_people; -mod get_person; +pub mod get_person; pub use get_person::get_person; -mod update_person; +pub mod update_person; pub use update_person::update_person; diff --git a/backend/src/routes/update_person.rs b/backend/src/routes/update_person.rs index 99b3804..521f71b 100644 --- a/backend/src/routes/update_person.rs +++ b/backend/src/routes/update_person.rs @@ -12,6 +12,25 @@ use crate::{ use super::get_person::verify_password; +#[utoipa::path( + patch, + path = "/event/{event_id}/people/{person_name}", + params( + ("event_id", description = "The ID of the event"), + ("person_name", description = "The name of the person"), + ), + request_body(content = UpdatePersonInput, description = "Person details"), + responses( + (status = 200, description = "Ok", body = PersonResponse), + (status = 401, description = "Incorrect password"), + (status = 404, description = "Event or person not found"), + (status = 415, description = "Unsupported input format"), + (status = 422, description = "Invalid input provided"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Update a person's availabilities pub async fn update_person( extract::State(state): State, Path((event_id, person_name)): Path<(String, String)>, From 2da5ba107f942455c6e2e2ed4338cea1d2d16eba Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Sun, 14 May 2023 02:10:22 +1000 Subject: [PATCH 11/29] Refactor password auth to use Bearer token --- backend/Cargo.lock | 27 +++++++++++++++ backend/Cargo.toml | 3 +- backend/src/docs.rs | 52 +++++++++++++++++++++++++++++ backend/src/main.rs | 32 ++---------------- backend/src/payloads.rs | 8 +---- backend/src/routes/get_person.rs | 28 +++++++++++----- backend/src/routes/update_person.rs | 15 +++++---- 7 files changed, 113 insertions(+), 52 deletions(-) create mode 100644 backend/src/docs.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 4e5bcab..9222e24 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -244,6 +244,7 @@ dependencies = [ "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -599,6 +600,7 @@ name = "crabfit_backend" version = "2.0.0" dependencies = [ "axum", + "base64 0.21.0", "bcrypt", "chrono", "common", @@ -1091,6 +1093,31 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "headers" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" +dependencies = [ + "base64 0.13.1", + "bitflags", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.3.3" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 63c90b9..c943b8b 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" members = ["common", "adaptors/*"] [dependencies] -axum = "0.6.18" +axum = { version = "0.6.18", features = ["headers"] } serde = { version = "1.0.162", features = ["derive"] } tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] } common = { path = "common" } @@ -28,3 +28,4 @@ tower_governor = "0.0.4" tower = "0.4.13" utoipa = { version = "3.3.0", features = ["axum_extras", "preserve_order"] } utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] } +base64 = "0.21.0" diff --git a/backend/src/docs.rs b/backend/src/docs.rs new file mode 100644 index 0000000..372ce2a --- /dev/null +++ b/backend/src/docs.rs @@ -0,0 +1,52 @@ +use crate::payloads; +use crate::routes; + +use utoipa::{ + openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, + Modify, OpenApi, +}; + +// OpenAPI documentation +#[derive(OpenApi)] +#[openapi( + info(title = "Crab Fit API"), + paths( + routes::get_stats::get_stats, + routes::create_event::create_event, + routes::get_event::get_event, + routes::get_people::get_people, + routes::get_person::get_person, + routes::update_person::update_person, + ), + components(schemas( + payloads::StatsResponse, + payloads::EventResponse, + payloads::PersonResponse, + payloads::EventInput, + payloads::PersonInput, + )), + tags( + (name = "info"), + (name = "event"), + (name = "person"), + ), + modifiers(&SecurityAddon), +)] +pub struct ApiDoc; + +struct SecurityAddon; + +// Add password auth spec +impl Modify for SecurityAddon { + fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { + openapi.components.as_mut().unwrap().add_security_scheme( + "password", + SecurityScheme::Http( + HttpBuilder::new() + .scheme(HttpAuthScheme::Bearer) + .bearer_format("base64") + .build(), + ), + ); + } +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 8d7b5f1..30d9194 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -16,6 +16,9 @@ use tower_http::{cors::CorsLayer, trace::TraceLayer}; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; +use crate::docs::ApiDoc; + +mod docs; mod errors; mod payloads; mod routes; @@ -33,35 +36,6 @@ async fn main() { // Load env dotenv::dotenv().ok(); - #[derive(OpenApi)] - #[openapi( - info(title = "Crab Fit API"), - paths( - routes::get_stats::get_stats, - routes::create_event::create_event, - routes::get_event::get_event, - routes::get_people::get_people, - routes::get_person::get_person, - routes::update_person::update_person, - ), - components( - schemas( - payloads::StatsResponse, - payloads::EventResponse, - payloads::PersonResponse, - payloads::EventInput, - payloads::GetPersonInput, - payloads::UpdatePersonInput, - ), - ), - tags( - (name = "info"), - (name = "event"), - (name = "person"), - ), - )] - struct ApiDoc; - let shared_state = Arc::new(Mutex::new(ApiState { adaptor: SqlAdaptor::new().await, })); diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 60779e6..8ed6df2 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -70,12 +70,6 @@ impl From for PersonResponse { } #[derive(Deserialize, ToSchema)] -pub struct GetPersonInput { - pub password: Option, -} - -#[derive(Deserialize, ToSchema)] -pub struct UpdatePersonInput { - pub password: Option, +pub struct PersonInput { pub availability: Vec, } diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/get_person.rs index 90c104f..2f127e9 100644 --- a/backend/src/routes/get_person.rs +++ b/backend/src/routes/get_person.rs @@ -1,12 +1,14 @@ use axum::{ extract::{self, Path}, - Json, + headers::{authorization::Bearer, Authorization}, + Json, TypedHeader, }; +use base64::{engine::general_purpose, Engine}; use common::{adaptor::Adaptor, person::Person}; use crate::{ errors::ApiError, - payloads::{ApiResult, GetPersonInput, PersonResponse}, + payloads::{ApiResult, PersonResponse}, State, }; @@ -17,7 +19,7 @@ use crate::{ ("event_id", description = "The ID of the event"), ("person_name", description = "The name of the person"), ), - request_body(content = GetPersonInput, description = "Person details"), + security((), ("password" = [])), responses( (status = 200, description = "Ok", body = PersonResponse), (status = 401, description = "Incorrect password"), @@ -32,15 +34,12 @@ use crate::{ pub async fn get_person( extract::State(state): State, Path((event_id, person_name)): Path<(String, String)>, - input: Option>, + bearer: Option>>, ) -> ApiResult { let adaptor = &state.lock().await.adaptor; // Get inputted password - let password = match input { - Some(Json(i)) => i.password, - None => None, - }; + let password = parse_password(bearer); let existing_people = adaptor .get_people(event_id.clone()) @@ -96,9 +95,20 @@ pub async fn get_person( } } +pub fn parse_password(bearer: Option>>) -> Option { + bearer.map(|TypedHeader(Authorization(b))| { + String::from_utf8( + general_purpose::STANDARD + .decode(b.token().trim()) + .unwrap_or(vec![]), + ) + .unwrap_or("".to_owned()) + }) +} + pub fn verify_password(person: &Person, raw: Option) -> bool { match &person.password_hash { - Some(hash) => bcrypt::verify(raw.unwrap_or(String::from("")), hash).unwrap_or(false), + Some(hash) => bcrypt::verify(raw.unwrap_or("".to_owned()), hash).unwrap_or(false), // Specifically allow a user who doesn't have a password // set to log in with or without any password input None => true, diff --git a/backend/src/routes/update_person.rs b/backend/src/routes/update_person.rs index 521f71b..8bb45e3 100644 --- a/backend/src/routes/update_person.rs +++ b/backend/src/routes/update_person.rs @@ -1,16 +1,17 @@ use axum::{ extract::{self, Path}, - Json, + headers::{authorization::Bearer, Authorization}, + Json, TypedHeader, }; use common::{adaptor::Adaptor, person::Person}; use crate::{ errors::ApiError, - payloads::{ApiResult, PersonResponse, UpdatePersonInput}, + payloads::{ApiResult, PersonInput, PersonResponse}, State, }; -use super::get_person::verify_password; +use super::get_person::{parse_password, verify_password}; #[utoipa::path( patch, @@ -19,7 +20,8 @@ use super::get_person::verify_password; ("event_id", description = "The ID of the event"), ("person_name", description = "The name of the person"), ), - request_body(content = UpdatePersonInput, description = "Person details"), + security((), ("password" = [])), + request_body(content = PersonInput, description = "Person details"), responses( (status = 200, description = "Ok", body = PersonResponse), (status = 401, description = "Incorrect password"), @@ -34,7 +36,8 @@ use super::get_person::verify_password; pub async fn update_person( extract::State(state): State, Path((event_id, person_name)): Path<(String, String)>, - Json(input): Json, + bearer: Option>>, + Json(input): Json, ) -> ApiResult { let adaptor = &state.lock().await.adaptor; @@ -56,7 +59,7 @@ pub async fn update_person( .ok_or(ApiError::NotFound)?; // Verify password (if set) - if !verify_password(&existing_person, input.password) { + if !verify_password(&existing_person, parse_password(bearer)) { return Err(ApiError::NotAuthorized); } From 4abb538db77155cebbfa3db6c38ad3533abf9908 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 15:28:17 +1000 Subject: [PATCH 12/29] Create Google datastore adaptor --- backend/Cargo.lock | 412 +++++++++++++++++++++++++- backend/adaptors/datastore/Cargo.toml | 13 + backend/adaptors/datastore/src/lib.rs | 193 ++++++++++++ backend/adaptors/sql/src/lib.rs | 12 +- backend/common/src/adaptor.rs | 4 +- backend/common/src/stats.rs | 4 +- backend/src/payloads.rs | 4 +- 7 files changed, 621 insertions(+), 21 deletions(-) create mode 100644 backend/adaptors/datastore/Cargo.toml create mode 100644 backend/adaptors/datastore/src/lib.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 9222e24..03d0484 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -60,6 +60,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" + [[package]] name = "arrayvec" version = "0.7.2" @@ -296,6 +302,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "base64" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" + [[package]] name = "base64" version = "0.13.1" @@ -333,7 +345,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa" dependencies = [ - "num-bigint", + "num-bigint 0.4.3", "num-integer", "num-traits", ] @@ -669,6 +681,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "ct-logs" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1a816186fa68d9e426e3cb4ae4dff1fcd8e4a2c34b781bf7a822574a0d0aac8" +dependencies = [ + "sct", +] + [[package]] name = "ctor" version = "0.1.26" @@ -736,6 +757,19 @@ dependencies = [ "parking_lot_core 0.9.7", ] +[[package]] +name = "datastore-adaptor" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "common", + "google-cloud", + "serde", + "serde_json", + "tokio", +] + [[package]] name = "der" version = "0.5.1" @@ -832,6 +866,12 @@ dependencies = [ "instant", ] +[[package]] +name = "fixedbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" + [[package]] name = "flate2" version = "1.0.26" @@ -1048,6 +1088,28 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "google-cloud" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a517f0235af652d334a021b81aa2e8f18a77512c26be18722debb7d405912f80" +dependencies = [ + "chrono", + "futures", + "http", + "hyper", + "hyper-rustls", + "jsonwebtoken", + "prost", + "prost-types", + "serde", + "serde_json", + "thiserror", + "tokio", + "tonic", + "tonic-build", +] + [[package]] name = "governor" version = "0.5.1" @@ -1066,6 +1128,25 @@ dependencies = [ "smallvec", ] +[[package]] +name = "h2" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.8", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1225,6 +1306,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1238,6 +1320,23 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64" +dependencies = [ + "ct-logs", + "futures-util", + "hyper", + "log", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "webpki", +] + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -1312,6 +1411,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itertools" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.10.5" @@ -1336,6 +1444,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" +dependencies = [ + "base64 0.12.3", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1494,6 +1616,12 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "multimap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" + [[package]] name = "native-tls" version = "0.2.11" @@ -1550,6 +1678,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -1764,6 +1903,17 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pem" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +dependencies = [ + "base64 0.13.1", + "once_cell", + "regex", +] + [[package]] name = "pem-rfc7468" version = "0.3.1" @@ -1779,6 +1929,16 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +[[package]] +name = "petgraph" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "pin-project" version = "1.0.12" @@ -1903,6 +2063,57 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e6984d2f1a23009bd270b8bb56d0926810a3d483f59c987d77969e9d8e840b2" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d3ebd75ac2679c2af3a92246639f9fcc8a442ee420719cc4fe195b98dd5fa3" +dependencies = [ + "bytes", + "heck 0.3.3", + "itertools 0.9.0", + "log", + "multimap", + "petgraph", + "prost", + "prost-types", + "tempfile", + "which", +] + +[[package]] +name = "prost-derive" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "169a15f3008ecb5160cba7d37bcd690a7601b6d30cfb87a117d45e59d52af5d4" +dependencies = [ + "anyhow", + "itertools 0.9.0", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "prost-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b518d7cdd93dab1d1122cf07fa9a60771836c668dde9d9e2a139f957f0d9f1bb" +dependencies = [ + "bytes", + "prost", +] + [[package]] name = "ptr_meta" version = "0.1.4" @@ -2063,6 +2274,21 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin 0.5.2", + "untrusted", + "web-sys", + "winapi", +] + [[package]] name = "rkyv" version = "0.7.41" @@ -2175,6 +2401,31 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustls" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" +dependencies = [ + "base64 0.13.1", + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-native-certs" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a07b7c1885bd8ed3831c289b7870b13ef46fe0e856d288c30d9cc17d75a2092" +dependencies = [ + "openssl-probe", + "rustls", + "schannel", + "security-framework", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -2217,6 +2468,16 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "sct" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "sea-orm" version = "0.11.3" @@ -2411,18 +2672,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.162" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" +checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.162" +version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" +checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ "proc-macro2", "quote", @@ -2516,6 +2777,17 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "simple_asn1" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +dependencies = [ + "chrono", + "num-bigint 0.2.6", + "num-traits", +] + [[package]] name = "slab" version = "0.4.8" @@ -2586,7 +2858,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" dependencies = [ - "itertools", + "itertools 0.10.5", "nom", "unicode_categories", ] @@ -2639,7 +2911,7 @@ dependencies = [ "log", "md-5", "memchr", - "num-bigint", + "num-bigint 0.4.3", "once_cell", "paste", "percent-encoding", @@ -2850,9 +3122,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.0" +version = "1.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" dependencies = [ "autocfg", "bytes", @@ -2888,6 +3160,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-stream" version = "0.1.14" @@ -2899,6 +3182,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + [[package]] name = "toml" version = "0.5.11" @@ -2908,6 +3219,48 @@ dependencies = [ "serde", ] +[[package]] +name = "tonic" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac42cd97ac6bd2339af5bcabf105540e21e45636ec6fa6aae5e85d44db31be0" +dependencies = [ + "async-stream", + "async-trait", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "percent-encoding", + "pin-project", + "prost", + "prost-derive", + "tokio", + "tokio-rustls", + "tokio-stream", + "tokio-util 0.6.10", + "tower", + "tower-service", + "tracing", + "tracing-futures", +] + +[[package]] +name = "tonic-build" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c695de27302f4697191dda1c7178131a8cb805463dda02864acb80fe1322fdcf" +dependencies = [ + "proc-macro2", + "prost-build", + "quote", + "syn 1.0.109", +] + [[package]] name = "tower" version = "0.4.13" @@ -2916,9 +3269,13 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", + "indexmap", "pin-project", "pin-project-lite", + "rand", + "slab", "tokio", + "tokio-util 0.7.8", "tower-layer", "tower-service", "tracing", @@ -3009,6 +3366,16 @@ dependencies = [ "valuable", ] +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + [[package]] name = "tracing-log" version = "0.1.3" @@ -3098,6 +3465,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -3300,6 +3673,27 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki" +version = "0.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "which" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" +dependencies = [ + "either", + "libc", + "once_cell", +] + [[package]] name = "whoami" version = "1.4.0" diff --git a/backend/adaptors/datastore/Cargo.toml b/backend/adaptors/datastore/Cargo.toml new file mode 100644 index 0000000..f9a69ac --- /dev/null +++ b/backend/adaptors/datastore/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "datastore-adaptor" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.68" +chrono = "0.4.24" +common = { path = "../../common" } +google-cloud = { version = "0.2.1", features = ["datastore"] } +serde = "1.0.163" +serde_json = "1.0.96" +tokio = { version = "1.28.1", features = ["rt-multi-thread"] } diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs new file mode 100644 index 0000000..efbb496 --- /dev/null +++ b/backend/adaptors/datastore/src/lib.rs @@ -0,0 +1,193 @@ +use std::{collections::HashMap, env, error::Error, fmt::Display}; + +use async_trait::async_trait; +use chrono::{DateTime, NaiveDateTime, Utc}; +use common::{ + adaptor::Adaptor, + event::{Event, EventDeletion}, + person::Person, + stats::Stats, +}; +use google_cloud::{ + authorize::ApplicationCredentials, + datastore::{Client, Filter, FromValue, IntoValue, Key, Query, Value}, + error::ConvertError, +}; +use tokio::sync::Mutex; + +pub struct DatastoreAdaptor { + client: Mutex, +} + +// Keys +const STATS_KIND: &str = "Stats"; +const EVENT_KIND: &str = "Event"; +const PERSON_KIND: &str = "Person"; +const STATS_EVENTS_ID: &str = "eventCount"; +const STATS_PEOPLE_ID: &str = "personCount"; + +#[async_trait] +impl Adaptor for DatastoreAdaptor { + type Error = DatastoreAdaptorError; + + async fn get_stats(&self) -> Result { + let mut client = self.client.lock().await; + + let key = Key::new(STATS_KIND); + let event_count = client + .get(key.clone().id(STATS_EVENTS_ID)) + .await? + .unwrap_or(0); + let person_count = client.get(key.id(STATS_PEOPLE_ID)).await?.unwrap_or(0); + + Ok(Stats { + event_count, + person_count, + }) + } + + async fn increment_stat_event_count(&self) -> Result { + let mut client = self.client.lock().await; + + let key = Key::new(STATS_KIND).id(STATS_EVENTS_ID); + let event_count = client.get(key.clone()).await?.unwrap_or(0) + 1; + client.put((key, event_count)).await?; + Ok(event_count) + } + + async fn increment_stat_person_count(&self) -> Result { + let mut client = self.client.lock().await; + + let key = Key::new(STATS_KIND).id(STATS_PEOPLE_ID); + let person_count = client.get(key.clone()).await?.unwrap_or(0) + 1; + client.put((key, person_count)).await?; + Ok(person_count) + } + + async fn get_people(&self, event_id: String) -> Result>, Self::Error> { + let mut client = self.client.lock().await; + + // Check the event exists + if client + .get::(Key::new(EVENT_KIND).id(event_id.clone())) + .await? + .is_none() + { + return Ok(None); + } + + Ok(Some( + client + .query( + Query::new(PERSON_KIND) + .filter(Filter::Equal("eventId".into(), event_id.into_value())), + ) + .await? + .into_iter() + .filter_map(|entity| parse_into_person(entity.properties().clone()).ok()) + .collect(), + )) + } + + async fn upsert_person(&self, event_id: String, person: Person) -> Result { + let mut client = self.client.lock().await; + todo!() + } + + async fn get_event(&self, id: String) -> Result, Self::Error> { + let mut client = self.client.lock().await; + todo!() + } + + async fn create_event(&self, event: Event) -> Result { + let mut client = self.client.lock().await; + todo!() + } + + async fn delete_event(&self, id: String) -> Result { + let mut client = self.client.lock().await; + todo!() + } +} + +impl DatastoreAdaptor { + pub async fn new() -> Self { + let project_name = env::var("GCP_PROJECT_NAME").unwrap(); + + // Load credentials + let credentials: ApplicationCredentials = + serde_json::from_str(&env::var("GCP_CREDENTIALS").unwrap()).unwrap(); + + // Connect to datastore + let client = Client::from_credentials(project_name.clone(), credentials) + .await + .unwrap(); + let client = Mutex::new(client); + + println!("🎛️ Connected to datastore in project {}", project_name); + + Self { client } + } +} + +fn parse_into_person(value: Value) -> Result { + let person: HashMap = HashMap::from_value(value)?; + Ok(Person { + name: String::from_value( + person + .get("name") + .ok_or(ConvertError::MissingProperty("name".to_owned()))? + .clone(), + )?, + password_hash: person + .get("password") + .map(|p| String::from_value(p.clone())) + .transpose()?, + created_at: DateTime::from_utc( + NaiveDateTime::from_timestamp_opt( + i64::from_value( + person + .get("created") + .ok_or(ConvertError::MissingProperty("created".to_owned()))? + .clone(), + )?, + 0, + ) + .unwrap(), + Utc, + ), + availability: Vec::from_value( + person + .get("availability") + .ok_or(ConvertError::MissingProperty("availability".to_owned()))? + .clone(), + )?, + }) +} + +#[derive(Debug)] +pub enum DatastoreAdaptorError { + DatastoreError(google_cloud::error::Error), +} + +impl Display for DatastoreAdaptorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + DatastoreAdaptorError::DatastoreError(e) => write!(f, "Datastore Error: {}", e), + } + } +} + +impl Error for DatastoreAdaptorError {} + +impl From for DatastoreAdaptorError { + fn from(value: google_cloud::error::Error) -> Self { + Self::DatastoreError(value) + } +} + +impl From for DatastoreAdaptorError { + fn from(value: google_cloud::error::ConvertError) -> Self { + Self::DatastoreError(google_cloud::error::Error::Convert(value)) + } +} diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index 0a9d017..4ed8cb9 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -33,23 +33,23 @@ impl Adaptor for SqlAdaptor { async fn get_stats(&self) -> Result { let stats_row = get_stats_row(&self.db).await?; Ok(Stats { - event_count: stats_row.event_count.unwrap(), - person_count: stats_row.person_count.unwrap(), + event_count: stats_row.event_count.unwrap() as i64, + person_count: stats_row.person_count.unwrap() as i64, }) } - async fn increment_stat_event_count(&self) -> Result { + async fn increment_stat_event_count(&self) -> Result { let mut current_stats = get_stats_row(&self.db).await?; current_stats.event_count = Set(current_stats.event_count.unwrap() + 1); - Ok(current_stats.save(&self.db).await?.event_count.unwrap()) + Ok(current_stats.save(&self.db).await?.event_count.unwrap() as i64) } - async fn increment_stat_person_count(&self) -> Result { + async fn increment_stat_person_count(&self) -> Result { let mut current_stats = get_stats_row(&self.db).await?; current_stats.person_count = Set(current_stats.person_count.unwrap() + 1); - Ok(current_stats.save(&self.db).await?.person_count.unwrap()) + Ok(current_stats.save(&self.db).await?.person_count.unwrap() as i64) } async fn get_people(&self, event_id: String) -> Result>, Self::Error> { diff --git a/backend/common/src/adaptor.rs b/backend/common/src/adaptor.rs index 55f3544..b6e0350 100644 --- a/backend/common/src/adaptor.rs +++ b/backend/common/src/adaptor.rs @@ -15,8 +15,8 @@ pub trait Adaptor: Send + Sync { type Error: Error; async fn get_stats(&self) -> Result; - async fn increment_stat_event_count(&self) -> Result; - async fn increment_stat_person_count(&self) -> Result; + async fn increment_stat_event_count(&self) -> Result; + async fn increment_stat_person_count(&self) -> Result; async fn get_people(&self, event_id: String) -> Result>, Self::Error>; async fn upsert_person(&self, event_id: String, person: Person) -> Result; diff --git a/backend/common/src/stats.rs b/backend/common/src/stats.rs index 3f4843d..7bb9ec2 100644 --- a/backend/common/src/stats.rs +++ b/backend/common/src/stats.rs @@ -1,4 +1,4 @@ pub struct Stats { - pub event_count: i32, - pub person_count: i32, + pub event_count: i64, + pub person_count: i64, } diff --git a/backend/src/payloads.rs b/backend/src/payloads.rs index 8ed6df2..089a2b8 100644 --- a/backend/src/payloads.rs +++ b/backend/src/payloads.rs @@ -37,8 +37,8 @@ impl From for EventResponse { #[derive(Serialize, ToSchema)] pub struct StatsResponse { - pub event_count: i32, - pub person_count: i32, + pub event_count: i64, + pub person_count: i64, pub version: String, } From 9157308398503af2b96ce0d7210c0bb09f9f5add Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 16:30:08 +1000 Subject: [PATCH 13/29] Implement remaining methods --- backend/adaptors/datastore/src/lib.rs | 141 +++++++++++++++++++++++++- backend/adaptors/sql/src/lib.rs | 1 + 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs index efbb496..b2e4d8b 100644 --- a/backend/adaptors/datastore/src/lib.rs +++ b/backend/adaptors/datastore/src/lib.rs @@ -91,22 +91,104 @@ impl Adaptor for DatastoreAdaptor { async fn upsert_person(&self, event_id: String, person: Person) -> Result { let mut client = self.client.lock().await; - todo!() + + // Check if person exists + let existing_person = client + .query( + Query::new(PERSON_KIND) + .filter(Filter::Equal( + "eventId".into(), + event_id.clone().into_value(), + )) + .filter(Filter::Equal( + "name".into(), + person.name.clone().into_value(), + )), + ) + .await?; + + let mut key = Key::new(PERSON_KIND); + if let Some(entity) = existing_person.first() { + key = entity.key().clone(); + } + + let mut properties = HashMap::new(); + properties.insert(String::from("name"), person.name.clone().into_value()); + if let Some(password_hash) = person.password_hash.clone() { + properties.insert(String::from("password"), password_hash.into_value()); + } + properties.insert(String::from("eventId"), event_id.into_value()); + properties.insert( + String::from("created"), + person.created_at.clone().timestamp().into_value(), + ); + properties.insert( + String::from("availability"), + person.availability.clone().into_value(), + ); + + client.put((key, properties)).await?; + + Ok(person) } async fn get_event(&self, id: String) -> Result, Self::Error> { let mut client = self.client.lock().await; - todo!() + + // TODO: mark as visited + + Ok(client + .get::(Key::new(EVENT_KIND).id(id.clone())) + .await? + .map(|value| parse_into_event(id, value)) + .transpose()?) } async fn create_event(&self, event: Event) -> Result { let mut client = self.client.lock().await; - todo!() + + let key = Key::new(EVENT_KIND).id(event.id.clone()); + + let mut properties = HashMap::new(); + properties.insert(String::from("name"), event.name.clone().into_value()); + properties.insert( + String::from("created"), + event.created_at.clone().timestamp().into_value(), + ); + properties.insert( + String::from("visited"), + event.visited_at.clone().timestamp().into_value(), + ); + properties.insert(String::from("times"), event.times.clone().into_value()); + properties.insert( + String::from("timezone"), + event.timezone.clone().into_value(), + ); + + client.put((key, properties)).await?; + + Ok(event) } async fn delete_event(&self, id: String) -> Result { let mut client = self.client.lock().await; - todo!() + + let mut people_keys: Vec = client + .query( + Query::new(PERSON_KIND) + .filter(Filter::Equal("eventId".into(), id.clone().into_value())), + ) + .await? + .iter() + .map(|entity| entity.key().clone()) + .collect(); + + let person_count = people_keys.len().try_into().unwrap(); + people_keys.insert(0, Key::new(EVENT_KIND).id(id.clone())); + + client.delete_all(people_keys).await?; + + Ok(EventDeletion { id, person_count }) } } @@ -165,6 +247,57 @@ fn parse_into_person(value: Value) -> Result { }) } +fn parse_into_event(id: String, value: Value) -> Result { + let event: HashMap = HashMap::from_value(value)?; + Ok(Event { + id, + name: String::from_value( + event + .get("name") + .ok_or(ConvertError::MissingProperty("name".to_owned()))? + .clone(), + )?, + created_at: DateTime::from_utc( + NaiveDateTime::from_timestamp_opt( + i64::from_value( + event + .get("created") + .ok_or(ConvertError::MissingProperty("created".to_owned()))? + .clone(), + )?, + 0, + ) + .unwrap(), + Utc, + ), + visited_at: DateTime::from_utc( + NaiveDateTime::from_timestamp_opt( + i64::from_value( + event + .get("visited") + .ok_or(ConvertError::MissingProperty("visited".to_owned()))? + .clone(), + )?, + 0, + ) + .unwrap(), + Utc, + ), + times: Vec::from_value( + event + .get("times") + .ok_or(ConvertError::MissingProperty("times".to_owned()))? + .clone(), + )?, + timezone: String::from_value( + event + .get("timezone") + .ok_or(ConvertError::MissingProperty("timezone".to_owned()))? + .clone(), + )?, + }) +} + #[derive(Debug)] pub enum DatastoreAdaptorError { DatastoreError(google_cloud::error::Error), diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index 4ed8cb9..147ba27 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -91,6 +91,7 @@ impl Adaptor for SqlAdaptor { } async fn get_event(&self, id: String) -> Result, Self::Error> { + // TODO: mark as visited Ok(event::Entity::find_by_id(id) .one(&self.db) .await? From aa3b323cb697fa5e04212e9288bb4026057073e1 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 16:48:00 +1000 Subject: [PATCH 14/29] Update visited date when event is fetched --- backend/adaptors/datastore/src/lib.rs | 17 +++++++++++++---- backend/adaptors/sql/src/lib.rs | 15 ++++++++++----- backend/common/src/adaptor.rs | 1 + 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs index b2e4d8b..d5bbc7c 100644 --- a/backend/adaptors/datastore/src/lib.rs +++ b/backend/adaptors/datastore/src/lib.rs @@ -135,11 +135,20 @@ impl Adaptor for DatastoreAdaptor { async fn get_event(&self, id: String) -> Result, Self::Error> { let mut client = self.client.lock().await; - // TODO: mark as visited + let key = Key::new(EVENT_KIND).id(id.clone()); + let existing_event = client.get::(key.clone()).await?; - Ok(client - .get::(Key::new(EVENT_KIND).id(id.clone())) - .await? + // Mark as visited if it exists + if let Some(mut event) = existing_event + .clone() + .map(HashMap::::from_value) + .transpose()? + { + event.insert(String::from("visited"), Utc::now().timestamp().into_value()); + client.put((key, event)).await?; + } + + Ok(existing_event .map(|value| parse_into_event(id, value)) .transpose()?) } diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index 147ba27..7d691dc 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -91,11 +91,16 @@ impl Adaptor for SqlAdaptor { } async fn get_event(&self, id: String) -> Result, Self::Error> { - // TODO: mark as visited - Ok(event::Entity::find_by_id(id) - .one(&self.db) - .await? - .map(|model| model.into())) + let existing_event = event::Entity::find_by_id(id).one(&self.db).await?; + + // Mark as visited + if let Some(event) = existing_event.clone() { + let mut event: event::ActiveModel = event.into(); + event.visited_at = Set(Utc::now().naive_utc()); + event.save(&self.db).await?; + } + + Ok(existing_event.map(|model| model.into())) } async fn create_event(&self, event: Event) -> Result { diff --git a/backend/common/src/adaptor.rs b/backend/common/src/adaptor.rs index b6e0350..73da286 100644 --- a/backend/common/src/adaptor.rs +++ b/backend/common/src/adaptor.rs @@ -21,6 +21,7 @@ pub trait Adaptor: Send + Sync { async fn get_people(&self, event_id: String) -> Result>, Self::Error>; async fn upsert_person(&self, event_id: String, person: Person) -> Result; + /// Get an event and update visited date to current time async fn get_event(&self, id: String) -> Result, Self::Error>; async fn create_event(&self, event: Event) -> Result; From bf5bcf99924a5c74e0893f0684a1270402ff9fb0 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 16:57:13 +1000 Subject: [PATCH 15/29] Co-locate related routes --- backend/src/docs.rs | 12 +-- backend/src/main.rs | 18 ++-- .../src/routes/{create_event.rs => event.rs} | 39 ++++++- backend/src/routes/get_event.rs | 42 -------- backend/src/routes/get_people.rs | 42 -------- backend/src/routes/mod.rs | 20 +--- .../src/routes/{get_person.rs => person.rs} | 100 +++++++++++++++++- backend/src/routes/{get_stats.rs => stats.rs} | 0 backend/src/routes/update_person.rs | 81 -------------- 9 files changed, 157 insertions(+), 197 deletions(-) rename backend/src/routes/{create_event.rs => event.rs} (75%) delete mode 100644 backend/src/routes/get_event.rs delete mode 100644 backend/src/routes/get_people.rs rename backend/src/routes/{get_person.rs => person.rs} (53%) rename backend/src/routes/{get_stats.rs => stats.rs} (100%) delete mode 100644 backend/src/routes/update_person.rs diff --git a/backend/src/docs.rs b/backend/src/docs.rs index 372ce2a..2ab310d 100644 --- a/backend/src/docs.rs +++ b/backend/src/docs.rs @@ -11,12 +11,12 @@ use utoipa::{ #[openapi( info(title = "Crab Fit API"), paths( - routes::get_stats::get_stats, - routes::create_event::create_event, - routes::get_event::get_event, - routes::get_people::get_people, - routes::get_person::get_person, - routes::update_person::update_person, + routes::stats::get_stats, + routes::event::create_event, + routes::event::get_event, + routes::person::get_people, + routes::person::get_person, + routes::person::update_person, ), components(schemas( payloads::StatsResponse, diff --git a/backend/src/main.rs b/backend/src/main.rs index 30d9194..10f4938 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -69,12 +69,18 @@ async fn main() { let app = Router::new() .merge(SwaggerUi::new("/docs").url("/docs/openapi.json", ApiDoc::openapi())) .route("/", get(get_root)) - .route("/stats", get(get_stats)) - .route("/event", post(create_event)) - .route("/event/:event_id", get(get_event)) - .route("/event/:event_id/people", get(get_people)) - .route("/event/:event_id/people/:person_name", get(get_person)) - .route("/event/:event_id/people/:person_name", patch(update_person)) + .route("/stats", get(stats::get_stats)) + .route("/event", post(event::create_event)) + .route("/event/:event_id", get(event::get_event)) + .route("/event/:event_id/people", get(person::get_people)) + .route( + "/event/:event_id/people/:person_name", + get(person::get_person), + ) + .route( + "/event/:event_id/people/:person_name", + patch(person::update_person), + ) .with_state(shared_state) .layer(cors) .layer(rate_limit) diff --git a/backend/src/routes/create_event.rs b/backend/src/routes/event.rs similarity index 75% rename from backend/src/routes/create_event.rs rename to backend/src/routes/event.rs index 0de6a4b..8f7945c 100644 --- a/backend/src/routes/create_event.rs +++ b/backend/src/routes/event.rs @@ -1,14 +1,49 @@ -use axum::{extract, http::StatusCode, Json}; +use axum::{ + extract::{self, Path}, + http::StatusCode, + Json, +}; use common::{adaptor::Adaptor, event::Event}; use rand::{seq::SliceRandom, thread_rng, Rng}; use regex::Regex; use crate::{ errors::ApiError, - payloads::{EventInput, EventResponse}, + payloads::{ApiResult, EventInput, EventResponse}, State, }; +#[utoipa::path( + get, + path = "/event/{event_id}", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = EventResponse), + (status = 404, description = "Not found"), + (status = 429, description = "Too many requests"), + ), + tag = "event", +)] +/// Get details about an event +pub async fn get_event( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let event = adaptor + .get_event(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match event { + Some(event) => Ok(Json(event.into())), + None => Err(ApiError::NotFound), + } +} + #[utoipa::path( post, path = "/event", diff --git a/backend/src/routes/get_event.rs b/backend/src/routes/get_event.rs deleted file mode 100644 index 36759fa..0000000 --- a/backend/src/routes/get_event.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::{ - extract::{self, Path}, - Json, -}; -use common::adaptor::Adaptor; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, EventResponse}, - State, -}; - -#[utoipa::path( - get, - path = "/event/{event_id}", - params( - ("event_id", description = "The ID of the event"), - ), - responses( - (status = 200, description = "Ok", body = EventResponse), - (status = 404, description = "Not found"), - (status = 429, description = "Too many requests"), - ), - tag = "event", -)] -/// Get details about an event -pub async fn get_event( - extract::State(state): State, - Path(event_id): Path, -) -> ApiResult { - let adaptor = &state.lock().await.adaptor; - - let event = adaptor - .get_event(event_id) - .await - .map_err(ApiError::AdaptorError)?; - - match event { - Some(event) => Ok(Json(event.into())), - None => Err(ApiError::NotFound), - } -} diff --git a/backend/src/routes/get_people.rs b/backend/src/routes/get_people.rs deleted file mode 100644 index cee8f6b..0000000 --- a/backend/src/routes/get_people.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::{ - extract::{self, Path}, - Json, -}; -use common::adaptor::Adaptor; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, PersonResponse}, - State, -}; - -#[utoipa::path( - get, - path = "/event/{event_id}/people", - params( - ("event_id", description = "The ID of the event"), - ), - responses( - (status = 200, description = "Ok", body = [PersonResponse]), - (status = 404, description = "Event not found"), - (status = 429, description = "Too many requests"), - ), - tag = "person", -)] -/// Get availabilities for an event -pub async fn get_people( - extract::State(state): State, - Path(event_id): Path, -) -> ApiResult, A> { - let adaptor = &state.lock().await.adaptor; - - let people = adaptor - .get_people(event_id) - .await - .map_err(ApiError::AdaptorError)?; - - match people { - Some(people) => Ok(Json(people.into_iter().map(|p| p.into()).collect())), - None => Err(ApiError::NotFound), - } -} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs index e9e6bed..5a4889d 100644 --- a/backend/src/routes/mod.rs +++ b/backend/src/routes/mod.rs @@ -1,17 +1,3 @@ -pub mod get_event; -pub use get_event::get_event; - -pub mod get_stats; -pub use get_stats::get_stats; - -pub mod create_event; -pub use create_event::create_event; - -pub mod get_people; -pub use get_people::get_people; - -pub mod get_person; -pub use get_person::get_person; - -pub mod update_person; -pub use update_person::update_person; +pub mod event; +pub mod person; +pub mod stats; diff --git a/backend/src/routes/get_person.rs b/backend/src/routes/person.rs similarity index 53% rename from backend/src/routes/get_person.rs rename to backend/src/routes/person.rs index 2f127e9..ec249d7 100644 --- a/backend/src/routes/get_person.rs +++ b/backend/src/routes/person.rs @@ -8,10 +8,41 @@ use common::{adaptor::Adaptor, person::Person}; use crate::{ errors::ApiError, - payloads::{ApiResult, PersonResponse}, + payloads::{ApiResult, PersonInput, PersonResponse}, State, }; +#[utoipa::path( + get, + path = "/event/{event_id}/people", + params( + ("event_id", description = "The ID of the event"), + ), + responses( + (status = 200, description = "Ok", body = [PersonResponse]), + (status = 404, description = "Event not found"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Get availabilities for an event +pub async fn get_people( + extract::State(state): State, + Path(event_id): Path, +) -> ApiResult, A> { + let adaptor = &state.lock().await.adaptor; + + let people = adaptor + .get_people(event_id) + .await + .map_err(ApiError::AdaptorError)?; + + match people { + Some(people) => Ok(Json(people.into_iter().map(|p| p.into()).collect())), + None => Err(ApiError::NotFound), + } +} + #[utoipa::path( get, path = "/event/{event_id}/people/{person_name}", @@ -95,6 +126,73 @@ pub async fn get_person( } } +#[utoipa::path( + patch, + path = "/event/{event_id}/people/{person_name}", + params( + ("event_id", description = "The ID of the event"), + ("person_name", description = "The name of the person"), + ), + security((), ("password" = [])), + request_body(content = PersonInput, description = "Person details"), + responses( + (status = 200, description = "Ok", body = PersonResponse), + (status = 401, description = "Incorrect password"), + (status = 404, description = "Event or person not found"), + (status = 415, description = "Unsupported input format"), + (status = 422, description = "Invalid input provided"), + (status = 429, description = "Too many requests"), + ), + tag = "person", +)] +/// Update a person's availabilities +pub async fn update_person( + extract::State(state): State, + Path((event_id, person_name)): Path<(String, String)>, + bearer: Option>>, + Json(input): Json, +) -> ApiResult { + let adaptor = &state.lock().await.adaptor; + + let existing_people = adaptor + .get_people(event_id.clone()) + .await + .map_err(ApiError::AdaptorError)?; + + // Event not found + if existing_people.is_none() { + return Err(ApiError::NotFound); + } + + // Check if the user exists + let existing_person = existing_people + .unwrap() + .into_iter() + .find(|p| p.name == person_name) + .ok_or(ApiError::NotFound)?; + + // Verify password (if set) + if !verify_password(&existing_person, parse_password(bearer)) { + return Err(ApiError::NotAuthorized); + } + + Ok(Json( + adaptor + .upsert_person( + event_id, + Person { + name: existing_person.name, + password_hash: existing_person.password_hash, + created_at: existing_person.created_at, + availability: input.availability, + }, + ) + .await + .map_err(ApiError::AdaptorError)? + .into(), + )) +} + pub fn parse_password(bearer: Option>>) -> Option { bearer.map(|TypedHeader(Authorization(b))| { String::from_utf8( diff --git a/backend/src/routes/get_stats.rs b/backend/src/routes/stats.rs similarity index 100% rename from backend/src/routes/get_stats.rs rename to backend/src/routes/stats.rs diff --git a/backend/src/routes/update_person.rs b/backend/src/routes/update_person.rs deleted file mode 100644 index 8bb45e3..0000000 --- a/backend/src/routes/update_person.rs +++ /dev/null @@ -1,81 +0,0 @@ -use axum::{ - extract::{self, Path}, - headers::{authorization::Bearer, Authorization}, - Json, TypedHeader, -}; -use common::{adaptor::Adaptor, person::Person}; - -use crate::{ - errors::ApiError, - payloads::{ApiResult, PersonInput, PersonResponse}, - State, -}; - -use super::get_person::{parse_password, verify_password}; - -#[utoipa::path( - patch, - path = "/event/{event_id}/people/{person_name}", - params( - ("event_id", description = "The ID of the event"), - ("person_name", description = "The name of the person"), - ), - security((), ("password" = [])), - request_body(content = PersonInput, description = "Person details"), - responses( - (status = 200, description = "Ok", body = PersonResponse), - (status = 401, description = "Incorrect password"), - (status = 404, description = "Event or person not found"), - (status = 415, description = "Unsupported input format"), - (status = 422, description = "Invalid input provided"), - (status = 429, description = "Too many requests"), - ), - tag = "person", -)] -/// Update a person's availabilities -pub async fn update_person( - extract::State(state): State, - Path((event_id, person_name)): Path<(String, String)>, - bearer: Option>>, - Json(input): Json, -) -> ApiResult { - let adaptor = &state.lock().await.adaptor; - - let existing_people = adaptor - .get_people(event_id.clone()) - .await - .map_err(ApiError::AdaptorError)?; - - // Event not found - if existing_people.is_none() { - return Err(ApiError::NotFound); - } - - // Check if the user exists - let existing_person = existing_people - .unwrap() - .into_iter() - .find(|p| p.name == person_name) - .ok_or(ApiError::NotFound)?; - - // Verify password (if set) - if !verify_password(&existing_person, parse_password(bearer)) { - return Err(ApiError::NotAuthorized); - } - - Ok(Json( - adaptor - .upsert_person( - event_id, - Person { - name: existing_person.name, - password_hash: existing_person.password_hash, - created_at: existing_person.created_at, - availability: input.availability, - }, - ) - .await - .map_err(ApiError::AdaptorError)? - .into(), - )) -} From 1a8db405dea97fabe44bd79f355826b4e20522e9 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 18:18:26 +1000 Subject: [PATCH 16/29] Add an in-memory storage adaptor --- backend/Cargo.lock | 12 +++ backend/Cargo.toml | 2 + backend/adaptors/memory/Cargo.toml | 10 ++ backend/adaptors/memory/src/lib.rs | 146 +++++++++++++++++++++++++++++ backend/common/src/event.rs | 2 + backend/common/src/person.rs | 1 + backend/common/src/stats.rs | 1 + backend/src/adaptors.rs | 13 +++ backend/src/main.rs | 5 +- 9 files changed, 190 insertions(+), 2 deletions(-) create mode 100644 backend/adaptors/memory/Cargo.toml create mode 100644 backend/adaptors/memory/src/lib.rs create mode 100644 backend/src/adaptors.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 03d0484..025bf58 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -616,7 +616,9 @@ dependencies = [ "bcrypt", "chrono", "common", + "datastore-adaptor", "dotenv", + "memory-adaptor", "punycode", "rand", "regex", @@ -1573,6 +1575,16 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memory-adaptor" +version = "0.1.0" +dependencies = [ + "async-trait", + "chrono", + "common", + "tokio", +] + [[package]] name = "mime" version = "0.3.17" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index c943b8b..e0da7b4 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,6 +14,8 @@ serde = { version = "1.0.162", features = ["derive"] } tokio = { version = "1.28.0", features = ["macros", "rt-multi-thread"] } common = { path = "common" } sql-adaptor = { path = "adaptors/sql" } +datastore-adaptor = { path = "adaptors/datastore" } +memory-adaptor = { path = "adaptors/memory" } dotenv = "0.15.0" serde_json = "1.0.96" rand = "0.8.5" diff --git a/backend/adaptors/memory/Cargo.toml b/backend/adaptors/memory/Cargo.toml new file mode 100644 index 0000000..b5280ca --- /dev/null +++ b/backend/adaptors/memory/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "memory-adaptor" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-trait = "0.1.68" +chrono = "0.4.24" +common = { path = "../../common" } +tokio = { version = "1.28.1", features = ["rt-multi-thread"] } diff --git a/backend/adaptors/memory/src/lib.rs b/backend/adaptors/memory/src/lib.rs new file mode 100644 index 0000000..200f8c4 --- /dev/null +++ b/backend/adaptors/memory/src/lib.rs @@ -0,0 +1,146 @@ +use std::{collections::HashMap, error::Error, fmt::Display}; + +use async_trait::async_trait; +use chrono::Utc; +use common::{ + adaptor::Adaptor, + event::{Event, EventDeletion}, + person::Person, + stats::Stats, +}; +use tokio::sync::Mutex; + +struct State { + stats: Stats, + events: HashMap, + people: HashMap<(String, String), Person>, +} + +pub struct MemoryAdaptor { + state: Mutex, +} + +#[async_trait] +impl Adaptor for MemoryAdaptor { + type Error = MemoryAdaptorError; + + async fn get_stats(&self) -> Result { + let state = self.state.lock().await; + + Ok(state.stats.clone()) + } + + async fn increment_stat_event_count(&self) -> Result { + let mut state = self.state.lock().await; + + state.stats.event_count += 1; + Ok(state.stats.event_count) + } + + async fn increment_stat_person_count(&self) -> Result { + let mut state = self.state.lock().await; + + state.stats.person_count += 1; + Ok(state.stats.person_count) + } + + async fn get_people(&self, event_id: String) -> Result>, Self::Error> { + let state = self.state.lock().await; + + // Event doesn't exist + if state.events.get(&event_id).is_none() { + return Ok(None); + } + + Ok(Some( + state + .people + .clone() + .into_iter() + .filter_map(|((p_event_id, _), p)| { + if p_event_id == event_id { + Some(p) + } else { + None + } + }) + .collect(), + )) + } + + async fn upsert_person(&self, event_id: String, person: Person) -> Result { + let mut state = self.state.lock().await; + + state + .people + .insert((event_id, person.name.clone()), person.clone()); + + Ok(person) + } + + async fn get_event(&self, id: String) -> Result, Self::Error> { + let mut state = self.state.lock().await; + + let event = state.events.get(&id).cloned(); + if let Some(mut event) = event.clone() { + event.visited_at = Utc::now(); + state.events.insert(id, event); + } + + Ok(event) + } + + async fn create_event(&self, event: Event) -> Result { + let mut state = self.state.lock().await; + + state.events.insert(event.id.clone(), event.clone()); + + Ok(event) + } + + async fn delete_event(&self, id: String) -> Result { + let mut state = self.state.lock().await; + + let mut person_count: u64 = state.people.len() as u64; + state.people = state + .people + .clone() + .into_iter() + .filter(|((event_id, _), _)| event_id != &id) + .collect(); + person_count -= state.people.len() as u64; + + state.events.remove(&id); + + Ok(EventDeletion { id, person_count }) + } +} + +impl MemoryAdaptor { + pub async fn new() -> Self { + println!("🧠 Using in-memory storage"); + println!("🚨 WARNING: All data will be lost when the process ends. Make sure you choose a database adaptor before deploying."); + + let state = Mutex::new(State { + stats: Stats { + event_count: 0, + person_count: 0, + }, + events: HashMap::new(), + people: HashMap::new(), + }); + + Self { state } + } +} + +#[derive(Debug)] +pub enum MemoryAdaptorError {} + +impl Display for MemoryAdaptorError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Memory adaptor error") + } +} + +impl Error for MemoryAdaptorError {} diff --git a/backend/common/src/event.rs b/backend/common/src/event.rs index 4584183..1b2fd07 100644 --- a/backend/common/src/event.rs +++ b/backend/common/src/event.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc}; +#[derive(Clone)] pub struct Event { pub id: String, pub name: String, @@ -9,6 +10,7 @@ pub struct Event { pub timezone: String, } +#[derive(Clone)] /// Info about a deleted event pub struct EventDeletion { pub id: String, diff --git a/backend/common/src/person.rs b/backend/common/src/person.rs index 8642cd4..fd19b76 100644 --- a/backend/common/src/person.rs +++ b/backend/common/src/person.rs @@ -1,5 +1,6 @@ use chrono::{DateTime, Utc}; +#[derive(Clone)] pub struct Person { pub name: String, pub password_hash: Option, diff --git a/backend/common/src/stats.rs b/backend/common/src/stats.rs index 7bb9ec2..a88d949 100644 --- a/backend/common/src/stats.rs +++ b/backend/common/src/stats.rs @@ -1,3 +1,4 @@ +#[derive(Clone)] pub struct Stats { pub event_count: i64, pub person_count: i64, diff --git a/backend/src/adaptors.rs b/backend/src/adaptors.rs new file mode 100644 index 0000000..95d280b --- /dev/null +++ b/backend/src/adaptors.rs @@ -0,0 +1,13 @@ +#[cfg(feature = "sql-adaptor")] +pub async fn create_adaptor() -> sql_adaptor::SqlAdaptor { + sql_adaptor::SqlAdaptor::new().await +} + +#[cfg(feature = "datastore-adaptor")] +pub async fn create_adaptor() -> datastore_adaptor::DatastoreAdaptor { + datastore_adaptor::DatastoreAdaptor::new().await +} + +pub async fn create_adaptor() -> memory_adaptor::MemoryAdaptor { + memory_adaptor::MemoryAdaptor::new().await +} diff --git a/backend/src/main.rs b/backend/src/main.rs index 10f4938..ee9924a 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -8,7 +8,6 @@ use axum::{ BoxError, Router, Server, }; use routes::*; -use sql_adaptor::SqlAdaptor; use tokio::sync::Mutex; use tower::ServiceBuilder; use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; @@ -16,8 +15,10 @@ use tower_http::{cors::CorsLayer, trace::TraceLayer}; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; +use crate::adaptors::create_adaptor; use crate::docs::ApiDoc; +mod adaptors; mod docs; mod errors; mod payloads; @@ -37,7 +38,7 @@ async fn main() { dotenv::dotenv().ok(); let shared_state = Arc::new(Mutex::new(ApiState { - adaptor: SqlAdaptor::new().await, + adaptor: create_adaptor().await, })); // CORS configuration From 2ba96cc9cb6a70ef9e1ecb86af45100ba4e1bfb4 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 18:33:32 +1000 Subject: [PATCH 17/29] Setup features for conditional compilation --- backend/Cargo.toml | 4 ++++ backend/src/adaptors.rs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/backend/Cargo.toml b/backend/Cargo.toml index e0da7b4..2d55a2a 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -5,6 +5,10 @@ license = "GPL-3.0-only" version = "2.0.0" edition = "2021" +[features] +sql-adaptor = [] +datastore-adaptor = [] + [workspace] members = ["common", "adaptors/*"] diff --git a/backend/src/adaptors.rs b/backend/src/adaptors.rs index 95d280b..2010922 100644 --- a/backend/src/adaptors.rs +++ b/backend/src/adaptors.rs @@ -8,6 +8,8 @@ pub async fn create_adaptor() -> datastore_adaptor::DatastoreAdaptor { datastore_adaptor::DatastoreAdaptor::new().await } +#[cfg(not(feature = "sql-adaptor"))] +#[cfg(not(feature = "datastore-adaptor"))] pub async fn create_adaptor() -> memory_adaptor::MemoryAdaptor { memory_adaptor::MemoryAdaptor::new().await } From b586b4c88d203b21b827f063dc13177ab5cbe003 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 20:11:41 +1000 Subject: [PATCH 18/29] More helpful error messages when creating adaptors --- backend/Cargo.lock | 8 +------- backend/Cargo.toml | 2 +- backend/adaptors/datastore/src/lib.rs | 17 ++++++++++------- backend/adaptors/sql/src/lib.rs | 13 +++++++++---- backend/src/main.rs | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 025bf58..04eb650 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -617,7 +617,7 @@ dependencies = [ "chrono", "common", "datastore-adaptor", - "dotenv", + "dotenvy", "memory-adaptor", "punycode", "rand", @@ -814,12 +814,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "dotenv" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" - [[package]] name = "dotenvy" version = "0.15.7" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 2d55a2a..dadb128 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -20,7 +20,7 @@ common = { path = "common" } sql-adaptor = { path = "adaptors/sql" } datastore-adaptor = { path = "adaptors/datastore" } memory-adaptor = { path = "adaptors/memory" } -dotenv = "0.15.0" +dotenvy = "0.15.7" serde_json = "1.0.96" rand = "0.8.5" punycode = "0.4.1" diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs index d5bbc7c..9402cb8 100644 --- a/backend/adaptors/datastore/src/lib.rs +++ b/backend/adaptors/datastore/src/lib.rs @@ -203,19 +203,22 @@ impl Adaptor for DatastoreAdaptor { impl DatastoreAdaptor { pub async fn new() -> Self { - let project_name = env::var("GCP_PROJECT_NAME").unwrap(); - // Load credentials - let credentials: ApplicationCredentials = - serde_json::from_str(&env::var("GCP_CREDENTIALS").unwrap()).unwrap(); + let credentials: ApplicationCredentials = serde_json::from_str( + &env::var("GCP_CREDENTIALS").expect("Expected GCP_CREDENTIALS environment variable"), + ) + .expect("GCP_CREDENTIALS environment variable is not valid JSON"); // Connect to datastore - let client = Client::from_credentials(project_name.clone(), credentials) + let client = Client::from_credentials(credentials.project_id.clone(), credentials.clone()) .await - .unwrap(); + .expect("Failed to setup datastore client"); let client = Mutex::new(client); - println!("🎛️ Connected to datastore in project {}", project_name); + println!( + "🎛️ Connected to datastore in project {}", + credentials.project_id + ); Self { client } } diff --git a/backend/adaptors/sql/src/lib.rs b/backend/adaptors/sql/src/lib.rs index 7d691dc..f0a9137 100644 --- a/backend/adaptors/sql/src/lib.rs +++ b/backend/adaptors/sql/src/lib.rs @@ -158,23 +158,28 @@ async fn get_stats_row(db: &DatabaseConnection) -> Result Self { - let connection_string = env::var("DATABASE_URL").unwrap(); + let connection_string = + env::var("DATABASE_URL").expect("Expected DATABASE_URL environment variable"); // Connect to the database - let db = Database::connect(&connection_string).await.unwrap(); + let db = Database::connect(&connection_string) + .await + .expect("Failed to connect to SQL database"); println!( "{} Connected to database at {}", match db { DatabaseConnection::SqlxMySqlPoolConnection(_) => "🐬", DatabaseConnection::SqlxPostgresPoolConnection(_) => "🐘", DatabaseConnection::SqlxSqlitePoolConnection(_) => "🪶", - DatabaseConnection::Disconnected => panic!("Failed to connect"), + DatabaseConnection::Disconnected => panic!("Failed to connect to SQL database"), }, connection_string ); // Setup tables - Migrator::up(&db, None).await.unwrap(); + Migrator::up(&db, None) + .await + .expect("Failed to set up tables in the database"); Self { db } } diff --git a/backend/src/main.rs b/backend/src/main.rs index ee9924a..2dd0788 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -35,7 +35,7 @@ async fn main() { tracing_subscriber::fmt::init(); // Load env - dotenv::dotenv().ok(); + dotenvy::dotenv().ok(); let shared_state = Arc::new(Mutex::new(ApiState { adaptor: create_adaptor().await, From 6b99fe1c725046fb27f1638385f8401061b9b7de Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 20:23:43 +1000 Subject: [PATCH 19/29] Properly parse and save stats to datastore --- backend/adaptors/datastore/src/lib.rs | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs index 9402cb8..21622b0 100644 --- a/backend/adaptors/datastore/src/lib.rs +++ b/backend/adaptors/datastore/src/lib.rs @@ -31,14 +31,8 @@ impl Adaptor for DatastoreAdaptor { type Error = DatastoreAdaptorError; async fn get_stats(&self) -> Result { - let mut client = self.client.lock().await; - - let key = Key::new(STATS_KIND); - let event_count = client - .get(key.clone().id(STATS_EVENTS_ID)) - .await? - .unwrap_or(0); - let person_count = client.get(key.id(STATS_PEOPLE_ID)).await?.unwrap_or(0); + let event_count = get_stats_value(&self.client, STATS_EVENTS_ID).await?; + let person_count = get_stats_value(&self.client, STATS_PEOPLE_ID).await?; Ok(Stats { event_count, @@ -50,8 +44,10 @@ impl Adaptor for DatastoreAdaptor { let mut client = self.client.lock().await; let key = Key::new(STATS_KIND).id(STATS_EVENTS_ID); - let event_count = client.get(key.clone()).await?.unwrap_or(0) + 1; - client.put((key, event_count)).await?; + let event_count = get_stats_value(&self.client, STATS_EVENTS_ID).await? + 1; + + let updated_props = HashMap::from([(String::from("value"), event_count.into_value())]); + client.put((key, updated_props)).await?; Ok(event_count) } @@ -59,8 +55,10 @@ impl Adaptor for DatastoreAdaptor { let mut client = self.client.lock().await; let key = Key::new(STATS_KIND).id(STATS_PEOPLE_ID); - let person_count = client.get(key.clone()).await?.unwrap_or(0) + 1; - client.put((key, person_count)).await?; + let person_count = get_stats_value(&self.client, STATS_PEOPLE_ID).await? + 1; + + let updated_props = HashMap::from([(String::from("value"), person_count.into_value())]); + client.put((key, updated_props)).await?; Ok(person_count) } @@ -224,6 +222,17 @@ impl DatastoreAdaptor { } } +async fn get_stats_value(client: &Mutex, id: &str) -> Result { + let mut client = client.lock().await; + Ok(client + .get(Key::new(STATS_KIND).id(id)) + .await? + .unwrap_or(HashMap::from([(String::from("value"), 0)])) + .get("value") + .cloned() + .unwrap_or(0)) +} + fn parse_into_person(value: Value) -> Result { let person: HashMap = HashMap::from_value(value)?; Ok(Person { From e13f46678520d3ae51bfdc7565a49620c9cf6187 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 22:53:50 +1000 Subject: [PATCH 20/29] Support Null values and use structs --- backend/Cargo.lock | 61 +++++- backend/adaptors/datastore/Cargo.toml | 2 +- backend/adaptors/datastore/src/lib.rs | 263 +++++++++++--------------- 3 files changed, 168 insertions(+), 158 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 04eb650..0a5fd77 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -746,6 +746,41 @@ dependencies = [ "syn 2.0.15", ] +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core", + "quote", + "syn 1.0.109", +] + [[package]] name = "dashmap" version = "5.4.0" @@ -1087,11 +1122,11 @@ dependencies = [ [[package]] name = "google-cloud" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a517f0235af652d334a021b81aa2e8f18a77512c26be18722debb7d405912f80" +source = "git+https://github.com/GRA0007/google-cloud-rs.git#5b2c3d6dcde9e58528c90c0a3016f123104c5fe3" dependencies = [ "chrono", "futures", + "google-cloud-derive", "http", "hyper", "hyper-rustls", @@ -1106,6 +1141,16 @@ dependencies = [ "tonic-build", ] +[[package]] +name = "google-cloud-derive" +version = "0.2.1" +source = "git+https://github.com/GRA0007/google-cloud-rs.git#5b2c3d6dcde9e58528c90c0a3016f123104c5fe3" +dependencies = [ + "darling", + "quote", + "syn 1.0.109", +] + [[package]] name = "governor" version = "0.5.1" @@ -1357,6 +1402,12 @@ dependencies = [ "cxx-build", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -2981,6 +3032,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "subtle" version = "2.4.1" diff --git a/backend/adaptors/datastore/Cargo.toml b/backend/adaptors/datastore/Cargo.toml index f9a69ac..bc6764b 100644 --- a/backend/adaptors/datastore/Cargo.toml +++ b/backend/adaptors/datastore/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" async-trait = "0.1.68" chrono = "0.4.24" common = { path = "../../common" } -google-cloud = { version = "0.2.1", features = ["datastore"] } +google-cloud = { git = "https://github.com/GRA0007/google-cloud-rs.git", features = ["datastore", "derive"] } serde = "1.0.163" serde_json = "1.0.96" tokio = { version = "1.28.1", features = ["rt-multi-thread"] } diff --git a/backend/adaptors/datastore/src/lib.rs b/backend/adaptors/datastore/src/lib.rs index 21622b0..3d4e33c 100644 --- a/backend/adaptors/datastore/src/lib.rs +++ b/backend/adaptors/datastore/src/lib.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, env, error::Error, fmt::Display}; +use std::{env, error::Error, fmt::Display}; use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; @@ -10,8 +10,7 @@ use common::{ }; use google_cloud::{ authorize::ApplicationCredentials, - datastore::{Client, Filter, FromValue, IntoValue, Key, Query, Value}, - error::ConvertError, + datastore::{Client, Filter, FromValue, IntoValue, Key, Query}, }; use tokio::sync::Mutex; @@ -31,12 +30,17 @@ impl Adaptor for DatastoreAdaptor { type Error = DatastoreAdaptorError; async fn get_stats(&self) -> Result { - let event_count = get_stats_value(&self.client, STATS_EVENTS_ID).await?; - let person_count = get_stats_value(&self.client, STATS_PEOPLE_ID).await?; + let mut client = self.client.lock().await; + + let event_key = Key::new(STATS_KIND).id(STATS_EVENTS_ID); + let event_stats: DatastoreStats = client.get(event_key).await?.unwrap_or_default(); + + let person_key = Key::new(STATS_KIND).id(STATS_PEOPLE_ID); + let person_stats: DatastoreStats = client.get(person_key).await?.unwrap_or_default(); Ok(Stats { - event_count, - person_count, + event_count: event_stats.value, + person_count: person_stats.value, }) } @@ -44,22 +48,22 @@ impl Adaptor for DatastoreAdaptor { let mut client = self.client.lock().await; let key = Key::new(STATS_KIND).id(STATS_EVENTS_ID); - let event_count = get_stats_value(&self.client, STATS_EVENTS_ID).await? + 1; + let mut event_stats: DatastoreStats = client.get(key.clone()).await?.unwrap_or_default(); - let updated_props = HashMap::from([(String::from("value"), event_count.into_value())]); - client.put((key, updated_props)).await?; - Ok(event_count) + event_stats.value += 1; + client.put((key, event_stats.clone())).await?; + Ok(event_stats.value) } async fn increment_stat_person_count(&self) -> Result { let mut client = self.client.lock().await; let key = Key::new(STATS_KIND).id(STATS_PEOPLE_ID); - let person_count = get_stats_value(&self.client, STATS_PEOPLE_ID).await? + 1; + let mut person_stats: DatastoreStats = client.get(key.clone()).await?.unwrap_or_default(); - let updated_props = HashMap::from([(String::from("value"), person_count.into_value())]); - client.put((key, updated_props)).await?; - Ok(person_count) + person_stats.value += 1; + client.put((key, person_stats.clone())).await?; + Ok(person_stats.value) } async fn get_people(&self, event_id: String) -> Result>, Self::Error> { @@ -67,7 +71,7 @@ impl Adaptor for DatastoreAdaptor { // Check the event exists if client - .get::(Key::new(EVENT_KIND).id(event_id.clone())) + .get::(Key::new(EVENT_KIND).id(event_id.clone())) .await? .is_none() { @@ -82,7 +86,11 @@ impl Adaptor for DatastoreAdaptor { ) .await? .into_iter() - .filter_map(|entity| parse_into_person(entity.properties().clone()).ok()) + .filter_map(|entity| { + DatastorePerson::from_value(entity.properties().clone()) + .ok() + .map(|ds_person| ds_person.into()) + }) .collect(), )) } @@ -110,22 +118,9 @@ impl Adaptor for DatastoreAdaptor { key = entity.key().clone(); } - let mut properties = HashMap::new(); - properties.insert(String::from("name"), person.name.clone().into_value()); - if let Some(password_hash) = person.password_hash.clone() { - properties.insert(String::from("password"), password_hash.into_value()); - } - properties.insert(String::from("eventId"), event_id.into_value()); - properties.insert( - String::from("created"), - person.created_at.clone().timestamp().into_value(), - ); - properties.insert( - String::from("availability"), - person.availability.clone().into_value(), - ); - - client.put((key, properties)).await?; + client + .put((key, DatastorePerson::from_person(person.clone(), event_id))) + .await?; Ok(person) } @@ -134,21 +129,15 @@ impl Adaptor for DatastoreAdaptor { let mut client = self.client.lock().await; let key = Key::new(EVENT_KIND).id(id.clone()); - let existing_event = client.get::(key.clone()).await?; + let existing_event = client.get::(key.clone()).await?; // Mark as visited if it exists - if let Some(mut event) = existing_event - .clone() - .map(HashMap::::from_value) - .transpose()? - { - event.insert(String::from("visited"), Utc::now().timestamp().into_value()); + if let Some(mut event) = existing_event.clone() { + event.visited = Utc::now().timestamp(); client.put((key, event)).await?; } - Ok(existing_event - .map(|value| parse_into_event(id, value)) - .transpose()?) + Ok(existing_event.map(|e| e.to_event(id))) } async fn create_event(&self, event: Event) -> Result { @@ -156,23 +145,8 @@ impl Adaptor for DatastoreAdaptor { let key = Key::new(EVENT_KIND).id(event.id.clone()); - let mut properties = HashMap::new(); - properties.insert(String::from("name"), event.name.clone().into_value()); - properties.insert( - String::from("created"), - event.created_at.clone().timestamp().into_value(), - ); - properties.insert( - String::from("visited"), - event.visited_at.clone().timestamp().into_value(), - ); - properties.insert(String::from("times"), event.times.clone().into_value()); - properties.insert( - String::from("timezone"), - event.timezone.clone().into_value(), - ); - - client.put((key, properties)).await?; + let ds_event: DatastoreEvent = event.clone().into(); + client.put((key, ds_event)).await?; Ok(event) } @@ -180,7 +154,7 @@ impl Adaptor for DatastoreAdaptor { async fn delete_event(&self, id: String) -> Result { let mut client = self.client.lock().await; - let mut people_keys: Vec = client + let mut keys_to_delete: Vec = client .query( Query::new(PERSON_KIND) .filter(Filter::Equal("eventId".into(), id.clone().into_value())), @@ -190,10 +164,10 @@ impl Adaptor for DatastoreAdaptor { .map(|entity| entity.key().clone()) .collect(); - let person_count = people_keys.len().try_into().unwrap(); - people_keys.insert(0, Key::new(EVENT_KIND).id(id.clone())); + let person_count = keys_to_delete.len().try_into().unwrap(); + keys_to_delete.insert(0, Key::new(EVENT_KIND).id(id.clone())); - client.delete_all(people_keys).await?; + client.delete_all(keys_to_delete).await?; Ok(EventDeletion { id, person_count }) } @@ -222,101 +196,80 @@ impl DatastoreAdaptor { } } -async fn get_stats_value(client: &Mutex, id: &str) -> Result { - let mut client = client.lock().await; - Ok(client - .get(Key::new(STATS_KIND).id(id)) - .await? - .unwrap_or(HashMap::from([(String::from("value"), 0)])) - .get("value") - .cloned() - .unwrap_or(0)) +#[derive(FromValue, IntoValue, Default, Clone)] +struct DatastoreStats { + value: i64, } -fn parse_into_person(value: Value) -> Result { - let person: HashMap = HashMap::from_value(value)?; - Ok(Person { - name: String::from_value( - person - .get("name") - .ok_or(ConvertError::MissingProperty("name".to_owned()))? - .clone(), - )?, - password_hash: person - .get("password") - .map(|p| String::from_value(p.clone())) - .transpose()?, - created_at: DateTime::from_utc( - NaiveDateTime::from_timestamp_opt( - i64::from_value( - person - .get("created") - .ok_or(ConvertError::MissingProperty("created".to_owned()))? - .clone(), - )?, - 0, - ) - .unwrap(), - Utc, - ), - availability: Vec::from_value( - person - .get("availability") - .ok_or(ConvertError::MissingProperty("availability".to_owned()))? - .clone(), - )?, - }) +#[derive(FromValue, IntoValue, Clone)] +struct DatastoreEvent { + name: String, + created: i64, + visited: i64, + times: Vec, + timezone: String, } -fn parse_into_event(id: String, value: Value) -> Result { - let event: HashMap = HashMap::from_value(value)?; - Ok(Event { - id, - name: String::from_value( - event - .get("name") - .ok_or(ConvertError::MissingProperty("name".to_owned()))? - .clone(), - )?, - created_at: DateTime::from_utc( - NaiveDateTime::from_timestamp_opt( - i64::from_value( - event - .get("created") - .ok_or(ConvertError::MissingProperty("created".to_owned()))? - .clone(), - )?, - 0, - ) - .unwrap(), - Utc, - ), - visited_at: DateTime::from_utc( - NaiveDateTime::from_timestamp_opt( - i64::from_value( - event - .get("visited") - .ok_or(ConvertError::MissingProperty("visited".to_owned()))? - .clone(), - )?, - 0, - ) - .unwrap(), - Utc, - ), - times: Vec::from_value( - event - .get("times") - .ok_or(ConvertError::MissingProperty("times".to_owned()))? - .clone(), - )?, - timezone: String::from_value( - event - .get("timezone") - .ok_or(ConvertError::MissingProperty("timezone".to_owned()))? - .clone(), - )?, - }) +#[derive(FromValue, IntoValue)] +#[allow(non_snake_case)] +struct DatastorePerson { + name: String, + password: Option, + created: i64, + eventId: String, + availability: Vec, +} + +impl From for Person { + fn from(value: DatastorePerson) -> Self { + Self { + name: value.name, + password_hash: value.password, + created_at: unix_to_date(value.created), + availability: value.availability, + } + } +} + +impl DatastorePerson { + fn from_person(person: Person, event_id: String) -> Self { + Self { + name: person.name, + password: person.password_hash, + created: person.created_at.timestamp(), + eventId: event_id, + availability: person.availability, + } + } +} + +impl From for DatastoreEvent { + fn from(value: Event) -> Self { + Self { + name: value.name, + created: value.created_at.timestamp(), + visited: value.visited_at.timestamp(), + times: value.times, + timezone: value.timezone, + } + } +} + +impl DatastoreEvent { + fn to_event(&self, event_id: String) -> Event { + Event { + id: event_id, + name: self.name.clone(), + created_at: unix_to_date(self.created), + visited_at: unix_to_date(self.visited), + times: self.times.clone(), + timezone: self.timezone.clone(), + } + } +} + +fn unix_to_date(unix: i64) -> DateTime { + DateTime::from_utc(NaiveDateTime::from_timestamp_opt(unix, 0).unwrap(), Utc) } #[derive(Debug)] From dfdfc24ee53851c600e223f91817b7c22d9e5334 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 22:54:07 +1000 Subject: [PATCH 21/29] Ignore case for person names --- backend/src/routes/person.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/routes/person.rs b/backend/src/routes/person.rs index ec249d7..28c8051 100644 --- a/backend/src/routes/person.rs +++ b/backend/src/routes/person.rs @@ -86,7 +86,7 @@ pub async fn get_person( let existing_person = existing_people .unwrap() .into_iter() - .find(|p| p.name == person_name); + .find(|p| p.name.to_lowercase() == person_name.to_lowercase()); match existing_person { // Login @@ -168,7 +168,7 @@ pub async fn update_person( let existing_person = existing_people .unwrap() .into_iter() - .find(|p| p.name == person_name) + .find(|p| p.name.to_lowercase() == person_name.to_lowercase()) .ok_or(ApiError::NotFound)?; // Verify password (if set) From 3e770a337b78e29f3cb45b2c74cfa7d222d13b8e Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Mon, 15 May 2023 23:51:12 +1000 Subject: [PATCH 22/29] Include documentation for API and subcrates --- README.md | 13 ++++------ {backend => api}/.gitignore | 0 {backend => api}/Cargo.lock | 0 {backend => api}/Cargo.toml | 2 +- api/README.md | 26 +++++++++++++++++++ api/adaptors/README.md | 21 +++++++++++++++ .../adaptors/datastore/Cargo.toml | 1 + api/adaptors/datastore/README.md | 13 ++++++++++ .../adaptors/datastore/src/lib.rs | 0 {backend => api}/adaptors/memory/Cargo.toml | 0 api/adaptors/memory/README.md | 6 +++++ {backend => api}/adaptors/memory/src/lib.rs | 0 {backend => api}/adaptors/sql/Cargo.toml | 0 api/adaptors/sql/README.md | 13 ++++++++++ .../adaptors/sql/src/entity/event.rs | 0 .../adaptors/sql/src/entity/mod.rs | 0 .../adaptors/sql/src/entity/person.rs | 0 .../adaptors/sql/src/entity/prelude.rs | 0 .../adaptors/sql/src/entity/stats.rs | 0 {backend => api}/adaptors/sql/src/lib.rs | 0 .../sql/src/migration/m01_setup_tables.rs | 0 .../adaptors/sql/src/migration/mod.rs | 0 {backend => api}/common/Cargo.toml | 0 api/common/README.md | 3 +++ {backend => api}/common/src/adaptor.rs | 0 {backend => api}/common/src/event.rs | 0 {backend => api}/common/src/lib.rs | 0 {backend => api}/common/src/person.rs | 0 {backend => api}/common/src/stats.rs | 0 {backend => api}/src/adaptors.rs | 0 {backend => api}/src/docs.rs | 0 {backend => api}/src/errors.rs | 0 {backend => api}/src/main.rs | 0 {backend => api}/src/payloads.rs | 0 {backend => api}/src/res/adjectives.json | 0 {backend => api}/src/res/crabs.json | 0 {backend => api}/src/routes/event.rs | 0 {backend => api}/src/routes/mod.rs | 0 {backend => api}/src/routes/person.rs | 0 {backend => api}/src/routes/stats.rs | 0 40 files changed, 89 insertions(+), 9 deletions(-) rename {backend => api}/.gitignore (100%) rename {backend => api}/Cargo.lock (100%) rename {backend => api}/Cargo.toml (97%) create mode 100644 api/README.md create mode 100644 api/adaptors/README.md rename {backend => api}/adaptors/datastore/Cargo.toml (84%) create mode 100644 api/adaptors/datastore/README.md rename {backend => api}/adaptors/datastore/src/lib.rs (100%) rename {backend => api}/adaptors/memory/Cargo.toml (100%) create mode 100644 api/adaptors/memory/README.md rename {backend => api}/adaptors/memory/src/lib.rs (100%) rename {backend => api}/adaptors/sql/Cargo.toml (100%) create mode 100644 api/adaptors/sql/README.md rename {backend => api}/adaptors/sql/src/entity/event.rs (100%) rename {backend => api}/adaptors/sql/src/entity/mod.rs (100%) rename {backend => api}/adaptors/sql/src/entity/person.rs (100%) rename {backend => api}/adaptors/sql/src/entity/prelude.rs (100%) rename {backend => api}/adaptors/sql/src/entity/stats.rs (100%) rename {backend => api}/adaptors/sql/src/lib.rs (100%) rename {backend => api}/adaptors/sql/src/migration/m01_setup_tables.rs (100%) rename {backend => api}/adaptors/sql/src/migration/mod.rs (100%) rename {backend => api}/common/Cargo.toml (100%) create mode 100644 api/common/README.md rename {backend => api}/common/src/adaptor.rs (100%) rename {backend => api}/common/src/event.rs (100%) rename {backend => api}/common/src/lib.rs (100%) rename {backend => api}/common/src/person.rs (100%) rename {backend => api}/common/src/stats.rs (100%) rename {backend => api}/src/adaptors.rs (100%) rename {backend => api}/src/docs.rs (100%) rename {backend => api}/src/errors.rs (100%) rename {backend => api}/src/main.rs (100%) rename {backend => api}/src/payloads.rs (100%) rename {backend => api}/src/res/adjectives.json (100%) rename {backend => api}/src/res/crabs.json (100%) rename {backend => api}/src/routes/event.rs (100%) rename {backend => api}/src/routes/mod.rs (100%) rename {backend => api}/src/routes/person.rs (100%) rename {backend => api}/src/routes/stats.rs (100%) diff --git a/README.md b/README.md index 1b44745..e59204f 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ Align your schedules to find the perfect time that works for everyone. Licensed under the GNU GPLv3. -Crab Fit - Use your availability to find a time that works for everyone | Product Hunt - ## Contributing ### ⭐️ Bugs or feature requests @@ -17,14 +15,13 @@ If you speak a language other than English and you want to help translate Crab F ## Setup -1. Clone the repo. -2. Run `yarn` in both backend and frontend folders. -3. Run `yarn dev` in the backend folder to start the API. **Note:** you will need a google cloud app set up with datastore enabled and set your `GOOGLE_APPLICATION_CREDENTIALS` environment variable to your service key path. -4. Run `yarn dev` in the frontend folder to start the frontend. +1. Clone the repo and ensure you have `node`, `yarn` and `rust` installed on your machine. +2. Run `yarn` in `frontend` folder to install dependencies, then `yarn dev` to start the dev server. +3. Run `cargo run` in the `api` folder to start the API. ### 🔌 Browser extension -The browser extension in `crabfit-browser-extension` can be tested by first running the frontend, and changing the iframe url in the extension's `popup.html` to match the local Crab Fit. Then it can be loaded as an unpacked extension in Chrome to test. +The browser extension in `browser-extension` can be tested by first running the frontend, and changing the iframe url in the extension's `popup.html` to match the local Crab Fit. Then it can be loaded as an unpacked extension in Chrome to test. ## Deploy @@ -34,4 +31,4 @@ To deploy cron jobs (i.e. monthly cleanup of old events), run `gcloud app deploy ### 🔌 Browser extension -Compress everything inside the `crabfit-browser-extension` folder and use that zip to deploy using Chrome web store and Mozilla Add-on store. +Compress everything inside the `browser-extension` folder and use that zip to deploy using Chrome web store and Mozilla Add-on store. diff --git a/backend/.gitignore b/api/.gitignore similarity index 100% rename from backend/.gitignore rename to api/.gitignore diff --git a/backend/Cargo.lock b/api/Cargo.lock similarity index 100% rename from backend/Cargo.lock rename to api/Cargo.lock diff --git a/backend/Cargo.toml b/api/Cargo.toml similarity index 97% rename from backend/Cargo.toml rename to api/Cargo.toml index dadb128..0d5ff31 100644 --- a/backend/Cargo.toml +++ b/api/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "crabfit_backend" +name = "crabfit-api" description = "API for Crab Fit" license = "GPL-3.0-only" version = "2.0.0" diff --git a/api/README.md b/api/README.md new file mode 100644 index 0000000..3b9eade --- /dev/null +++ b/api/README.md @@ -0,0 +1,26 @@ +# Crab Fit API + +This is the API for Crab Fit, written in Rust. It uses the [axum](https://crates.io/crates/axum) framework to run a HTTP server, and supports multiple storage adaptors. + +## API docs + +OpenAPI compatible API docs are generated using [utoipa](https://crates.io/crates/utoipa). You can visit them at [https://api.crab.fit/docs](https://api.crab.fit/docs). + +## Storage adaptors + +| Adaptor | Works with | +| ------- | ---------- | +| `memory-adaptor` | Stores data in memory | +| `sql-adaptor` | Postgres, MySQL, SQLite | +| `datastore-adaptor` | Google Datastore | + +To choose an adaptor, specify it in the `features` when compiling, e.g. `cargo run --features sql-adaptor`. + +Some adaptors require environment variables to be set. You can specify them in a `.env` file and they'll be loaded in using [dotenvy](https://crates.io/crates/dotenvy). See a specific adaptor's readme for more information. + +> **Note** +> `memory-adaptor` is the default if no features are specified. Ensure you specify a different adaptor when deploying. + +### Adding an adaptor + +See [adding an adaptor](adaptors/README.md#adding-an-adaptor) in the adaptors readme. diff --git a/api/adaptors/README.md b/api/adaptors/README.md new file mode 100644 index 0000000..f1fbcaa --- /dev/null +++ b/api/adaptors/README.md @@ -0,0 +1,21 @@ +# Crab Fit Storage Adaptors + +This directory contains sub-crates that connect Crab Fit to a database of some sort. For a list of available adaptors, see the [api readme](../README.md). + +## Adding an adaptor + +The suggested flow is copying an existing adaptor, such as `memory`, and altering the code to work with your chosen database. + +Note, you will need to have the following crates as dependencies in your adaptor: + +- `common`
Includes a trait for implementing your adaptor, as well as structs your adaptor needs to return. +- `async-trait`
Required because the trait from `common` uses async functions, make sure you include `#[async_trait]` above your trait implementation. + +Once you've created the adaptor, you'll need to make sure it's included as a dependency in the root [`Cargo.toml`](../Cargo.toml), and add a feature flag with the same name. Make sure you also document the new adaptor in the [api readme](../README.md). + +Finally, add a new version of the `create_adaptor` function in the [`adaptors.rs`](../src/adaptors.rs) file that will only compile if the specific feature flag you added is set. Don't forget to add a `not` version of the feature to the default memory adaptor function at the bottom of the file. + +## FAQ + +Why is it spelt "adaptor" and not "adapter"? +> The maintainer lives in Australia, where it's usually spelt "adaptor" 😎 diff --git a/backend/adaptors/datastore/Cargo.toml b/api/adaptors/datastore/Cargo.toml similarity index 84% rename from backend/adaptors/datastore/Cargo.toml rename to api/adaptors/datastore/Cargo.toml index bc6764b..da37e8c 100644 --- a/backend/adaptors/datastore/Cargo.toml +++ b/api/adaptors/datastore/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" async-trait = "0.1.68" chrono = "0.4.24" common = { path = "../../common" } +# Uses custom version of google-cloud that has support for NULL values google-cloud = { git = "https://github.com/GRA0007/google-cloud-rs.git", features = ["datastore", "derive"] } serde = "1.0.163" serde_json = "1.0.96" diff --git a/api/adaptors/datastore/README.md b/api/adaptors/datastore/README.md new file mode 100644 index 0000000..ca3a3d6 --- /dev/null +++ b/api/adaptors/datastore/README.md @@ -0,0 +1,13 @@ +# Google Datastore Adaptor + +This adaptor works with [Google Cloud Datastore](https://cloud.google.com/datastore). Please note that it's compatible with Firestore in Datastore mode, but not with Firestore. + +## Environment + +To use this adaptor, make sure you have the `GCP_CREDENTIALS` environment variable set to your service account credentials in JSON format. See [this page](https://developers.google.com/workspace/guides/create-credentials#service-account) for info on setting up a service account and generating credentials. + +Example: + +```env +GCP_CREDENTIALS='{"type":"service_account","project_id":"my-project"}' +``` diff --git a/backend/adaptors/datastore/src/lib.rs b/api/adaptors/datastore/src/lib.rs similarity index 100% rename from backend/adaptors/datastore/src/lib.rs rename to api/adaptors/datastore/src/lib.rs diff --git a/backend/adaptors/memory/Cargo.toml b/api/adaptors/memory/Cargo.toml similarity index 100% rename from backend/adaptors/memory/Cargo.toml rename to api/adaptors/memory/Cargo.toml diff --git a/api/adaptors/memory/README.md b/api/adaptors/memory/README.md new file mode 100644 index 0000000..c7a43e4 --- /dev/null +++ b/api/adaptors/memory/README.md @@ -0,0 +1,6 @@ +# Memory Adaptor + +This adaptor stores everything in memory, and all data is lost when the API is stopped. Useful for testing. + +> **Warning** +> Do not use this adaptor in production! diff --git a/backend/adaptors/memory/src/lib.rs b/api/adaptors/memory/src/lib.rs similarity index 100% rename from backend/adaptors/memory/src/lib.rs rename to api/adaptors/memory/src/lib.rs diff --git a/backend/adaptors/sql/Cargo.toml b/api/adaptors/sql/Cargo.toml similarity index 100% rename from backend/adaptors/sql/Cargo.toml rename to api/adaptors/sql/Cargo.toml diff --git a/api/adaptors/sql/README.md b/api/adaptors/sql/README.md new file mode 100644 index 0000000..0fcb334 --- /dev/null +++ b/api/adaptors/sql/README.md @@ -0,0 +1,13 @@ +# SQL Adaptor + +This adaptor works with [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/) or [SQLite](https://sqlite.org/index.html) databases. + +## Environment + +To use this adaptor, make sure you have the `DATABASE_URL` environment variable set to the database url for your chosen database. + +Example: + +```env +DATABASE_URL="postgresql://username:password@localhost:5432/crabfit" +``` diff --git a/backend/adaptors/sql/src/entity/event.rs b/api/adaptors/sql/src/entity/event.rs similarity index 100% rename from backend/adaptors/sql/src/entity/event.rs rename to api/adaptors/sql/src/entity/event.rs diff --git a/backend/adaptors/sql/src/entity/mod.rs b/api/adaptors/sql/src/entity/mod.rs similarity index 100% rename from backend/adaptors/sql/src/entity/mod.rs rename to api/adaptors/sql/src/entity/mod.rs diff --git a/backend/adaptors/sql/src/entity/person.rs b/api/adaptors/sql/src/entity/person.rs similarity index 100% rename from backend/adaptors/sql/src/entity/person.rs rename to api/adaptors/sql/src/entity/person.rs diff --git a/backend/adaptors/sql/src/entity/prelude.rs b/api/adaptors/sql/src/entity/prelude.rs similarity index 100% rename from backend/adaptors/sql/src/entity/prelude.rs rename to api/adaptors/sql/src/entity/prelude.rs diff --git a/backend/adaptors/sql/src/entity/stats.rs b/api/adaptors/sql/src/entity/stats.rs similarity index 100% rename from backend/adaptors/sql/src/entity/stats.rs rename to api/adaptors/sql/src/entity/stats.rs diff --git a/backend/adaptors/sql/src/lib.rs b/api/adaptors/sql/src/lib.rs similarity index 100% rename from backend/adaptors/sql/src/lib.rs rename to api/adaptors/sql/src/lib.rs diff --git a/backend/adaptors/sql/src/migration/m01_setup_tables.rs b/api/adaptors/sql/src/migration/m01_setup_tables.rs similarity index 100% rename from backend/adaptors/sql/src/migration/m01_setup_tables.rs rename to api/adaptors/sql/src/migration/m01_setup_tables.rs diff --git a/backend/adaptors/sql/src/migration/mod.rs b/api/adaptors/sql/src/migration/mod.rs similarity index 100% rename from backend/adaptors/sql/src/migration/mod.rs rename to api/adaptors/sql/src/migration/mod.rs diff --git a/backend/common/Cargo.toml b/api/common/Cargo.toml similarity index 100% rename from backend/common/Cargo.toml rename to api/common/Cargo.toml diff --git a/api/common/README.md b/api/common/README.md new file mode 100644 index 0000000..3ca3179 --- /dev/null +++ b/api/common/README.md @@ -0,0 +1,3 @@ +# Common + +This crate contains the [adaptor trait](./src/adaptor.rs), and structs that are used by it. These are separated into their own crate so that the root crate and the adaptors can import from it without causing a circular dependency. diff --git a/backend/common/src/adaptor.rs b/api/common/src/adaptor.rs similarity index 100% rename from backend/common/src/adaptor.rs rename to api/common/src/adaptor.rs diff --git a/backend/common/src/event.rs b/api/common/src/event.rs similarity index 100% rename from backend/common/src/event.rs rename to api/common/src/event.rs diff --git a/backend/common/src/lib.rs b/api/common/src/lib.rs similarity index 100% rename from backend/common/src/lib.rs rename to api/common/src/lib.rs diff --git a/backend/common/src/person.rs b/api/common/src/person.rs similarity index 100% rename from backend/common/src/person.rs rename to api/common/src/person.rs diff --git a/backend/common/src/stats.rs b/api/common/src/stats.rs similarity index 100% rename from backend/common/src/stats.rs rename to api/common/src/stats.rs diff --git a/backend/src/adaptors.rs b/api/src/adaptors.rs similarity index 100% rename from backend/src/adaptors.rs rename to api/src/adaptors.rs diff --git a/backend/src/docs.rs b/api/src/docs.rs similarity index 100% rename from backend/src/docs.rs rename to api/src/docs.rs diff --git a/backend/src/errors.rs b/api/src/errors.rs similarity index 100% rename from backend/src/errors.rs rename to api/src/errors.rs diff --git a/backend/src/main.rs b/api/src/main.rs similarity index 100% rename from backend/src/main.rs rename to api/src/main.rs diff --git a/backend/src/payloads.rs b/api/src/payloads.rs similarity index 100% rename from backend/src/payloads.rs rename to api/src/payloads.rs diff --git a/backend/src/res/adjectives.json b/api/src/res/adjectives.json similarity index 100% rename from backend/src/res/adjectives.json rename to api/src/res/adjectives.json diff --git a/backend/src/res/crabs.json b/api/src/res/crabs.json similarity index 100% rename from backend/src/res/crabs.json rename to api/src/res/crabs.json diff --git a/backend/src/routes/event.rs b/api/src/routes/event.rs similarity index 100% rename from backend/src/routes/event.rs rename to api/src/routes/event.rs diff --git a/backend/src/routes/mod.rs b/api/src/routes/mod.rs similarity index 100% rename from backend/src/routes/mod.rs rename to api/src/routes/mod.rs diff --git a/backend/src/routes/person.rs b/api/src/routes/person.rs similarity index 100% rename from backend/src/routes/person.rs rename to api/src/routes/person.rs diff --git a/backend/src/routes/stats.rs b/api/src/routes/stats.rs similarity index 100% rename from backend/src/routes/stats.rs rename to api/src/routes/stats.rs From 0304f5955dc4fd46709da057228a68ed27dc01bb Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 01:37:01 +1000 Subject: [PATCH 23/29] Change how cleanup trait fn works using a date cutoff --- api/Cargo.lock | 2 +- api/adaptors/README.md | 1 + api/adaptors/datastore/src/lib.rs | 62 ++++++++++++++++++-------- api/adaptors/memory/src/lib.rs | 53 ++++++++++++++++------- api/adaptors/sql/src/lib.rs | 72 ++++++++++++++++++++----------- api/common/README.md | 2 +- api/common/src/adaptor.rs | 30 ------------- api/common/src/event.rs | 19 -------- api/common/src/lib.rs | 58 +++++++++++++++++++++++-- api/common/src/person.rs | 9 ---- api/common/src/stats.rs | 5 --- api/src/docs.rs | 1 + api/src/errors.rs | 2 +- api/src/main.rs | 1 + api/src/payloads.rs | 2 +- api/src/routes/event.rs | 2 +- api/src/routes/mod.rs | 1 + api/src/routes/person.rs | 4 +- api/src/routes/stats.rs | 2 +- api/src/routes/tasks.rs | 40 +++++++++++++++++ 20 files changed, 237 insertions(+), 131 deletions(-) delete mode 100644 api/common/src/adaptor.rs delete mode 100644 api/common/src/event.rs delete mode 100644 api/common/src/person.rs delete mode 100644 api/common/src/stats.rs create mode 100644 api/src/routes/tasks.rs diff --git a/api/Cargo.lock b/api/Cargo.lock index 0a5fd77..3c605c3 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -608,7 +608,7 @@ dependencies = [ ] [[package]] -name = "crabfit_backend" +name = "crabfit-api" version = "2.0.0" dependencies = [ "axum", diff --git a/api/adaptors/README.md b/api/adaptors/README.md index f1fbcaa..f8e7f1b 100644 --- a/api/adaptors/README.md +++ b/api/adaptors/README.md @@ -10,6 +10,7 @@ Note, you will need to have the following crates as dependencies in your adaptor - `common`
Includes a trait for implementing your adaptor, as well as structs your adaptor needs to return. - `async-trait`
Required because the trait from `common` uses async functions, make sure you include `#[async_trait]` above your trait implementation. +- `chrono`
Required to deal with dates in the common structs and trait function signatures. Once you've created the adaptor, you'll need to make sure it's included as a dependency in the root [`Cargo.toml`](../Cargo.toml), and add a feature flag with the same name. Make sure you also document the new adaptor in the [api readme](../README.md). diff --git a/api/adaptors/datastore/src/lib.rs b/api/adaptors/datastore/src/lib.rs index 3d4e33c..dc38e1b 100644 --- a/api/adaptors/datastore/src/lib.rs +++ b/api/adaptors/datastore/src/lib.rs @@ -2,15 +2,10 @@ use std::{env, error::Error, fmt::Display}; use async_trait::async_trait; use chrono::{DateTime, NaiveDateTime, Utc}; -use common::{ - adaptor::Adaptor, - event::{Event, EventDeletion}, - person::Person, - stats::Stats, -}; +use common::{Adaptor, Event, Person, Stats}; use google_cloud::{ authorize::ApplicationCredentials, - datastore::{Client, Filter, FromValue, IntoValue, Key, Query}, + datastore::{Client, Filter, FromValue, IntoValue, Key, KeyID, Query}, }; use tokio::sync::Mutex; @@ -95,9 +90,22 @@ impl Adaptor for DatastoreAdaptor { )) } - async fn upsert_person(&self, event_id: String, person: Person) -> Result { + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result, Self::Error> { let mut client = self.client.lock().await; + // Check the event exists + if client + .get::(Key::new(EVENT_KIND).id(event_id.clone())) + .await? + .is_none() + { + return Ok(None); + } + // Check if person exists let existing_person = client .query( @@ -122,7 +130,7 @@ impl Adaptor for DatastoreAdaptor { .put((key, DatastorePerson::from_person(person.clone(), event_id))) .await?; - Ok(person) + Ok(Some(person)) } async fn get_event(&self, id: String) -> Result, Self::Error> { @@ -151,25 +159,45 @@ impl Adaptor for DatastoreAdaptor { Ok(event) } - async fn delete_event(&self, id: String) -> Result { + async fn delete_events(&self, cutoff: DateTime) -> Result { let mut client = self.client.lock().await; let mut keys_to_delete: Vec = client - .query( - Query::new(PERSON_KIND) - .filter(Filter::Equal("eventId".into(), id.clone().into_value())), - ) + .query(Query::new(EVENT_KIND).filter(Filter::LesserThan( + "visited".into(), + cutoff.timestamp().into_value(), + ))) .await? .iter() .map(|entity| entity.key().clone()) .collect(); - let person_count = keys_to_delete.len().try_into().unwrap(); - keys_to_delete.insert(0, Key::new(EVENT_KIND).id(id.clone())); + let event_count = keys_to_delete.len() as i64; + + let events_to_delete = keys_to_delete.clone(); + for e in events_to_delete.iter() { + if let KeyID::StringID(id) = e.get_id() { + let mut event_people_to_delete: Vec = client + .query( + Query::new(PERSON_KIND) + .filter(Filter::Equal("eventId".into(), id.clone().into_value())), + ) + .await? + .iter() + .map(|entity| entity.key().clone()) + .collect(); + keys_to_delete.append(&mut event_people_to_delete); + } + } + + let person_count = keys_to_delete.len() as i64 - event_count; client.delete_all(keys_to_delete).await?; - Ok(EventDeletion { id, person_count }) + Ok(Stats { + event_count, + person_count, + }) } } diff --git a/api/adaptors/memory/src/lib.rs b/api/adaptors/memory/src/lib.rs index 200f8c4..2881696 100644 --- a/api/adaptors/memory/src/lib.rs +++ b/api/adaptors/memory/src/lib.rs @@ -1,13 +1,8 @@ use std::{collections::HashMap, error::Error, fmt::Display}; use async_trait::async_trait; -use chrono::Utc; -use common::{ - adaptor::Adaptor, - event::{Event, EventDeletion}, - person::Person, - stats::Stats, -}; +use chrono::{DateTime, Utc}; +use common::{Adaptor, Event, Person, Stats}; use tokio::sync::Mutex; struct State { @@ -68,14 +63,23 @@ impl Adaptor for MemoryAdaptor { )) } - async fn upsert_person(&self, event_id: String, person: Person) -> Result { + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result, Self::Error> { let mut state = self.state.lock().await; + // Check event exists + if state.events.get(&event_id).is_none() { + return Ok(None); + } + state .people .insert((event_id, person.name.clone()), person.clone()); - Ok(person) + Ok(Some(person)) } async fn get_event(&self, id: String) -> Result, Self::Error> { @@ -98,21 +102,38 @@ impl Adaptor for MemoryAdaptor { Ok(event) } - async fn delete_event(&self, id: String) -> Result { + async fn delete_events(&self, cutoff: DateTime) -> Result { let mut state = self.state.lock().await; - let mut person_count: u64 = state.people.len() as u64; + // Delete events older than cutoff date + let mut deleted_event_ids: Vec = Vec::new(); + state.events = state + .events + .clone() + .into_iter() + .filter(|(id, event)| { + if event.visited_at >= cutoff { + true + } else { + deleted_event_ids.push(id.into()); + false + } + }) + .collect(); + + let mut person_count = state.people.len() as i64; state.people = state .people .clone() .into_iter() - .filter(|((event_id, _), _)| event_id != &id) + .filter(|((event_id, _), _)| deleted_event_ids.contains(event_id)) .collect(); - person_count -= state.people.len() as u64; + person_count -= state.people.len() as i64; - state.events.remove(&id); - - Ok(EventDeletion { id, person_count }) + Ok(Stats { + event_count: deleted_event_ids.len() as i64, + person_count, + }) } } diff --git a/api/adaptors/sql/src/lib.rs b/api/adaptors/sql/src/lib.rs index f0a9137..8a706f3 100644 --- a/api/adaptors/sql/src/lib.rs +++ b/api/adaptors/sql/src/lib.rs @@ -1,13 +1,8 @@ use std::{env, error::Error}; use async_trait::async_trait; -use chrono::{DateTime as ChronoDateTime, Utc}; -use common::{ - adaptor::Adaptor, - event::{Event, EventDeletion}, - person::Person, - stats::Stats, -}; +use chrono::{DateTime, Utc}; +use common::{Adaptor, Event, Person, Stats}; use entity::{event, person, stats}; use migration::{Migrator, MigratorTrait}; use sea_orm::{ @@ -70,7 +65,11 @@ impl Adaptor for SqlAdaptor { }) } - async fn upsert_person(&self, event_id: String, person: Person) -> Result { + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result, Self::Error> { let data = person::ActiveModel { name: Set(person.name.clone()), password_hash: Set(person.password_hash), @@ -79,7 +78,16 @@ impl Adaptor for SqlAdaptor { event_id: Set(event_id.clone()), }; - Ok( + // Check if the event exists + if event::Entity::find_by_id(event_id.clone()) + .one(&self.db) + .await? + .is_none() + { + return Ok(None); + } + + Ok(Some( match person::Entity::find_by_id((person.name, event_id)) .one(&self.db) .await? @@ -87,7 +95,7 @@ impl Adaptor for SqlAdaptor { Some(_) => data.update(&self.db).await?.try_into_model()?.into(), None => data.insert(&self.db).await?.try_into_model()?.into(), }, - ) + )) } async fn get_event(&self, id: String) -> Result, Self::Error> { @@ -118,27 +126,43 @@ impl Adaptor for SqlAdaptor { .into()) } - async fn delete_event(&self, id: String) -> Result { - let event_id = id.clone(); - let person_count = self + async fn delete_events(&self, cutoff: DateTime) -> Result { + let (event_count, person_count) = self .db - .transaction::<_, u64, DbErr>(|t| { + .transaction::<_, (i64, i64), DbErr>(|t| { Box::pin(async move { + // Get events older than the cutoff date + let old_events = event::Entity::find() + .filter(event::Column::VisitedAt.lt(cutoff.naive_utc())) + .all(t) + .await?; + // Delete people - let people_delete_result = person::Entity::delete_many() - .filter(person::Column::EventId.eq(&event_id)) + let mut people_deleted: i64 = 0; + // TODO: run concurrently + for e in old_events.iter() { + let people_delete_result = person::Entity::delete_many() + .filter(person::Column::EventId.eq(&e.id)) + .exec(t) + .await?; + people_deleted += people_delete_result.rows_affected as i64; + } + + // Delete events + let event_delete_result = event::Entity::delete_many() + .filter(event::Column::VisitedAt.lt(cutoff.naive_utc())) .exec(t) .await?; - // Delete event - event::Entity::delete_by_id(event_id).exec(t).await?; - - Ok(people_delete_result.rows_affected) + Ok((event_delete_result.rows_affected as i64, people_deleted)) }) }) .await?; - Ok(EventDeletion { id, person_count }) + Ok(Stats { + event_count, + person_count, + }) } } @@ -190,8 +214,8 @@ impl From for Event { Self { id: value.id, name: value.name, - created_at: ChronoDateTime::::from_utc(value.created_at, Utc), - visited_at: ChronoDateTime::::from_utc(value.visited_at, Utc), + created_at: DateTime::::from_utc(value.created_at, Utc), + visited_at: DateTime::::from_utc(value.visited_at, Utc), times: serde_json::from_value(value.times).unwrap_or(vec![]), timezone: value.timezone, } @@ -203,7 +227,7 @@ impl From for Person { Self { name: value.name, password_hash: value.password_hash, - created_at: ChronoDateTime::::from_utc(value.created_at, Utc), + created_at: DateTime::::from_utc(value.created_at, Utc), availability: serde_json::from_value(value.availability).unwrap_or(vec![]), } } diff --git a/api/common/README.md b/api/common/README.md index 3ca3179..864ea8b 100644 --- a/api/common/README.md +++ b/api/common/README.md @@ -1,3 +1,3 @@ # Common -This crate contains the [adaptor trait](./src/adaptor.rs), and structs that are used by it. These are separated into their own crate so that the root crate and the adaptors can import from it without causing a circular dependency. +This crate contains the adaptor trait, and structs that are used by it. These are separated into their own crate so that the root crate and the adaptors can import from it without causing a circular dependency. diff --git a/api/common/src/adaptor.rs b/api/common/src/adaptor.rs deleted file mode 100644 index 73da286..0000000 --- a/api/common/src/adaptor.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::error::Error; - -use async_trait::async_trait; - -use crate::{ - event::{Event, EventDeletion}, - person::Person, - stats::Stats, -}; - -/// Data storage adaptor, all methods on an adaptor can return an error if -/// something goes wrong, or potentially None if the data requested was not found. -#[async_trait] -pub trait Adaptor: Send + Sync { - type Error: Error; - - async fn get_stats(&self) -> Result; - async fn increment_stat_event_count(&self) -> Result; - async fn increment_stat_person_count(&self) -> Result; - - async fn get_people(&self, event_id: String) -> Result>, Self::Error>; - async fn upsert_person(&self, event_id: String, person: Person) -> Result; - - /// Get an event and update visited date to current time - async fn get_event(&self, id: String) -> Result, Self::Error>; - async fn create_event(&self, event: Event) -> Result; - - /// Delete an event as well as all related people - async fn delete_event(&self, id: String) -> Result; -} diff --git a/api/common/src/event.rs b/api/common/src/event.rs deleted file mode 100644 index 1b2fd07..0000000 --- a/api/common/src/event.rs +++ /dev/null @@ -1,19 +0,0 @@ -use chrono::{DateTime, Utc}; - -#[derive(Clone)] -pub struct Event { - pub id: String, - pub name: String, - pub created_at: DateTime, - pub visited_at: DateTime, - pub times: Vec, - pub timezone: String, -} - -#[derive(Clone)] -/// Info about a deleted event -pub struct EventDeletion { - pub id: String, - /// The amount of people that were in this event that were also deleted - pub person_count: u64, -} diff --git a/api/common/src/lib.rs b/api/common/src/lib.rs index ae9caf1..5c485a4 100644 --- a/api/common/src/lib.rs +++ b/api/common/src/lib.rs @@ -1,4 +1,54 @@ -pub mod adaptor; -pub mod event; -pub mod person; -pub mod stats; +use std::error::Error; + +use async_trait::async_trait; +use chrono::{DateTime, Utc}; + +/// Data storage adaptor, all methods on an adaptor can return an error if +/// something goes wrong, or potentially None if the data requested was not found. +#[async_trait] +pub trait Adaptor: Send + Sync { + type Error: Error; + + async fn get_stats(&self) -> Result; + async fn increment_stat_event_count(&self) -> Result; + async fn increment_stat_person_count(&self) -> Result; + + async fn get_people(&self, event_id: String) -> Result>, Self::Error>; + async fn upsert_person( + &self, + event_id: String, + person: Person, + ) -> Result, Self::Error>; + + /// Get an event and update visited date to current time + async fn get_event(&self, id: String) -> Result, Self::Error>; + async fn create_event(&self, event: Event) -> Result; + + /// Delete events older than a cutoff date, as well as any associated people + /// Returns the amount of events and people deleted + async fn delete_events(&self, cutoff: DateTime) -> Result; +} + +#[derive(Clone)] +pub struct Stats { + pub event_count: i64, + pub person_count: i64, +} + +#[derive(Clone)] +pub struct Event { + pub id: String, + pub name: String, + pub created_at: DateTime, + pub visited_at: DateTime, + pub times: Vec, + pub timezone: String, +} + +#[derive(Clone)] +pub struct Person { + pub name: String, + pub password_hash: Option, + pub created_at: DateTime, + pub availability: Vec, +} diff --git a/api/common/src/person.rs b/api/common/src/person.rs deleted file mode 100644 index fd19b76..0000000 --- a/api/common/src/person.rs +++ /dev/null @@ -1,9 +0,0 @@ -use chrono::{DateTime, Utc}; - -#[derive(Clone)] -pub struct Person { - pub name: String, - pub password_hash: Option, - pub created_at: DateTime, - pub availability: Vec, -} diff --git a/api/common/src/stats.rs b/api/common/src/stats.rs deleted file mode 100644 index a88d949..0000000 --- a/api/common/src/stats.rs +++ /dev/null @@ -1,5 +0,0 @@ -#[derive(Clone)] -pub struct Stats { - pub event_count: i64, - pub person_count: i64, -} diff --git a/api/src/docs.rs b/api/src/docs.rs index 2ab310d..ae3ad11 100644 --- a/api/src/docs.rs +++ b/api/src/docs.rs @@ -17,6 +17,7 @@ use utoipa::{ routes::person::get_people, routes::person::get_person, routes::person::update_person, + routes::tasks::cleanup, ), components(schemas( payloads::StatsResponse, diff --git a/api/src/errors.rs b/api/src/errors.rs index 47719e0..fe1a1dc 100644 --- a/api/src/errors.rs +++ b/api/src/errors.rs @@ -1,5 +1,5 @@ use axum::{http::StatusCode, response::IntoResponse}; -use common::adaptor::Adaptor; +use common::Adaptor; pub enum ApiError { AdaptorError(A::Error), diff --git a/api/src/main.rs b/api/src/main.rs index 2dd0788..44bca8b 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -82,6 +82,7 @@ async fn main() { "/event/:event_id/people/:person_name", patch(person::update_person), ) + .route("/tasks/cleanup", patch(tasks::cleanup)) .with_state(shared_state) .layer(cors) .layer(rate_limit) diff --git a/api/src/payloads.rs b/api/src/payloads.rs index 089a2b8..6bf0176 100644 --- a/api/src/payloads.rs +++ b/api/src/payloads.rs @@ -1,5 +1,5 @@ use axum::Json; -use common::{event::Event, person::Person, stats::Stats}; +use common::{Event, Person, Stats}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; diff --git a/api/src/routes/event.rs b/api/src/routes/event.rs index 8f7945c..934b57c 100644 --- a/api/src/routes/event.rs +++ b/api/src/routes/event.rs @@ -3,7 +3,7 @@ use axum::{ http::StatusCode, Json, }; -use common::{adaptor::Adaptor, event::Event}; +use common::{Adaptor, Event}; use rand::{seq::SliceRandom, thread_rng, Rng}; use regex::Regex; diff --git a/api/src/routes/mod.rs b/api/src/routes/mod.rs index 5a4889d..7e1429b 100644 --- a/api/src/routes/mod.rs +++ b/api/src/routes/mod.rs @@ -1,3 +1,4 @@ pub mod event; pub mod person; pub mod stats; +pub mod tasks; diff --git a/api/src/routes/person.rs b/api/src/routes/person.rs index 28c8051..974a519 100644 --- a/api/src/routes/person.rs +++ b/api/src/routes/person.rs @@ -4,7 +4,7 @@ use axum::{ Json, TypedHeader, }; use base64::{engine::general_purpose, Engine}; -use common::{adaptor::Adaptor, person::Person}; +use common::{Adaptor, Person}; use crate::{ errors::ApiError, @@ -120,6 +120,7 @@ pub async fn get_person( ) .await .map_err(ApiError::AdaptorError)? + .unwrap() .into(), )) } @@ -189,6 +190,7 @@ pub async fn update_person( ) .await .map_err(ApiError::AdaptorError)? + .unwrap() .into(), )) } diff --git a/api/src/routes/stats.rs b/api/src/routes/stats.rs index 46398ce..539fffa 100644 --- a/api/src/routes/stats.rs +++ b/api/src/routes/stats.rs @@ -1,5 +1,5 @@ use axum::{extract, Json}; -use common::adaptor::Adaptor; +use common::Adaptor; use crate::{ errors::ApiError, diff --git a/api/src/routes/tasks.rs b/api/src/routes/tasks.rs new file mode 100644 index 0000000..4017f6d --- /dev/null +++ b/api/src/routes/tasks.rs @@ -0,0 +1,40 @@ +use std::env; + +use axum::{extract, http::HeaderMap}; +use common::Adaptor; +use tracing::info; + +use crate::{errors::ApiError, State}; + +#[utoipa::path( + get, + path = "/tasks/cleanup", + responses( + (status = 200, description = "Cleanup complete"), + (status = 401, description = "Missing or incorrect X-Cron-Key header"), + (status = 429, description = "Too many requests"), + ), + tag = "tasks", +)] +/// Delete events older than 3 months +pub async fn cleanup( + extract::State(state): State, + headers: HeaderMap, +) -> Result<(), ApiError> { + // Check cron key + let cron_key = headers.get("X-Cron-Key").ok_or(ApiError::NotAuthorized)?; + if let Ok(key) = env::var("CRON_KEY") { + if !key.is_empty() && *cron_key != key { + return Err(ApiError::NotAuthorized); + } + } + + info!("Running cleanup task"); + + let adaptor = &state.lock().await.adaptor; + + // TODO: + //let stats = adaptor.get_stats().await.map_err(ApiError::AdaptorError)?; + + Ok(()) +} From 68cf43164d6b353ee4886bd61584415f98f5ebca Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 02:06:56 +1000 Subject: [PATCH 24/29] Implement cleanup route --- api/Cargo.toml | 2 +- api/src/docs.rs | 7 +++++++ api/src/main.rs | 5 +++-- api/src/routes/tasks.rs | 23 +++++++++++++++++------ 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/api/Cargo.toml b/api/Cargo.toml index 0d5ff31..066af92 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -33,5 +33,5 @@ tower-http = { version = "0.4.0", features = ["cors", "trace"] } tower_governor = "0.0.4" tower = "0.4.13" utoipa = { version = "3.3.0", features = ["axum_extras", "preserve_order"] } -utoipa-swagger-ui = { version = "3.1.3", features = ["axum"] } +utoipa-swagger-ui = { version = "3.1.3", features = ["axum", "debug-embed"] } base64 = "0.21.0" diff --git a/api/src/docs.rs b/api/src/docs.rs index ae3ad11..34710a0 100644 --- a/api/src/docs.rs +++ b/api/src/docs.rs @@ -1,6 +1,8 @@ use crate::payloads; use crate::routes; +use utoipa::openapi::security::ApiKey; +use utoipa::openapi::security::ApiKeyValue; use utoipa::{ openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}, Modify, OpenApi, @@ -30,6 +32,7 @@ use utoipa::{ (name = "info"), (name = "event"), (name = "person"), + (name = "tasks"), ), modifiers(&SecurityAddon), )] @@ -49,5 +52,9 @@ impl Modify for SecurityAddon { .build(), ), ); + openapi.components.as_mut().unwrap().add_security_scheme( + "cron-key", + SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("X-Cron-Key"))), + ); } } diff --git a/api/src/main.rs b/api/src/main.rs index 44bca8b..5459081 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -12,6 +12,7 @@ use tokio::sync::Mutex; use tower::ServiceBuilder; use tower_governor::{errors::display_error, governor::GovernorConfigBuilder, GovernorLayer}; use tower_http::{cors::CorsLayer, trace::TraceLayer}; +use tracing::Level; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; @@ -32,7 +33,7 @@ pub type State = extract::State>>>; #[tokio::main] async fn main() { - tracing_subscriber::fmt::init(); + tracing_subscriber::fmt().with_max_level(Level::INFO).init(); // Load env dotenvy::dotenv().ok(); @@ -82,7 +83,7 @@ async fn main() { "/event/:event_id/people/:person_name", patch(person::update_person), ) - .route("/tasks/cleanup", patch(tasks::cleanup)) + .route("/tasks/cleanup", get(tasks::cleanup)) .with_state(shared_state) .layer(cors) .layer(rate_limit) diff --git a/api/src/routes/tasks.rs b/api/src/routes/tasks.rs index 4017f6d..b7a328f 100644 --- a/api/src/routes/tasks.rs +++ b/api/src/routes/tasks.rs @@ -1,6 +1,7 @@ use std::env; use axum::{extract, http::HeaderMap}; +use chrono::{Duration, Utc}; use common::Adaptor; use tracing::info; @@ -14,6 +15,7 @@ use crate::{errors::ApiError, State}; (status = 401, description = "Missing or incorrect X-Cron-Key header"), (status = 429, description = "Too many requests"), ), + security((), ("cron-key" = [])), tag = "tasks", )] /// Delete events older than 3 months @@ -22,10 +24,12 @@ pub async fn cleanup( headers: HeaderMap, ) -> Result<(), ApiError> { // Check cron key - let cron_key = headers.get("X-Cron-Key").ok_or(ApiError::NotAuthorized)?; - if let Ok(key) = env::var("CRON_KEY") { - if !key.is_empty() && *cron_key != key { - return Err(ApiError::NotAuthorized); + let cron_key_header = headers.get("X-Cron-Key"); + if let Some(cron_key) = cron_key_header { + if let Ok(key) = env::var("CRON_KEY") { + if !key.is_empty() && *cron_key != key { + return Err(ApiError::NotAuthorized); + } } } @@ -33,8 +37,15 @@ pub async fn cleanup( let adaptor = &state.lock().await.adaptor; - // TODO: - //let stats = adaptor.get_stats().await.map_err(ApiError::AdaptorError)?; + let result = adaptor + .delete_events(Utc::now() - Duration::days(90)) + .await + .map_err(ApiError::AdaptorError)?; + + info!( + "Cleanup successful: {} events and {} people removed", + result.event_count, result.person_count + ); Ok(()) } From bca67d2f0608a359360778816a683b61f61ff960 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 02:18:13 +1000 Subject: [PATCH 25/29] Fix logic and document CRON_KEY env variable --- api/README.md | 4 ++++ api/src/routes/tasks.rs | 14 +++++++------- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/api/README.md b/api/README.md index 3b9eade..4ae479f 100644 --- a/api/README.md +++ b/api/README.md @@ -24,3 +24,7 @@ Some adaptors require environment variables to be set. You can specify them in a ### Adding an adaptor See [adding an adaptor](adaptors/README.md#adding-an-adaptor) in the adaptors readme. + +## Cleanup task + +By default, anyone can run the cleanup task at `/tasks/cleanup`. This is usually not an issue, as it's based on when the events were last visited, and not when it's run, but if you'd prefer to restrict runs of the cleanup task (as it can be intensive), set a `CRON_KEY` environment variable in `.env`. This will require sending an `X-Cron-Key` header to the route with a value that matches `CRON_KEY`, or the route will return a 401 Unauthorized error. diff --git a/api/src/routes/tasks.rs b/api/src/routes/tasks.rs index b7a328f..b8c60a8 100644 --- a/api/src/routes/tasks.rs +++ b/api/src/routes/tasks.rs @@ -24,13 +24,13 @@ pub async fn cleanup( headers: HeaderMap, ) -> Result<(), ApiError> { // Check cron key - let cron_key_header = headers.get("X-Cron-Key"); - if let Some(cron_key) = cron_key_header { - if let Ok(key) = env::var("CRON_KEY") { - if !key.is_empty() && *cron_key != key { - return Err(ApiError::NotAuthorized); - } - } + let cron_key_header: String = headers + .get("X-Cron-Key") + .map(|k| k.to_str().unwrap_or_default().into()) + .unwrap_or_default(); + let env_key = env::var("CRON_KEY").unwrap_or_default(); + if !env_key.is_empty() && cron_key_header != env_key { + return Err(ApiError::NotAuthorized); } info!("Running cleanup task"); From fc18e6f85bb862e4d6b8bdae32cf71a125e70059 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 02:23:57 +1000 Subject: [PATCH 26/29] Update API version --- .gitignore | 1 - api/Cargo.lock | 2 +- api/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index fb6ff20..f11e864 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /graphics .DS_Store /browser-extension/*.zip -js-backend diff --git a/api/Cargo.lock b/api/Cargo.lock index 3c605c3..61a14d3 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -609,7 +609,7 @@ dependencies = [ [[package]] name = "crabfit-api" -version = "2.0.0" +version = "3.0.0" dependencies = [ "axum", "base64 0.21.0", diff --git a/api/Cargo.toml b/api/Cargo.toml index 066af92..116f5dd 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -2,7 +2,7 @@ name = "crabfit-api" description = "API for Crab Fit" license = "GPL-3.0-only" -version = "2.0.0" +version = "3.0.0" edition = "2021" [features] From f78d76299914ce539c91b9ff3f1acd8dac8dbb00 Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 02:42:01 +1000 Subject: [PATCH 27/29] Add docs for FRONTEND_URL env var --- .gitignore | 1 - README.md | 4 ++++ api/README.md | 8 +++++++- browser-extension/.gitignore | 1 + 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 browser-extension/.gitignore diff --git a/.gitignore b/.gitignore index f11e864..cfc9619 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ /graphics .DS_Store -/browser-extension/*.zip diff --git a/README.md b/README.md index e59204f..8ba0b35 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,10 @@ If you find any bugs or have a feature request, please create an issue by Date: Tue, 16 May 2023 15:36:53 +1000 Subject: [PATCH 28/29] Set up dockerfile, and handle SIGINT signals --- api/.dockerignore | 2 ++ api/Cargo.lock | 4 ++-- api/Dockerfile | 31 +++++++++++++++++++++++++++++++ api/src/main.rs | 7 ++++++- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 api/.dockerignore create mode 100644 api/Dockerfile diff --git a/api/.dockerignore b/api/.dockerignore new file mode 100644 index 0000000..c5dd462 --- /dev/null +++ b/api/.dockerignore @@ -0,0 +1,2 @@ +target +.env diff --git a/api/Cargo.lock b/api/Cargo.lock index 61a14d3..caf90f8 100644 --- a/api/Cargo.lock +++ b/api/Cargo.lock @@ -1122,7 +1122,7 @@ dependencies = [ [[package]] name = "google-cloud" version = "0.2.1" -source = "git+https://github.com/GRA0007/google-cloud-rs.git#5b2c3d6dcde9e58528c90c0a3016f123104c5fe3" +source = "git+https://github.com/GRA0007/google-cloud-rs.git#4a2db92efd57a896e14d18877458c6ae43418aec" dependencies = [ "chrono", "futures", @@ -1144,7 +1144,7 @@ dependencies = [ [[package]] name = "google-cloud-derive" version = "0.2.1" -source = "git+https://github.com/GRA0007/google-cloud-rs.git#5b2c3d6dcde9e58528c90c0a3016f123104c5fe3" +source = "git+https://github.com/GRA0007/google-cloud-rs.git#4a2db92efd57a896e14d18877458c6ae43418aec" dependencies = [ "darling", "quote", diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..4b611e3 --- /dev/null +++ b/api/Dockerfile @@ -0,0 +1,31 @@ +# This dockerfile builds the API and runs it on a minimal container with the Datastore adaptor + +FROM rust:latest as builder + +# Install CA Certs for Hyper +RUN apt-get install -y --no-install-recommends ca-certificates +RUN update-ca-certificates + +WORKDIR /usr/src/app +COPY . . +# Will build and cache the binary and dependent crates in release mode +RUN --mount=type=cache,target=/usr/local/cargo,from=rust:latest,source=/usr/local/cargo \ + --mount=type=cache,target=target \ + cargo build --release --features datastore-adaptor && mv ./target/release/crabfit-api ./api + +# Runtime image +FROM debian:bullseye-slim + +# Run as "app" user +RUN useradd -ms /bin/bash app + +USER app +WORKDIR /app + +# Get compiled binaries from builder's cargo install directory +COPY --from=builder /usr/src/app/api /app/api +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +# Run the app +EXPOSE 3000 +CMD ./api diff --git a/api/src/main.rs b/api/src/main.rs index 5459081..e2f46aa 100644 --- a/api/src/main.rs +++ b/api/src/main.rs @@ -89,7 +89,7 @@ async fn main() { .layer(rate_limit) .layer(TraceLayer::new_for_http()); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); println!( "🦀 Crab Fit API listening at http://{} in {} mode", @@ -102,6 +102,11 @@ async fn main() { ); Server::bind(&addr) .serve(app.into_make_service_with_connect_info::()) + .with_graceful_shutdown(async { + tokio::signal::ctrl_c() + .await + .expect("Failed to install Ctrl+C handler") + }) .await .unwrap(); } From 44288f99d1a7e4b63e7f90718ecf854c62e336fd Mon Sep 17 00:00:00 2001 From: Ben Grant Date: Tue, 16 May 2023 16:57:19 +1000 Subject: [PATCH 29/29] Create workflow to deploy the API on EC2 --- .github/workflows/deploy_api.yml | 58 +++++++++++++++++++ .github/workflows/deploy_backend.yml | 37 ------------ .../public/i18n/hu-HU/privacy.json | 0 3 files changed, 58 insertions(+), 37 deletions(-) create mode 100644 .github/workflows/deploy_api.yml delete mode 100644 .github/workflows/deploy_backend.yml rename {crabfit-frontend => frontend}/public/i18n/hu-HU/privacy.json (100%) diff --git a/.github/workflows/deploy_api.yml b/.github/workflows/deploy_api.yml new file mode 100644 index 0000000..79c336e --- /dev/null +++ b/.github/workflows/deploy_api.yml @@ -0,0 +1,58 @@ +name: Deploy API + +on: + push: + branches: ['main'] + paths: ['api/**'] + +env: + REGISTRY: ghcr.io + +jobs: + build-and-push: + name: Build Docker image and push to GitHub Container Registry + runs-on: ubuntu-latest + + defaults: + run: + working-directory: api + + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + - uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - uses: docker/metadata-action@v4 + id: meta + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/api + - uses: docker/build-push-action@v4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + deploy: + needs: [build-and-push] + name: Deploy to EC2 + runs-on: ubuntu-latest + + steps: + - uses: appleboy/ssh-action@master + with: + host: ${{ secrets.EC2_HOST }} + username: ${{ secrets.EC2_USERNAME }} + key: ${{ secrets.EC2_SSH_KEY }} + script: | + docker login ${{ env.REGISTRY }} -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} + docker pull ${{ env.REGISTRY }}/${{ github.repository }}/api:latest + docker stop crabfit-api + docker rm crabfit-api + docker run -d -p 3000:3000 --name crabfit-api --env-file ./.env ${{ env.REGISTRY }}/${{ github.repository }}/api:latest diff --git a/.github/workflows/deploy_backend.yml b/.github/workflows/deploy_backend.yml deleted file mode 100644 index e9e34c5..0000000 --- a/.github/workflows/deploy_backend.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Deploy Backend - -on: - push: - branches: ['main'] - paths: ['backend/**'] - -jobs: - deploy: - runs-on: ubuntu-latest - - defaults: - run: - working-directory: backend - - permissions: - contents: read - id-token: write - - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 17 - cache: yarn - cache-dependency-path: '**/yarn.lock' - - run: yarn install --immutable - - run: yarn build - - id: auth - uses: google-github-actions/auth@v0 - with: - credentials_json: '${{ secrets.GCP_SA_KEY }}' - - id: deploy - uses: google-github-actions/deploy-appengine@v0 - with: - working_directory: backend - version: v1 diff --git a/crabfit-frontend/public/i18n/hu-HU/privacy.json b/frontend/public/i18n/hu-HU/privacy.json similarity index 100% rename from crabfit-frontend/public/i18n/hu-HU/privacy.json rename to frontend/public/i18n/hu-HU/privacy.json